npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@3dsource/metabox-front-api

v3.0.5

Published

API for Metabox BASIC configurator

Downloads

539

Readme

Metabox Basic Configurator API

A powerful TypeScript API for seamlessly integrating and controlling the Metabox Basic Configurator in your web applications. This API provides full programmatic control over 3D product visualization, products, their material slots, and materials, environments.

Table of Contents

Features

  • 🚀 Easy integration with any web framework (Angular, React, Vue, etc.)
  • 📱 Responsive design support
  • 🎨 Full control over materials, environments, and products
  • 📸 Screenshot and PDF generation
  • 🎬 Showcase management
  • 📦 TypeScript support with full type definitions
  • 🌐 CDN support for quick prototyping

Prerequisites

  • Modern web browser with ES6 module support
  • HTTPS connection (required for unreal engine)
  • Valid Metabox basic configurator URL

Environment requirements (per package.json engines):

  • Node.js >= 20
  • npm > 9

Important: integrateMetabox validates inputs at runtime. It requires a non-empty configuratorId and a non-empty containerId. The iframe URL is constructed internally as https://{domain}/metabox-configurator/basic/{configuratorId} with optional query parameters (introImage, introVideo, loadingImage). HTTPS is enforced and non-HTTPS URLs are rejected.

Installation

Method 1: NPM Package (Recommended)

For use with frameworks like Angular, React, Vue, etc.:

npm install @3dsource/metabox-front-api@latest --save
# or pin a specific version

Then import the required components:

import { integrateMetabox, Communicator, ConfiguratorEnvelope, SetProduct, SetProductMaterial, SetEnvironment, GetScreenshot, GetPdf, saveImage } from '@3dsource/metabox-front-api';

Method 2: CDN Import

Alternative CDN options:

  • jsDelivr (latest): https://cdn.jsdelivr.net/npm/@3dsource/metabox-front-api@latest/+esm

For quick prototyping or vanilla JavaScript projects:

import { integrateMetabox, Communicator, ConfiguratorEnvelope, SetProduct, SetProductMaterial, SetEnvironment, GetScreenshot, GetPdf, saveImage } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-front-api@latest/+esm';

Quick Start

1. HTML Setup

Create a container element where the Metabox Configurator will be embedded:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Metabox Configurator</title>
    <style>
      #embed3DSource {
        width: 100%;
        height: 600px;
        border: 1px solid #ccc;
        border-radius: 8px;
      }
    </style>
  </head>
  <body>
    <div id="embed3DSource">
      <!-- Metabox Configurator will be embedded here -->
    </div>
  </body>
</html>

2. Basic Integration

JavaScript Example

<script type="module">
  import { integrateMetabox, SetProduct, SetProductMaterial, SetEnvironment, GetScreenshot, saveImage } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-front-api@latest/+esm';

  const configuratorId = 'configurator-id'; // your actual configurator id
  // Ensure a container with id embed3DSource exists (see HTML Setup)
  integrateMetabox(configuratorId, 'embed3DSource', (api) => {
    api.addEventListener('configuratorDataUpdated', (env) => {
      console.log('State updated:', env.productId, env.environmentId);
    });
    api.addEventListener('screenshotReady', (image) => {
      if (image) saveImage(image, `configurator-${Date.now()}.png`);
    });
    // Initial commands
    api.sendCommandToMetabox(new SetProduct('product-1'));
    api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1280, y: 720 }));
  });
</script>

TypeScript Example

import { integrateMetabox, Communicator, ConfiguratorEnvelope, SetProduct, SetProductMaterial, SetEnvironment, GetScreenshot, GetPdf, ShowEmbeddedMenu, ShowOverlayInterface, saveImage } from '@3dsource/metabox-front-api';

class MetaboxIntegrator {
  private api: Communicator | null = null;

  constructor() {
    this.initialize();
  }

  private async initialize(): Promise<void> {
    try {
      // Replace 'configurator-id' with your actual configurator ID
      const configuratorId = 'configurator-id';

      // Initialize the configurator with type safety
      integrateMetabox(configuratorId, 'embed3DSource', (api: Communicator) => {
        this.onApiReady(api);
      });
    } catch (error) {
      console.error('Failed to initialize Metabox:', error);
    }
  }

  private onApiReady(api: Communicator): void {
    this.api = api;
    console.log('Metabox API is ready!');

    // Set up event listeners with proper typing
    this.setupEventListeners();

    // Configure initial state
    this.configureInitialState();
  }

  private setupEventListeners(): void {
    if (!this.api) return;

    // Listen for configurator data updates
    this.api.addEventListener('configuratorDataUpdated', (data: ConfiguratorEnvelope) => {
      console.log('Configurator data updated:', data);
      this.handleConfiguratorUpdate(data);
    });

    // Listen for screenshot/render completion
    this.api.addEventListener('screenshotReady', (imageData: string) => {
      console.log('Screenshot ready');
      saveImage(imageData, 'configurator-screenshot.png');
    });
  }

  private handleConfiguratorUpdate(data: ConfiguratorEnvelope): void {
    // Handle configurator state changes with full type safety
    // Access ConfiguratorEnvelope properties.

    // Example: Update UI based on current product
    if (data) {
      console.log('Current state:', data);
    }
  }

  private configureInitialState(): void {
    if (!this.api) return;

    // Set initial product
    this.api.sendCommandToMetabox(new SetProduct('your-product-id'));

    // Set initial environment
    this.api.sendCommandToMetabox(new SetEnvironment('your-environment-id'));

    // Apply material to a specific slot
    this.api.sendCommandToMetabox(new SetProductMaterial('slot-id', 'material-id'));

    // Configure UI visibility
    this.api.sendCommandToMetabox(new ShowEmbeddedMenu(false));
    this.api.sendCommandToMetabox(new ShowOverlayInterface(false));

    // Generate initial screenshot
    this.api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1024, y: 1024 }));
  }

  // Public method to change product
  public changeProduct(productId: string): void {
    if (this.api) {
      this.api.sendCommandToMetabox(new SetProduct(productId));
    }
  }

  // Public method to apply material
  public applyMaterial(slotId: string, materialId: string): void {
    if (this.api) {
      this.api.sendCommandToMetabox(new SetProductMaterial(slotId, materialId));
    }
  }

  // Public method to take screenshot
  public takeScreenshot(width: number = 1024, height: number = 1024): void {
    if (this.api) {
      this.api.sendCommandToMetabox(new GetScreenshot('image/png', { x: width, y: height }));
    }
  }
}

