Skip to content

TECSAFE/widget-sdk

Repository files navigation

Widget SDK

The TECSAFE Widget SDK provides a convenience wrapper to interact with TECSAFE Widgets (PDP-Layout-Configurator, Layout-Editor, Layout-Project-App), handling IFrame communication and JWT management automatically. The full Widget API documentation can be found on tecsafe.github.io/widget-sdk.

API Usage

The Widget SDK is publicly available on npm. You can install it via npm or yarn:

npm install @tecsafe/widget-sdk

Initializing the SDK

First, initialize the TecsafeWidgetManager. It requires three arguments:

  1. customerTokenCallback
  2. addToCartCallback
  3. widgetManagerConfig

It is important to instantiate the manager only once, as otherwise the SDK could fetch multiple salechannel customer tokens or cause duplicate event bindings.

import { TecsafeWidgetManager, WidgetManagerConfig } from '@tecsafe/widget-sdk'

const manager = new TecsafeWidgetManager(
  // 1. Customer Token Callback
  async (oldToken?: string) => {
    const response = await fetch('https://mybackend.com/tecsafe/saleschannel-customer-token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json'},
      body: JSON.stringify({ oldToken }),
    })
    const json = await response.json()
    return json.token
  },
  // 2. Add To Cart Handler
  {
    single: async (articleNumber, quantity, configurationId) => {
      // Your custom logic to add an item to the cart
      return true // Return boolean to indicate success
    },
  },
  // 3. Configuration
  new WidgetManagerConfig({
    trackingAllowed: true,
    languageRFC4647: 'en-US',
    currencyCodeISO4217: 'USD',
    taxIncluded: true,
  })
)

Creating a Widget

Use the manager instance to create widgets and attach them to DOM elements. This will inject TECSAFE iframes with the respectively requested applications. All available widgets alongside their purpose can be found in the documentation under the widget section: @tecsafe/widget-sdk > Widget

const pdWidget = manager.createProductDetailWidget(
  document.getElementById('product-detail-widget'),
  'the-article-number-of-the-product'
)

Updating the sales channel customer token

We are assuming two types of users within a sales channel:

  1. guest
  2. registered / logged in customer

If a customer's login status changes you have to notify TECSAFE about that change. The customer token API will ensure that guest assets will be transferred to registered accounts on login and that registered customer sessions will be cleaned appropriately for a new guest session. You can update the token by calling the refreshToken method. This will call the customerTokenCallback from the constructor. Alternatively, you can also pass the token directly

manager.refreshToken()

// Alternatively, you can also pass the token directly if you happen to have requested it already in the process.
manager.refreshToken('<new-token>')

Unmounting & Cleanup

If a user withdraws consent, or navigates away from the page (relevant for SPAs), you should destroy all widgets and clean up event listeners by calling the destroyAll method.

manager.destroyAll()

Widget Communication & Events

Widget communication architecture

All communication between a widget and the hosting website functions via event-messaging. The Widget SDK does the heavy lifting of the entire event setup and exposes all relevant events for subscription and publishing accordingly.

Events from hosting Website to the SDK are labelled as OutMessages, whereas events from the SDK to the hosting website are labelled as InMessages. Anything in the documentation that is prefixed with Internal refers to the internal event system plumbing and should not be relevant for integrating TECSAFE functionality.

Event Subscription

