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

@tecsafe/widget-sdk

v0.2.0

Published

Tecsafe Widget SDK for automated IFrame and JWT management

Readme

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>