// Initialize the integrator
const metaboxIntegrator = new MetaboxIntegrator();

// Example usage:
// metaboxIntegrator.changeProduct('new-product-id');
// metaboxIntegrator.applyMaterial('slot-1', 'material-2');
// metaboxIntegrator.takeScreenshot(1920, 1080);

API Reference

System Terminology

  • product - 3D digital twin of the actual physical product within a Metabox system; concept is used in Basic configurator.
  • productId - Unique Product ID within the Metabox system; mandatory. Assigned by the Metabox system automatically after the product's 3D digital twin is uploaded into Metabox.
  • environment - 3D digital twin of the actual environment (room scene or other) within a Metabox system.
  • environmentId - Unique environment ID within the Metabox system; mandatory. Assigned by the Metabox system automatically after the environment's 3D digital twin is uploaded into Metabox.
  • externalId - Product or Material ID assigned to product or material in Metabox; optional; can be a combination of letters or numbers or both. Should be identical to the product SKU number in any other non-Metabox e-commerce system that is used for managing products, so Metabox can be integrated with such a system.
  • showcase - Exists as an attribute of product only; optional. If present, it means the product's digital twin contains a camera sequence that causes the video mode added automatically to the configurator by Metabox system to play this camera sequence.
  • component - Product in the Modular configurator context.
  • componentId - Unique Component ID within the Metabox system; mandatory. Assigned by the Metabox system automatically after the Modular configurator is created in Metabox.
  • componentType - Exists in Modular Configurator only; the name of the type of the product component, which can include one or several separate products of the same category (examples of product component types: truck wheel, truck bumper). It is set by an unreal artist while creating the digital twin model and is automatically added to the Metabox system after this twin is uploaded to Metabox.
  • E‑Com Configurator — can be created from any Basic or Modular Configurator by adding a CTA (call‑to‑action) button to the standard configurator right‑nav menu in the Metabox Configurator Manager (where users create and edit configurators). To enable the CTA button, add two mandatory parameters in the Configurator Manager:
    • Text label for the CTA button (examples: "Get Quote", "Contact Dealer").
    • Callback URL for an HTTP POST request. Response requirements:
    {
      "redirectUrl": "https://your.site/thank-you-or-checkout"
    }
    When the user clicks the CTA button in the configurator:
    • Metabox generates a JSON payload with the current configuration and sends it to the specified Callback URL.
    • Your backend decides how to use the configuration JSON (e.g., generate a PDF or build a product list).
    • After your endpoint responds with a JSON containing "redirectUrl", the user is redirected in a new browser tab to that URL (a non‑Metabox page on your side) to complete the flow (e.g., leave contact information, proceed to checkout, etc.).

Steps to build a custom menu

  • configuratorDataUpdated - add event listener to receive configurator data and updates
  • showEmbeddedMenu - hide embedded metabox right sidebar; show again with the command
  • showOverlayInterface - hide viewport metabox actions if you want to add yours
  • setEnvironment - set active environment when you want to change environment
  • setEnvironmentMaterialById - set environment material by slot ID
  • setProduct - set active product when you want to change the product
  • setProductMaterialById - set product material by slot ID
  • getScreenshot - get screenshot
  • getCallToActionInformation - send call to action information to api endpoint provided in CTA configuration
  • getPdf - get PDF.

Core Functions

Integrates the Metabox configurator into the specified container element.

TypeScript Signature:

function integrateMetabox(
  configuratorId: string,
  containerId?: string, // defaults to 'embed3DSource'
  apiReadyCallback: (api: Communicator) => void, // required
  config?: IntegrateMetaboxConfig,
): void;

Parameters:

  • configuratorId (string, required): Basic Configurator ID (not a full URL). Internal iframe src: https://{domain}/metabox-configurator/basic/{configuratorId}.
  • containerId (string, optional): Host element id. Defaults to embed3DSource.
  • apiReadyCallback (required): Invoked once Metabox signals readiness.
  • config (optional):
    • standalone?: Disable built‑in UI/template logic inside iframe.
    • introImage?, introVideo?, loadingImage?: Optional URLs appended as query params.
    • state?: Optional initial configurator state.
    • domain?: Override domain (HTTPS enforced).

Throws: Error if configuratorId or containerId empty, container element missing, or generated URL not HTTPS.

Notes:

  • HTTPS is enforced: attempts to use non-HTTPS iframe URLs are rejected.
  • If a previous iframe with id embeddedContent exists, it will be removed before inserting a new one.

Example:

import { IntegrateMetaboxConfig } from '@3dsource/metabox-front-api';

const config: IntegrateMetaboxConfig = {
  introImage: 'https://example.com/intro.png',
  loadingImage: 'https://example.com/loading.png',
  standalone: false,
};

integrateMetabox(
  'configurator-id',
  'embed3DSource',
  (api) => {
    console.log('API ready!', api);
  },
  config,
);

Command Classes

All commands are sent using api.sendCommandToMetabox(new SomeCommand(...)).

Command Payload Types (Internal)

Product & Environment

  • SetProduct(productId: string) – Select active product.
  • SetProductMaterial(slotId: string, materialId: string) – Apply material to product slot.
  • SetEnvironment(id: string) – Activate environment.
  • SetEnvironmentMaterial(slotId: string, materialId: string) – Apply material to environment slot.

UI & Overlay

  • ShowEmbeddedMenu(visible: boolean) – Toggle right sidebar menu.
  • ShowOverlayInterface(visible: boolean) – Toggle viewport overlay UI.

Camera & View

  • ResetCamera() – Reset camera to initial state.
  • ApplyZoom(zoom: number) – Apply a zoom factor within allowed range.

Showcase Control

  • InitShowcase() – Initialize a product showcase sequence.
  • PlayShowcase() / PauseShowcase() / StopShowcase() – Control showcase playback.