All exposed events should be subscribed to and implemented to guarantee the intended user experience and data integrity. Please ensure to implement all @tecsafe/widget-sdk > InMessages` accordingly.

Architecture and Flows

Understanding the internal flows of the Widget SDK ensures a smooth integration into your project, especially regarding authentication and cart interactions. The following section gives some intrspection and more detailled implementation examples for a better understanding.

Authentication Flow & Token Management

The TECSAFE Widget manager requires a valid customer token to identify users and link configurations to their sessions. It handles both guest and registered customer sessions efficiently:

  • When the SDK requires a token, it delegates to the customerTokenCallback provided upon initialization. This naturally occurs when the session requires a token or when it expires.
  • Your frontend is expected to set up the customerTokenCallback such that it will send token requests to your backend's custom token endpoint (see below). The SDK provides the oldToken in the callback to maintain continuity. When your backend requests a new customerToken from the TECSAFE backend, it is strictly necessary to forward this oldToken as part of the request. The TECSAFE backend will then automatically merge the sessions. This guarantees that any products configured during a guest session are seamlessly transferred and assigned to a freshly logged in user or jump across states without data loss.
  • Your backend is expected to provide a custom token Endpoint. It should validate the user (or guest session) via your own authentication logic (e.g., checking standard cookies/headers). Once authenticated the backend should aggregate additional customer data (userID, email, search tags) and tunnel there entire request through to TECSAFE's backend using /jwt/saleschannel-customer.
  • Please note:
    • that your provided userID is the unique customer identifier per sales channel. Anything else can be changed on each request to update the customer on the TECSAFE platform.
    • that search tags are your flexible configuration option to cluster customers. These tags will be available in the cockpit application to filter customers and also manage access right and therefore should be carefully aligned with the responsible specialist department.

Authentication Flow

Subscribing to Widget Events (e.g., RequestArticleInfo)

While the SDK handles internal events (like adding to the cart using your callback), you will often need to listen to and respond to specific widget events manually. Even though every event is optional, it is highly recommended to implement as many as possible to significantly enhance the user experience.

A prime example is providing article details when the widget requests them. Advanced features like up-selling and proactive price previews strictly need the article info event to function correctly. Without implementing this event, the widget will only be able to show a "Price will be calculated in the cart" placeholder.

  • When a widget needs to render product details or prices, it emits an InMessageRequestArticleInfo event.
  • Your application must subscribe to this event using the SDK's .on() methodology.
  • Once you retrieve the corresponding info from your backend or state, you respond directly using the .respond() function attached to the event payload.
  • You MUST respond in the OutMessageArticleInfo format, providing either the details (like name and price) or null if the article is missing (so the widget can quickly show fallback UI rather than timing out).

Article Info Flow

Single Page Application lifecycle

When navigating within a Single Page Application (SPA), it is crucial to handle the widget lifecycle properly to avoid duplicate instances or memory leaks. When a page transition occurs (e.g., leaving a product detail page), you should:

  1. Call .destroy() on the specific widget (e.g., if rendering a different product view), OR
  2. Call .destroyAll() on the manager if no widgets are needed on the next view. This safely cleans up and resets the internal token refresh timeout.

Manager Reset and Reuse: If you used destroyAll(), the current TecsafeWidgetManager cleans up efficiently. While you can recycle and reuse the manager for .createProductDetailWidget() loops again later on, it is generally easier to instantiate a new TecsafeWidgetManager upon mounting the integration point again. However, if your Vue/React wrapper remains globally alive across routes, reusing the existing .destroyAll()-cleaned manager is perfectly fine and maintains the browser session. The .destroyAll() should not be needed for a cleanly implemented SPA which always cleans up behind itself, using for example onBeforeUnmount or useEffect to destroy the widget, like in the Vue.js example below.

Widget Lifecycle

Complete example implementation

Pure HTML

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Tecsafe Widget SDK</title>
  </head>
  <body>
    <div id="product-detail-widget"></div>
    <script src="https://unpkg.com/@tecsafe/widget-sdk@latest/dist/index.js"></script>
    <script>
      const manager = new TecsafeWidgetManager(
        async (oldToken) => {
          const response = await fetch('https://mybackend.com/tecsafe/token', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ oldToken }),
          })
          const json = await response.json()
          return json.token
        },
        {
          single: async (articleNumber, quantity, configurationId) => {
            await fetch('https://mybackend.com/tecsafe/add-to-cart', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ articleNumber, quantity, configurationId })
            });

            if (!response.ok) {
              throw new Error(`HTTP error! status: ${response.status}`);
            }

            return true;
          },
        },
        new WidgetManagerConfig({
          trackingAllowed: true, //assuming that cookies have been accepted by user
          languageRFC4647: 'en-US',
          currencyCodeISO4217: 'USD',
          taxIncluded: true,
        })
      )

      manager.on(InMessageRequestArticleInfo, (e) => {
        const response = await fetch('https://mybackend.com/tecsafe/articleInfo/' + { e.event.articleNumber })
        const articleInfo = await response.json()

        e.respond(OutMessageArticleInfo.create({
          articleNumber: e.event.articleNumber,
          info: {
            ean: articleInfo.ean,
            name: articleInfo.name,
            price: articleInfo.price,
            stock: articleInfo.stock,
          },
        }))
      })

      manager.createProductDetailWidget(
        document.getElementById('product-detail-widget'),
        'SKU-123456'
      )
    </script>
  </body>
</html>

Vue.js

<template>
  <shop-base>
    <product-preview />
    <div ref="container" />
    <product-detail />
    <product-comments />
  </shop-base>
</template>

<script lang="ts" setup>
  import { onMounted, onBeforeUnmount, ref } from 'vue'
  import { TecsafeWidgetManager } from '@tecsafe/widget-sdk'

  const container = ref<HTMLElement | null>(null)
  const manager = ref<TecsafeWidgetManager | null>(null)

  onMounted(() => {
    if (!container.value) throw new Error('Container not found')

    manager.value = new TecsafeWidgetManager(
      async (oldToken) => {
          const response = await fetch('https://mybackend.com/tecsafe/token', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ oldToken }),
          })
          const json = await response.json()
          return json.token
        },
      {
        single: async (articleNumber, quantity, configurationId) => {
          await fetch('https://mybackend.com/tecsafe/add-to-cart', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ articleNumber, quantity, configurationId })
          });

          if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
          }

          return true;
        },
      },
      new WidgetManagerConfig({
        trackingAllowed: true,
        languageRFC4647: 'en-US',
        currencyCodeISO4217: 'USD',
        taxIncluded: true,
      })
    )

    manager.on(InMessageRequestArticleInfo, (e) => {
      const response = await fetch('https://mybackend.com/tecsafe/articleInfo/' + { e.event.articleNumber })
      const articleInfo = await response.json()

      e.respond(OutMessageArticleInfo.create({
        articleNumber: e.event.articleNumber,
        info: {
          ean: articleInfo.ean,
          name: articleInfo.name,
          price: articleInfo.price,
          stock: articleInfo.stock,
        },
      }))
    })

    manager.createProductDetailWidget(
      document.getElementById('product-detail-widget'),
      'SKU-123456'
    )

  })

  onBeforeUnmount(() => {
    if (!manager.value) return
    manager.value.destroyAll()
  })
</script>

About

The TECSAFE Widget SDK for automated IFrame and JWT management

Resources

License

Stars

Watchers

Forks

Contributors