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.
The Widget SDK is publicly available on npm. You can install it via npm or yarn:
npm install @tecsafe/widget-sdkFirst, initialize the
TecsafeWidgetManager. It
requires three arguments:
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,
})
)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'
)We are assuming two types of users within a sales channel:
- guest
- 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>')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()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.
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.
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.
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
customerTokenCallbackprovided upon initialization. This naturally occurs when the session requires a token or when it expires. - Your frontend is expected to set up the
customerTokenCallbacksuch that it will send token requests to your backend's custom token endpoint (see below). The SDK provides theoldTokenin the callback to maintain continuity. When your backend requests a newcustomerTokenfrom the TECSAFE backend, it is strictly necessary to forward thisoldTokenas 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.
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
InMessageRequestArticleInfoevent. - 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
OutMessageArticleInfoformat, providing either the details (like name and price) ornullif the article is missing (so the widget can quickly show fallback UI rather than timing out).
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:
- Call
.destroy()on the specific widget (e.g., if rendering a different product view), OR - 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.
<!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><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>