Media & Export

  • GetScreenshot(format: MimeType, size?) – Request a screenshot (listen to screenshotReady).
  • GetPdf() – Request PDF generation (no dedicated event, handled internally server‑side; poll status messages if needed).
  • GetCallToActionInformation() – Trigger CTA flow (backend must return { redirectUrl }).

Advanced / Experimental

  • UnrealCommand(command: object) – Send low‑level command to Unreal. Use cautiously.
  • MetaboxConfig(appId: string, partialConfig) – Internal initialization command (auto‑sent on communicator creation).

Example:

import { UnrealCommand } from '@3dsource/metabox-front-api';
api.sendCommandToMetabox(new UnrealCommand({ type: 'SetLightIntensity', value: 2.5 }));

Event Handling

The API uses an event-driven architecture. Register listeners via api.addEventListener(eventName, handler).

Available Events

| Event | Payload Type | Description | | ----------------------------- | -------------------- | ---------------------------------------------------------- | ------ | -------------------------- | | configuratorDataUpdated | ConfiguratorEnvelope | Product/environment/material selections changed. | | ecomConfiguratorDataUpdated | EcomConfigurator | CTA config (label, callbackUrl) updated. | | dataChannelConnected | boolean | Unreal data channel connectivity state. | | viewportReady | boolean | Unreal viewport visibility and readiness. | | showcaseStatusChanged | ShowCaseStatus | Showcase playback status: init/play/pause/stop. | | statusMessageChanged | string | null | Human‑readable loading/progress message. | | screenshotReady | string | null | Base64 image data from latest screenshot (null if failed). | | videoResolutionChanged | { width: number | null; height: number | null } | Video stream size changes. |

Example: Basic State Listener

api.addEventListener('configuratorDataUpdated', (data) => {
  console.log('Product:', data.productId);
  console.log('Product materials:', data.productMaterialsIds);
  console.log('Environment:', data.environmentId);
  console.log('Environment materials:', data.environmentMaterialsIds);
});

Screenshot

api.addEventListener('screenshotReady', (image) => {
  if (!image) return;
});
pdfGenerated

Fired when PDF generation is completed.

api.addEventListener('pdfGenerated', (pdfData: string) => {
  console.log('PDF generated');
  // Handle PDF data
});

Utility Functions

saveImage

Utility function to save rendered images to a file.

function saveImage(imageUrl: string, filename: string): void;

Parameters:

  • imageUrl: The URL of the image to save (can be base64 encoded image data)
  • filename: The name of the file to save

Example:

api.addEventListener('screenshotReady', (imageData: string | null) => {
  saveImage(imageData ?? '', 'my-render.png');
});

Examples

E‑Com CTA example

Minimal example of triggering the Call‑To‑Action flow from your page once the API is ready.

<div id="metabox-container"></div>
<button id="cta-btn" disabled>Send configuration (CTA)</button>

<script type="module">
  import { integrateMetabox, GetCallToActionInformation } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-front-api@latest/+esm';

  let api = null;

  integrateMetabox('configurator-id', 'metabox-container', (apiInstance) => {
    api = apiInstance;
    document.getElementById('cta-btn').disabled = false;
  });

  document.getElementById('cta-btn').addEventListener('click', () => {
    // Triggers sending the current configuration to your CTA Callback URL
    api.sendCommandToMetabox(new GetCallToActionInformation());
  });
</script>

Basic Product Configurator

A complete HTML example showing how to integrate the Metabox configurator with vanilla JavaScript.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Product Configurator</title>
    <style>
      #metabox-container {
        width: 100%;
        height: 600px;
        border: 1px solid #ddd;
        border-radius: 8px;
      }

      .controls {
        margin: 20px 0;
      }

      button {
        margin: 5px;
        padding: 10px 15px;
        border: 1px solid #ccc;
        border-radius: 4px;
        background: #f5f5f5;
        cursor: pointer;
      }

      button:hover {
        background: #e5e5e5;
      }

      button:disabled {
        opacity: 0.5;
        cursor: not-allowed;
      }

      .loading {
        text-align: center;
        padding: 20px;
        color: #666;
      }

      .error {
        color: #d32f2f;
        padding: 10px;
        background: #ffebee;
        border: 1px solid #ffcdd2;
        border-radius: 4px;
        margin: 10px 0;
      }
    </style>
  </head>
  <body>
    <div id="loading" class="loading">Loading configurator...</div>
    <div id="error" class="error" style="display: none;"></div>
    <div id="metabox-container"></div>
    <div class="controls">
      <button id="product1-btn" onclick="changeProduct('product-1')" disabled>Product 1</button>
      <button id="product2-btn" onclick="changeProduct('product-2')" disabled>Product 2</button>
      <button id="red-material-btn" onclick="applyMaterial('slot-1', 'material-red')" disabled>Red Material</button>
      <button id="blue-material-btn" onclick="applyMaterial('slot-1', 'material-blue')" disabled>Blue Material</button>
      <button id="screenshot-btn" onclick="takeScreenshot()" disabled>Take Screenshot</button>
    </div>

    <script type="module">
      import { integrateMetabox, SetProduct, SetProductMaterial, GetScreenshot, saveImage } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-front-api@latest/+esm';

      let api = null;
      let isApiReady = false;

      // Helper function to show/hide loading state
      function setLoadingState(loading) {
        const loadingEl = document.getElementById('loading');
        const buttons = document.querySelectorAll('button');

        loadingEl.style.display = loading ? 'block' : 'none';
        buttons.forEach((btn) => (btn.disabled = loading || !isApiReady));
      }

      // Helper function to show error messages
      function showError(message) {
        const errorEl = document.getElementById('error');
        errorEl.textContent = message;
        errorEl.style.display = 'block';
        setLoadingState(false);
      }

      // Initialize configurator with error handling
      try {
        // Replace 'configurator-id' with your actual configurator ID from 3DSource
        const configuratorId = 'configurator-id';

        integrate -
          metabox.ts(configuratorId, 'metabox-container', (apiInstance) => {
            try {
              api = apiInstance;
              isApiReady = true;
              setLoadingState(false);
              console.log('Configurator ready!');

              // Listen for configurator state changes
              api.addEventListener('configuratorDataUpdated', (data) => {
                console.log('Configurator state updated:', data);
                // You can update your UI based on the current state
                // For example, update available materials, products, etc.
              });

              // Listen for screenshot completion
              api.addEventListener('screenshotReady', (imageData) => {
                console.log('Screenshot captured successfully');
                // Save the image with a timestamp
                saveImage(imageData, `configurator-screenshot-${Date.now()}.png`);
              });

              // Listen for errors
              api.addEventListener('error', (error) => {
                console.error('Configurator error:', error);
                showError('An error occurred in the configurator: ' + error.message);
              });
            } catch (error) {
              console.error('Error setting up API:', error);
              showError('Failed to initialize configurator API: ' + error.message);
            }
          });
      } catch (error) {
        console.error('Error initializing configurator:', error);
        showError('Failed to load configurator: ' + error.message);
      }

      // Global functions for button interactions
      window.changeProduct = (productId) => {
        if (!isApiReady || !api) {
          console.warn('API not ready yet');
          return;
        }

        try {
          console.log(`Changing to product: ${productId}`);
          api.sendCommandToMetabox(new SetProduct(productId));
        } catch (error) {
          console.error('Error changing product:', error);
          showError('Failed to change product: ' + error.message);
        }
      };

      window.applyMaterial = (slotId, materialId) => {
        if (!isApiReady || !api) {
          console.warn('API not ready yet');
          return;
        }

        try {
          console.log(`Applying material ${materialId} to slot ${slotId}`);
          api.sendCommandToMetabox(new SetProductMaterial(slotId, materialId));
        } catch (error) {
          console.error('Error applying material:', error);
          showError('Failed to apply material: ' + error.message);
        }
      };

      window.takeScreenshot = () => {
        if (!isApiReady || !api) {
          console.warn('API not ready yet');
          return;
        }

        try {
          console.log('Taking screenshot...');
          // Request high-quality screenshot in PNG format
          api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1920, y: 1080 }));
        } catch (error) {
          console.error('Error taking screenshot:', error);
          showError('Failed to take screenshot: ' + error.message);
        }
      };
    </script>
  </body>
</html>

React Integration Example

import React, { useEffect, useRef, useState, useCallback } from 'react';
import { integrateMetabox, Communicator, ConfiguratorEnvelope, SetProduct, SetProductMaterial, GetScreenshot, saveImage } from '@3dsource/metabox-front-api';

interface MetaboxConfiguratorProps {
  configuratorId: string;
  onStateChange?: (data: ConfiguratorEnvelope) => void;
  onError?: (error: string) => void;
  className?: string;
}

interface ConfiguratorState {
  isLoading: boolean;
  error: string | null;
  isApiReady: boolean;
}

const MetaboxConfigurator: React.FC<MetaboxConfiguratorProps> = ({ configuratorId, onStateChange, onError, className }) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const [api, setApi] = useState<Communicator | null>(null);
  const [state, setState] = useState<ConfiguratorState>({
    isLoading: true,
    error: null,
    isApiReady: false,
  });

  // Generate unique container ID to avoid conflicts
  const containerId = useRef(`metabox-container-${Math.random().toString(36).substr(2, 9)}`);

  // Error handler
  const handleError = useCallback(
    (error: string) => {
      setState((prev) => ({ ...prev, error, isLoading: false }));
      onError?.(error);
      console.error('Metabox Configurator Error:', error);
    },
    [onError],
  );

  // Initialize configurator
  useEffect(() => {
    if (!containerRef.current) return;

    let mounted = true;

    const initializeConfigurator = async () => {
      try {
        setState((prev) => ({ ...prev, isLoading: true, error: null }));

        // Set the container ID
        containerRef.current!.id = containerId.current;

        integrateMetabox(configuratorId, containerId.current, (apiInstance) => {
          if (!mounted) return; // Component was unmounted

          try {
            setApi(apiInstance);
            setState((prev) => ({ ...prev, isLoading: false, isApiReady: true }));

            // Set up event listeners with proper typing
            apiInstance.addEventListener('configuratorDataUpdated', (data: ConfiguratorEnvelope) => {
              if (mounted) {
                onStateChange?.(data);
              }
            });

            apiInstance.addEventListener('screenshotReady', (imageData: string) => {
              if (mounted) {
                console.log('Screenshot captured successfully');
                saveImage(imageData, `configurator-screenshot-${Date.now()}.png`);
              }
            });

            // Listen for errors from the configurator
            apiInstance.addEventListener('error', (error: any) => {
              if (mounted) {
                handleError(`Configurator error: ${error.message || error}`);
              }
            });

            console.log('React Metabox Configurator initialized successfully');
          } catch (error) {
            if (mounted) {
              handleError(`Failed to set up API: ${error instanceof Error ? error.message : String(error)}`);
            }
          }
        });
      } catch (error) {
        if (mounted) {
          handleError(`Failed to initialize configurator: ${error instanceof Error ? error.message : String(error)}`);
        }
      }
    };

    initializeConfigurator();

    // Cleanup function
    return () => {
      mounted = false;
      if (api) {
        // Clean up any event listeners if the API provides cleanup methods
        console.log('Cleaning up Metabox Configurator');
      }
    };
  }, [configuratorId, onStateChange, handleError, api]);

  // Command methods with error handling
  const changeProduct = useCallback(
    (productId: string) => {
      if (!state.isApiReady || !api) {
        console.warn('API not ready yet');
        return;
      }

      try {
        console.log(`Changing to product: ${productId}`);
        api.sendCommandToMetabox(new SetProduct(productId));
      } catch (error) {
        handleError(`Failed to change product: ${error instanceof Error ? error.message : String(error)}`);
      }
    },
    [api, state.isApiReady, handleError],
  );

  const applyMaterial = useCallback(
    (slotId: string, materialId: string) => {
      if (!state.isApiReady || !api) {
        console.warn('API not ready yet');
        return;
      }

      try {
        console.log(`Applying material ${materialId} to slot ${slotId}`);
        api.sendCommandToMetabox(new SetProductMaterial(slotId, materialId));
      } catch (error) {
        handleError(`Failed to apply material: ${error instanceof Error ? error.message : String(error)}`);
      }
    },
    [api, state.isApiReady, handleError],
  );

  const takeScreenshot = useCallback(() => {
    if (!state.isApiReady || !api) {
      console.warn('API not ready yet');
      return;
    }

    try {
      console.log('Taking screenshot...');
      api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1920, y: 1080 }));
    } catch (error) {
      handleError(`Failed to take screenshot: ${error instanceof Error ? error.message : String(error)}`);
    }
  }, [api, state.isApiReady, handleError]);

  // Render error state
  if (state.error) {
    return (
      <div className={className}>
        <div
          style={{
            color: '#d32f2f',
            padding: '20px',
            background: '#ffebee',
            border: '1px solid #ffcdd2',
            borderRadius: '4px',
            textAlign: 'center',
          }}
        >
          <h3>Configurator Error</h3>
          <p>{state.error}</p>
          <button
            onClick={() => window.location.reload()}
            style={{
              padding: '10px 20px',
              marginTop: '10px',
              border: '1px solid #d32f2f',
              background: '#fff',
              color: '#d32f2f',
              borderRadius: '4px',
              cursor: 'pointer',
            }}
          >
            Reload Page
          </button>
        </div>
      </div>
    );
  }

  return (
    <div className={className}>
      {/* Loading indicator */}
      {state.isLoading && (
        <div
          style={{
            textAlign: 'center',
            padding: '40px',
            color: '#666',
            background: '#f5f5f5',
            borderRadius: '8px',
          }}
        >
          <div>Loading configurator...</div>
          <div style={{ marginTop: '10px', fontSize: '14px' }}>Please wait while we initialize the 3D viewer</div>
        </div>
      )}

      {/* Configurator container */}
      <div
        ref={containerRef}
        style={{
          width: '100%',
          height: '600px',
          border: '1px solid #ddd',
          borderRadius: '8px',
          display: state.isLoading ? 'none' : 'block',
        }}
      />

      {/* Controls */}
      {state.isApiReady && (
        <div style={{ marginTop: '20px' }}>
          <div style={{ marginBottom: '10px', fontWeight: 'bold', color: '#333' }}>Configurator Controls:</div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px' }}>
            <button
              onClick={() => changeProduct('product-1')}
              disabled={!state.isApiReady}
              style={{
                padding: '10px 15px',
                border: '1px solid #ccc',
                borderRadius: '4px',
                background: state.isApiReady ? '#f5f5f5' : '#e0e0e0',
                cursor: state.isApiReady ? 'pointer' : 'not-allowed',
                transition: 'background-color 0.2s',
              }}
              onMouseOver={(e) => {
                if (state.isApiReady) e.currentTarget.style.background = '#e5e5e5';
              }}
              onMouseOut={(e) => {
                if (state.isApiReady) e.currentTarget.style.background = '#f5f5f5';
              }}
            >
              Product 1
            </button>
            <button
              onClick={() => changeProduct('product-2')}
              disabled={!state.isApiReady}
              style={{
                padding: '10px 15px',
                border: '1px solid #ccc',
                borderRadius: '4px',
                background: state.isApiReady ? '#f5f5f5' : '#e0e0e0',
                cursor: state.isApiReady ? 'pointer' : 'not-allowed',
              }}
            >
              Product 2
            </button>
            <button
              onClick={() => applyMaterial('slot-1', 'material-red')}
              disabled={!state.isApiReady}
              style={{
                padding: '10px 15px',
                border: '1px solid #ccc',
                borderRadius: '4px',
                background: state.isApiReady ? '#f5f5f5' : '#e0e0e0',
                cursor: state.isApiReady ? 'pointer' : 'not-allowed',
              }}
            >
              Red Material
            </button>
            <button
              onClick={() => applyMaterial('slot-1', 'material-blue')}
              disabled={!state.isApiReady}
              style={{
                padding: '10px 15px',
                border: '1px solid #ccc',
                borderRadius: '4px',
                background: state.isApiReady ? '#f5f5f5' : '#e0e0e0',
                cursor: state.isApiReady ? 'pointer' : 'not-allowed',
              }}
            >
              Blue Material
            </button>
            <button
              onClick={takeScreenshot}
              disabled={!state.isApiReady}
              style={{
                padding: '10px 15px',
                border: '1px solid #007bff',
                borderRadius: '4px',
                background: state.isApiReady ? '#007bff' : '#6c757d',
                color: 'white',
                cursor: state.isApiReady ? 'pointer' : 'not-allowed',
              }}
            >
              📸 Take Screenshot
            </button>
          </div>
        </div>
      )}
    </div>
  );
};

export default MetaboxConfigurator;

Angular Integration Example

// metabox-configurator.component.ts
import { Component, input, OnInit, output, signal } from '@angular/core';
import { Communicator, ConfiguratorEnvelope, GetPdf, GetScreenshot, InitShowcase, integrateMetabox, PauseShowcase, PlayShowcase, saveImage, SetProductMaterial, SetProduct, ShowEmbeddedMenu, ShowOverlayInterface, StopShowcase } from '@3dsource/metabox-front-api';

interface ConfiguratorState {
  isLoading: boolean;
  error: string | null;
  isApiReady: boolean;
}

@Component({
  selector: 'app-metabox-configurator',
  template: `
    @let _state = state();
    <div class="configurator-container">
      <!-- Loading State -->
      @if (_state.isLoading) {
        <div class="loading-container">
          <div class="loading-content">
            <div class="loading-spinner"></div>
            <div class="loading-text">Loading configurator...</div>
            <div class="loading-subtext">Please wait while we initialize the 3D viewer</div>
          </div>
        </div>
      }

      <!-- Error State -->
      @if (_state.error) {
        <div class="error-container">
          <h3>Configurator Error</h3>
          <p>{{ _state.error }}</p>
          <button (click)="retryInitialization()" class="retry-button">Retry</button>
        </div>
      }

      <!-- Configurator Container -->
      <div [id]="containerId()" class="configurator-viewport" [hidden]="_state.isLoading || _state.error"></div>

      <!-- Controls -->
      @if (_state.isApiReady) {
        <div class="controls">
          <div class="controls-title">Configurator Controls:</div>
          <div class="controls-buttons">
            <button (click)="changeProduct('541f46ab-a86c-48e3-bcfa-f92341483db3')" [disabled]="!_state.isApiReady" class="control-button">Product Change</button>
            <button (click)="initShowcase()" [disabled]="!_state.isApiReady" class="control-button">Init showcase</button>
            <button (click)="stopShowcase()" [disabled]="!_state.isApiReady" class="control-button">Stop showcase</button>
            <button (click)="playShowcase()" [disabled]="!_state.isApiReady" class="control-button">Play showcase</button>
            <button (click)="pauseShowcase()" [disabled]="!_state.isApiReady" class="control-button">Pause showcase</button>
            <button (click)="applyMaterial('slot-1', 'material-red')" [disabled]="!_state.isApiReady" class="control-button">Red Material</button>
            <button (click)="applyMaterial('slot-1', 'material-blue')" [disabled]="!_state.isApiReady" class="control-button">Blue Material</button>
            <button (click)="getPdf()" [disabled]="!_state.isApiReady" class="control-button">Get PDF</button>
            <button (click)="takeScreenshot()" [disabled]="!_state.isApiReady" class="control-button screenshot-button">📸 Take Screenshot</button>
            <button (click)="sendCallToActionInformation()" [disabled]="!_state.isApiReady" class="control-button">Send Call To Action Information</button>
          </div>
        </div>
      }
    </div>
  `,
  styles: [
    `
      .configurator-container {
        width: 100%;
        position: relative;
      }

      .configurator-viewport {
        width: 100%;
        height: 600px;
        border: 1px solid #ddd;
        border-radius: 8px;
      }

      .loading-container {
        display: flex;
        align-items: center;
        justify-content: center;
        height: 600px;
        background: #f5f5f5;
        border: 1px solid #ddd;
        border-radius: 8px;
      }

      .loading-content {
        text-align: center;
        color: #666;
      }

      .loading-spinner {
        width: 40px;
        height: 40px;
        border: 4px solid #f3f3f3;
        border-top: 4px solid #007bff;
        border-radius: 50%;
        animation: spin 1s linear infinite;
        margin: 0 auto 20px;
      }

      @keyframes spin {
        0% {
          transform: rotate(0deg);
        }
        100% {
          transform: rotate(360deg);
        }
      }

      .loading-text {
        font-size: 16px;
        font-weight: 500;
        margin-bottom: 8px;
      }

      .loading-subtext {
        font-size: 14px;
        opacity: 0.8;
      }

      .error-container {
        padding: 40px;
        text-align: center;
        background: #ffebee;
        border: 1px solid #ffcdd2;
        border-radius: 8px;
        color: #d32f2f;
      }

      .error-container h3 {
        margin: 0 0 16px 0;
        font-size: 18px;
      }

      .error-container p {
        margin: 0 0 20px 0;
        font-size: 14px;
      }

      .retry-button {
        padding: 10px 20px;
        border: 1px solid #d32f2f;
        background: #fff;
        color: #d32f2f;
        border-radius: 4px;
        cursor: pointer;
        font-size: 14px;
      }

      .retry-button:hover {
        background: #d32f2f;
        color: #fff;
      }

      .controls {
        margin-top: 20px;
      }

      .controls-title {
        font-weight: bold;
        color: #333;
        margin-bottom: 10px;
      }

      .controls-buttons {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
      }

      .control-button {
        padding: 10px 15px;
        border: 1px solid #ccc;
        border-radius: 4px;
        background: #f5f5f5;
        cursor: pointer;
        transition: all 0.2s;
        font-size: 14px;
      }

      .control-button:hover:not(:disabled) {
        background: #e5e5e5;
      }

      .control-button:disabled {
        opacity: 0.5;
        cursor: not-allowed;
      }

      .screenshot-button {
        border-color: #007bff;
        background: #007bff;
        color: white;
      }

      .screenshot-button:hover:not(:disabled) {
        background: #0056b3;
      }
    `,
  ],
})
export class MetaboxConfiguratorComponent implements OnInit {
  configuratorId = input.required<string>();
  stateChange = output<ConfiguratorEnvelope>();
  errorFired = output<string>();
  state = signal<ConfiguratorState>({
    isLoading: true,
    error: null,
    isApiReady: false,
  });
  containerId = signal(`metabox-container-${Math.random().toString(36).substring(2, 9)}`);
  private api: Communicator | null = null;

  ngOnInit(): void {
    this.initializeConfigurator();
  }

  // Public methods for external control
  changeProduct(productId: string): void {
    if (!this.state().isApiReady || !this.api) {
      console.warn('API not ready yet');
      return;
    }

    try {
      console.log(`Changing to product: ${productId}`);
      this.api.sendCommandToMetabox(new SetProduct(productId));
    } catch (error) {
      this.handleError(`Failed to change product: ${this.getErrorMessage(error)}`);
    }
  }

  initShowcase(): void {
    if (!this.state().isApiReady || !this.api) {
      console.warn('API not ready yet');
      return;
    }

    try {
      this.api.sendCommandToMetabox(new InitShowcase());
    } catch (error) {
      this.handleError(`Failed to init showcase for product: ${this.getErrorMessage(error)}`);
    }
  }

  stopShowcase(): void {
    if (!this.state().isApiReady || !this.api) {
      console.warn('API not ready yet');
      return;
    }

    try {
      console.log(`Stop showcase`);
      this.api.sendCommandToMetabox(new StopShowcase());
    } catch (error) {
      this.handleError(`Failed to init showcase for product: ${this.getErrorMessage(error)}`);
    }
  }

  playShowcase(): void {
    if (!this.state().isApiReady || !this.api) {
      console.warn('API not ready yet');
      return;
    }

    try {
      console.log(`Play showcase`);
      this.api.sendCommandToMetabox(new PlayShowcase());
    } catch {
      this.handleError(`Failed to play showcase`);
    }
  }

  pauseShowcase(): void {
    if (!this.state().isApiReady || !this.api) {
      console.warn('API not ready yet');
      return;
    }

    try {
      console.log(`Pause showcase`);
      this.api.sendCommandToMetabox(new PauseShowcase());
    } catch {
      this.handleError(`Failed to pause showcase`);
    }
  }

  applyMaterial(slotId: string, materialId: string): void {
    if (!this.state().isApiReady || !this.api) {
      console.warn('API not ready yet');
      return;
    }

    try {
      console.log(`Applying material ${materialId} to slot ${slotId}`);
      this.api.sendCommandToMetabox(new SetProductMaterial(slotId, materialId));
    } catch (error) {
      this.handleError(`Failed to apply material: ${this.getErrorMessage(error)}`);
    }
  }

  takeScreenshot(): void {
    if (!this.state().isApiReady || !this.api) {
      console.warn('API not ready yet');
      return;
    }

    try {
      console.log('Taking screenshot...');
      this.api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1920, y: 1080 }));
    } catch (error) {
      this.handleError(`Failed to take screenshot: ${this.getErrorMessage(error)}`);
    }
  }

  sendCallToActionInformation(): void {
    if (!this.state().isApiReady || !this.api) {
      console.warn('API not ready yet');
      return;
    }
    try {
      console.log('Generating cta information...');
      this.api.sendCommandToMetabox(new GetCallToActionInformation());
    } catch (error) {
      this.handleError(`Failed to generating cta information: ${this.getErrorMessage(error)}`);
    }
  }

  getPdf(): void {
    if (!this.state().isApiReady || !this.api) {
      console.warn('API not ready yet');
      return;
    }

    try {
      console.log('Generating PDF...');
      this.api.sendCommandToMetabox(new GetPdf());
    } catch (error) {
      this.handleError(`Failed to generating pdf: ${this.getErrorMessage(error)}`);
    }
  }

  retryInitialization(): void {
    this.updateState({ error: null });
    this.initializeConfigurator();
  }

  sendInitCommands() {
    if (!this.api) {
      return;
    }

    this.api.sendCommandToMetabox(new ShowEmbeddedMenu(true));
    this.api.sendCommandToMetabox(new ShowOverlayInterface(true));
  }

  private initializeConfigurator() {
    try {
      this.updateState({ isLoading: true, error: null });

      integrateMetabox(this.configuratorId(), this.containerId(), (apiInstance) => {
        try {
          this.api = apiInstance;
          this.updateState({ isLoading: false, isApiReady: true });
          this.sendInitCommands();
          this.setupEventListeners();

          console.log('Angular Metabox Configurator initialized successfully');
        } catch (error) {
          this.handleError(`Failed to set up API: ${this.getErrorMessage(error)}`);
        }
      });
    } catch (error) {
      this.handleError(`Failed to initialize configurator: ${this.getErrorMessage(error)}`);
    }
  }

  private setupEventListeners(): void {
    if (!this.api) {
      return;
    }
    // Listen for configurator state changes
    this.api.addEventListener('configuratorDataUpdated', (data: ConfiguratorEnvelope) => {
      console.log('Configurator state updated:', data);
      this.stateChange.emit(data);
    });

    // Listen for screenshot completion
    this.api.addEventListener('screenshotReady', (imageData: string | null) => {
      console.log(`Screenshot captured successfully ${imageData ?? ''}`);
      saveImage(imageData ?? '', `configurator-screenshot-${Date.now()}.png`);
    });
  }

  private updateState(updates: Partial<ConfiguratorState>): void {
    this.state.set({ ...this.state(), ...updates });
  }

  private handleError(errorMessage: string): void {
    console.error('Metabox Configurator Error:', errorMessage);
    this.updateState({ error: errorMessage, isLoading: false });
    this.errorFired.emit(errorMessage);
  }

  private getErrorMessage(error: any): string {
    if (error instanceof Error) {
      return error.message;
    }
    return String(error);
  }
}

Key Features of this Angular Example:

  • Comprehensive Error Handling: Try-catch blocks and user-friendly error states
  • Proper Lifecycle Management: OnDestroy implementation with cleanup
  • Loading States: Visual feedback with spinner during initialization
  • TypeScript Integration: Full type safety with proper interfaces
  • Unique Container IDs: Automatic ID generation to avoid conflicts
  • Event Outputs: Emits state changes and errors to parent components
  • Responsive Design: Clean, accessible button layout with proper styling
  • Component Cleanup: Prevents memory leaks and handles component destruction

Usage Example:

// app.component.ts
import { Component } from '@angular/core';
import { ConfiguratorEnvelope } from '@3dsource/metabox-front-api';

@Component({
  selector: 'app-root',
  template: `
    <div class="app-container">
      <h1>My Product Configurator</h1>
      <app-metabox-configurator [configuratorId]="configuratorId" (stateChange)="onConfiguratorStateChange($event)" (errorFired)="onConfiguratorError($event)"></app-metabox-configurator>
    </div>
  `,
  styles: [
    `
      .app-container {
        max-width: 1200px;
        margin: 0 auto;
        padding: 20px;
      }
    `,
  ],
})
export class AppComponent {
  configuratorId = 'configurator-id';

  onConfiguratorStateChange(data: ConfiguratorEnvelope): void {
    console.log('Configurator state changed:', data);
    // Update your application state based on configurator changes
  }

  onConfiguratorError(error: string): void {
    console.error('Configurator error:', error);
    // Handle errors (show notifications, log to analytics, etc.)
  }
}

To Use This Component:

  1. Install the package: npm install @3dsource/metabox-front-api@latest
  2. Import the component in your Angular module
  3. Replace 'configurator-id' with your actual configurator ID
  4. Replace placeholder IDs with your real product, material, and slot IDs
  5. Customize the styling by modifying the component styles

Best Practices

1. Error Handling

Always implement proper error handling when working with the API:

integrateMetabox(configuratorId, containerId, (api) => {
  try {
    // Set up event listeners with error handling
    api.addEventListener('configuratorDataUpdated', (data) => {
      try {
        handleConfiguratorUpdate(data);
      } catch (error) {
        console.error('Error handling configurator update:', error);
      }
    });

    // Send commands with error handling
    api.sendCommandToMetabox(new SetProduct(productId));
  } catch (error) {
    console.error('Error initializing configurator:', error);
  }
});

2. State Management

Keep track of the configurator state for a better user experience:

class ConfiguratorStateManager {
  private currentState = {
    product: null,
    materials: {},
    environment: null,
  };

  updateState(data) {
    this.currentState = { ...this.currentState, ...data };
    this.saveStateToLocalStorage();
  }

  saveStateToLocalStorage() {
    localStorage.setItem('configuratorState', JSON.stringify(this.currentState));
  }

  loadStateFromLocalStorage() {
    const saved = localStorage.getItem('configuratorState');
    return saved ? JSON.parse(saved) : null;
  }
}

3. Performance Optimization

  • Debounce rapid commands: Avoid sending too many commands in quick succession
// Debounce example
const debouncedMaterialChange = debounce((slotId, materialId) => {
  api.sendCommandToMetabox(new SetProductMaterial(slotId, materialId));
}, 300);

4. Responsive Design

Ensure the configurator works well on different screen sizes:

#embed3DSource {
  width: 100%;
  height: 60vh;
  min-height: 400px;
  max-height: 800px;
}

@media (max-width: 768px) {
  #embed3DSource {
    height: 50vh;
    min-height: 300px;
  }
}

5. Loading States

Provide feedback to users while the configurator loads:

const showLoadingState = () => {
  document.getElementById('loading').style.display = 'block';
};

const hideLoadingState = () => {
  document.getElementById('loading').style.display = 'none';
};

integrateMetabox(configuratorId, containerId, (api) => {
  hideLoadingState();
  // Continue with initialization
});

Troubleshooting

Common Issues

1. Configurator Not Loading

Problem: The configurator iframe doesn't appear or shows a blank screen.

Solutions:

  • Verify the configurator ID is correct and accessible
  • Ensure the container element exists in the DOM before calling integrateMetabox
  • Check that you're using HTTPS (required for Unreal Engine)
  • Verify your configurator ID is valid
// Check if container exists
const container = document.getElementById('embed3DSource');
if (!container) {
  console.error('Container element not found');
  return;
}

// Verify HTTPS
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
  console.warn('HTTPS is required for Metabox configurator');
}

2. Commands Not Working

Problem: Commands sent to the configurator don't have any effect.

Solutions:

  • Ensure the API is fully initialized before sending commands
  • Check that product/material/environment IDs are correct
  • Verify the configurator supports the specific command
let apiReady = false;

integrateMetabox(configuratorId, containerId, (api) => {
  apiReady = true;
  // Now it's safe to send commands
});

// Wait for API to be ready
const sendCommand = (command) => {
  if (apiReady && api) {
    api.sendCommandToMetabox(command);
  } else {
    console.warn('API not ready yet');
  }
};

3. Events Not Firing

Problem: Event listeners don't receive events from the configurator.

Solutions:

  • Set up event listeners immediately after API initialization
  • Check event names for typos
  • Ensure the configurator is configured to send the expected events
integrateMetabox(configuratorId, containerId, (api) => {
  // Set up listeners immediately
  api.addEventListener('configuratorDataUpdated', (data) => {
    console.log('Event received:', data);
  });

  // Test if events are working
  setTimeout(() => {
    console.log('Testing event system...');
    api.sendCommandToMetabox(new SetProduct('test-product'));
  }, 1000);
});

4. Screenshot/PDF Generation Issues

Problem: Screenshots or PDFs are not generated or are of poor quality.

Solutions:

  • Wait for the scene to fully load before capturing
  • Use appropriate dimensions for your use case
  • Ensure the configurator viewport is visible (not hidden or zero-sized)
// Wait for scene to load before screenshot
api.addEventListener('configuratorDataUpdated', (data) => {
  // Scene is loaded, safe to take screenshot
  setTimeout(() => {
    api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1920, y: 1080 }));
  }, 500);
});

Getting Help

If you're still experiencing issues:

  1. Check the browser console for error messages
  2. Verify your configurator ID in a separate browser tab
  3. Test with a minimal example to isolate the issue
  4. Contact support with:
  • Browser version and type
  • Configurator URL (if possible)
  • Console error messages
  • Steps to reproduce the issue

Standalone Mode

Standalone mode lets you embed the Metabox renderer without the built‑in Metabox UI and template logic. This is ideal when you want to build a fully custom UI (menus, buttons, flows) and drive the configurator entirely through this API.

When standalone is enabled, the embedded page does not render Metabox’s internal menus, overlays, or template logic. You are responsible for sending commands (e.g., setProduct, setMaterial, setEnvironment) and listening to events to keep your UI in sync.

Key points:

  • Purpose: use when you need a 100% custom interface and logic on the host page.
  • Behavior: Metabox default UI, embedded menu, and template logic are disabled on the iframe side.
  • Control: you drive everything with API commands and events.

How to enable

Pass as param to config with a standalone set to true when calling integrateMetabox.

TypeScript example:

import { integrateMetabox, Communicator, SetProduct, SetEnvironment } from '@3dsource/metabox-front-api';

integrateMetabox(
  'configurator-id',
  'embed3DSource',
  (api: Communicator) => {
    // Send your initial commands to establish state
    api.sendCommandToMetabox(new SetProduct('your-product-id'));
    api.sendCommandToMetabox(new SetEnvironment('your-environment-id'));
  },
  { standalone: true },
);

JavaScript (CDN) example:

<script type="module">
  import { integrateMetabox, SetProduct, SetEnvironment } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-front-api@latest/+esm';

  integrateMetabox(
    'configurator-id',
    'embed3DSource',
    (api) => {
      api.sendCommandToMetabox(new SetProduct('your-product-id'));
      api.sendCommandToMetabox(new SetEnvironment('your-environment-id'));
    },
    { standalone: true },
  );
</script>

Notes and tips:

  • In standalone mode, nothing will change until you send initial commands (e.g., set product/environment). If you see a blank/default view, ensure you dispatch the necessary SetProduct/SetEnvironment commands once api is ready.
  • All events (like configuratorDataUpdated, screenshotReady) still work; use them to keep your UI synchronized.
  • Runtime validation still applies: configuratorId and containerId must be non-empty. HTTPS is enforced for the internally constructed iframe URL. The hostUrl and apiVersion values are handled internally; you do not need to pass them.