From 8560a5b463e4b8bba329ddf548f795f75880be6d Mon Sep 17 00:00:00 2001 From: Joschua Becker Date: Thu, 23 Jul 2026 10:59:08 +0200 Subject: [PATCH 1/8] fix: register add-to-cart handler before constructor early returns --- src/TecsafeWidgetSDK.ts | 39 +++++++------ test/TecsafeWidgetSDK.spec.ts | 103 ++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 20 deletions(-) diff --git a/src/TecsafeWidgetSDK.ts b/src/TecsafeWidgetSDK.ts index f34f685..f7b89bc 100644 --- a/src/TecsafeWidgetSDK.ts +++ b/src/TecsafeWidgetSDK.ts @@ -59,26 +59,6 @@ export class TecsafeWidgetManager extends EventBus { document.createElement('div'), this ) - // To don't make it to obvious thats a "browserID" - // We shorten it to "bid" - this.browserId = localStorage.getItem('tecsafe-bid') - if (!this.browserId) { - this.browserId = Math.random().toString(36).slice(2) - localStorage.setItem('tecsafe-bid', this.browserId) - } - const params = readUrlParams() - if (!params.browserId) return - if (this.browserId !== params.browserId) { - clearUrlParams() - Logger.getInstance().warn('Browser ID mismatch, clearing URL params') - return - } - try { - this.openFullScreen(params.url) - } catch (e) { - Logger.getInstance().error('Failed to open full screen:', String(e)) - } - this.on(IN_MESSAGES.InMessageAddToCart, async (e) => { const positions = e.event.positions if ('bulk' in this.addToCartCallback) { @@ -107,6 +87,25 @@ export class TecsafeWidgetManager extends EventBus { ) } }) + // To don't make it to obvious thats a "browserID" + // We shorten it to "bid" + this.browserId = localStorage.getItem('tecsafe-bid') + if (!this.browserId) { + this.browserId = Math.random().toString(36).slice(2) + localStorage.setItem('tecsafe-bid', this.browserId) + } + const params = readUrlParams() + if (!params.browserId) return + if (this.browserId !== params.browserId) { + clearUrlParams() + Logger.getInstance().warn('Browser ID mismatch, clearing URL params') + return + } + try { + this.openFullScreen(params.url) + } catch (e) { + Logger.getInstance().error('Failed to open full screen:', String(e)) + } } private browserId: string diff --git a/test/TecsafeWidgetSDK.spec.ts b/test/TecsafeWidgetSDK.spec.ts index a0f64dd..2959d98 100644 --- a/test/TecsafeWidgetSDK.spec.ts +++ b/test/TecsafeWidgetSDK.spec.ts @@ -8,6 +8,7 @@ import { } from '@jest/globals' import { TecsafeWidgetManager } from '../src/TecsafeWidgetSDK' import { WidgetManagerConfig } from '../src/types/WidgetManagerConfig' +import { IN_MESSAGES } from '../src/messages/Messages' describe('TecsafeWidgetManager', () => { let mockTokenCallback: any @@ -209,4 +210,106 @@ describe('TecsafeWidgetManager', () => { manager.emit({ type: 'test', create: () => ({}) } as any, {}) expect(spy).toHaveBeenCalled() }) + + describe('add-to-cart handling', () => { + const positions = [ + { + linePosition: 1, + articleNumber: 'ART-1', + quantity: 2, + configurationId: 'cfg-1', + }, + { linePosition: 2, articleNumber: 'ART-2', quantity: 1 }, + ] + + const flushAsync = () => new Promise((resolve) => setTimeout(resolve, 0)) + + it('registers the add-to-cart listener even without tecsafe url params', () => { + const manager = new TecsafeWidgetManager( + mockTokenCallback, + mockAddToCartCallback, + mockConfig + ) + expect(manager.getMessageListeners()).toContain('add-to-cart') + }) + + it('calls the single handler per position and responds per position', async () => { + const single = jest + .fn< + ( + articleNumber: string, + quantity: number, + configurationId?: string + ) => Promise + >() + .mockResolvedValue(true) + const manager = new TecsafeWidgetManager( + mockTokenCallback, + { single }, + mockConfig + ) + const widget = { sendMessage: jest.fn() } as any + + manager._triggerListeners( + 'add-to-cart', + IN_MESSAGES.InMessageAddToCart.create({ positions }), + widget + ) + await flushAsync() + + expect(single).toHaveBeenCalledTimes(2) + expect(single).toHaveBeenNthCalledWith(1, 'ART-1', 2, 'cfg-1') + expect(single).toHaveBeenNthCalledWith(2, 'ART-2', 1, undefined) + expect(widget.sendMessage).toHaveBeenCalledWith({ + type: 'added-to-cart', + payload: { linePosition: 1, success: true }, + }) + expect(widget.sendMessage).toHaveBeenCalledWith({ + type: 'added-to-cart', + payload: { linePosition: 2, success: true }, + }) + }) + + it('calls the bulk handler once with all positions and responds per result', async () => { + const bulk = jest + .fn< + ( + items: { + linePosition: number + articleNumber: string + quantity: number + configurationId?: string + }[] + ) => Promise<{ linePosition: number; success: boolean }[]> + >() + .mockResolvedValue([ + { linePosition: 1, success: true }, + { linePosition: 2, success: false }, + ]) + const manager = new TecsafeWidgetManager( + mockTokenCallback, + { bulk }, + mockConfig + ) + const widget = { sendMessage: jest.fn() } as any + + manager._triggerListeners( + 'add-to-cart', + IN_MESSAGES.InMessageAddToCart.create({ positions }), + widget + ) + await flushAsync() + + expect(bulk).toHaveBeenCalledTimes(1) + expect(bulk).toHaveBeenCalledWith(positions) + expect(widget.sendMessage).toHaveBeenCalledWith({ + type: 'added-to-cart', + payload: { linePosition: 1, success: true }, + }) + expect(widget.sendMessage).toHaveBeenCalledWith({ + type: 'added-to-cart', + payload: { linePosition: 2, success: false }, + }) + }) + }) }) From a800520551a0c026c874a9339f214f9b639230a6 Mon Sep 17 00:00:00 2001 From: Joschua Becker Date: Thu, 23 Jul 2026 10:59:50 +0200 Subject: [PATCH 2/8] fix: rename AddToCardHandler file to AddToCartHandler --- src/TecsafeWidgetSDK.ts | 2 +- src/types/{AddToCardHandler.ts => AddToCartHandler.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/types/{AddToCardHandler.ts => AddToCartHandler.ts} (100%) diff --git a/src/TecsafeWidgetSDK.ts b/src/TecsafeWidgetSDK.ts index f7b89bc..9c2a7e1 100644 --- a/src/TecsafeWidgetSDK.ts +++ b/src/TecsafeWidgetSDK.ts @@ -15,7 +15,7 @@ import { AddToCartHandler, BulkAddToCartHandler, SingleAddToCartHandler, -} from './types/AddToCardHandler' +} from './types/AddToCartHandler' import { parseCustomerJwt } from './util/ParseCustomerJwt' diff --git a/src/types/AddToCardHandler.ts b/src/types/AddToCartHandler.ts similarity index 100% rename from src/types/AddToCardHandler.ts rename to src/types/AddToCartHandler.ts From 8fadc4f6928fbbcb3b7d8758bfad7d5acfc4b834 Mon Sep 17 00:00:00 2001 From: Joschua Becker Date: Thu, 23 Jul 2026 11:01:25 +0200 Subject: [PATCH 3/8] chore: export public callback types and clean up widget manager --- src/TecsafeWidgetSDK.ts | 22 ++++++---------------- src/index.ts | 3 +++ test/TecsafeWidgetSDK.spec.ts | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/TecsafeWidgetSDK.ts b/src/TecsafeWidgetSDK.ts index 9c2a7e1..dea719a 100644 --- a/src/TecsafeWidgetSDK.ts +++ b/src/TecsafeWidgetSDK.ts @@ -11,11 +11,7 @@ import { CustomerTokenCallback } from './types/CustomerTokenCallback' import { EventBus } from './util/EventBus' import { Logger } from './util/Logger' -import { - AddToCartHandler, - BulkAddToCartHandler, - SingleAddToCartHandler, -} from './types/AddToCartHandler' +import { AddToCartHandler } from './types/AddToCartHandler' import { parseCustomerJwt } from './util/ParseCustomerJwt' @@ -60,19 +56,17 @@ export class TecsafeWidgetManager extends EventBus { this ) this.on(IN_MESSAGES.InMessageAddToCart, async (e) => { + const handler = this.addToCartCallback const positions = e.event.positions - if ('bulk' in this.addToCartCallback) { - const results = await ( - this.addToCartCallback as BulkAddToCartHandler - ).bulk(positions) + if ('bulk' in handler) { + const results = await handler.bulk(positions) results.forEach((result) => { e.respond(OUT_MESSAGES.OutMessageAddedToCart.create(result)) }) - } else if ('single' in this.addToCartCallback) { - const singleCb = this.addToCartCallback as SingleAddToCartHandler + } else { await Promise.all( positions.map(async (pos) => { - const success = await singleCb.single( + const success = await handler.single( pos.articleNumber, pos.quantity, pos.configurationId @@ -150,10 +144,6 @@ export class TecsafeWidgetManager extends EventBus { this.sendToAllWidgets(OUT_MESSAGES.OutMessageFullScreenClosed.create()) } - /** - * Sends a message to all widgets - * @param message The message to send - */ /** * Sends a message to all widgets * @param message The message to send diff --git a/src/index.ts b/src/index.ts index c95dd3a..d500606 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,10 @@ import { TecsafeWidgetManager } from './TecsafeWidgetSDK' export * from './TecsafeWidgetSDK' +export * from './types/AddToCartHandler' export * from './types/BaseWidget' +export * from './types/Context' +export * from './types/CustomerTokenCallback' export * from './types/WidgetManagerConfig' export * from './types/MessageEnvelope' export * from './util/UrlParamRW' diff --git a/test/TecsafeWidgetSDK.spec.ts b/test/TecsafeWidgetSDK.spec.ts index 2959d98..5efec46 100644 --- a/test/TecsafeWidgetSDK.spec.ts +++ b/test/TecsafeWidgetSDK.spec.ts @@ -9,6 +9,12 @@ import { import { TecsafeWidgetManager } from '../src/TecsafeWidgetSDK' import { WidgetManagerConfig } from '../src/types/WidgetManagerConfig' import { IN_MESSAGES } from '../src/messages/Messages' +import type { + SingleAddToCartHandler, + BulkAddToCartHandler, + AddToCartHandler, + CustomerTokenCallback, +} from '../src' describe('TecsafeWidgetManager', () => { let mockTokenCallback: any @@ -311,5 +317,15 @@ describe('TecsafeWidgetManager', () => { payload: { linePosition: 2, success: false }, }) }) + + it('exposes the handler types via the package entrypoint', () => { + const single: SingleAddToCartHandler = { single: async () => true } + const bulk: BulkAddToCartHandler = { bulk: async () => [] } + const either: AddToCartHandler = single + const tokenCb: CustomerTokenCallback = async () => 'token' + expect('single' in either).toBe(true) + expect(typeof bulk.bulk).toBe('function') + expect(typeof tokenCb).toBe('function') + }) }) }) From 1235570b6ebcddad7f2f22ca39e05a49c62f4210 Mon Sep 17 00:00:00 2001 From: Joschua Becker Date: Thu, 23 Jul 2026 11:19:52 +0200 Subject: [PATCH 4/8] fix: use non-null assertion for properties and simplify browserId assignment --- src/TecsafeWidgetSDK.ts | 13 ++++++------- src/types/WidgetManagerConfig.ts | 8 ++++---- src/widget/AppWidget.ts | 7 +++++-- tsconfig.json | 3 ++- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/TecsafeWidgetSDK.ts b/src/TecsafeWidgetSDK.ts index dea719a..f65e511 100644 --- a/src/TecsafeWidgetSDK.ts +++ b/src/TecsafeWidgetSDK.ts @@ -83,9 +83,8 @@ export class TecsafeWidgetManager extends EventBus { }) // To don't make it to obvious thats a "browserID" // We shorten it to "bid" - this.browserId = localStorage.getItem('tecsafe-bid') - if (!this.browserId) { - this.browserId = Math.random().toString(36).slice(2) + this.browserId = localStorage.getItem('tecsafe-bid') ?? Math.random().toString(36).slice(2) + if (!localStorage.getItem('tecsafe-bid')) { localStorage.setItem('tecsafe-bid', this.browserId) } const params = readUrlParams() @@ -102,11 +101,11 @@ export class TecsafeWidgetManager extends EventBus { } } - private browserId: string + private browserId!: string private widgets: BaseWidget[] = [] - private appWidget: AppWidget - private token: string - private tokenTimeout: number + private appWidget!: AppWidget + private token!: string + private tokenTimeout!: number private tokenPromise: Promise | null = null private refreshTimeoutId: number | null = null private fullScreenData: any diff --git a/src/types/WidgetManagerConfig.ts b/src/types/WidgetManagerConfig.ts index e17bd99..6f650e5 100644 --- a/src/types/WidgetManagerConfig.ts +++ b/src/types/WidgetManagerConfig.ts @@ -7,19 +7,19 @@ export class RequiredWidgetManagerConfig { * Will activate customer tracking. Make sure to request tracking consent from your customer and * set accordingly. */ - public trackingAllowed: boolean + public trackingAllowed!: boolean /** * The RFC 4647 language tag representing the user's preferred language, e.g. "en-US" */ - public languageRFC4647: string + public languageRFC4647!: string /** * The ISO 4217 currency code, e.g. "USD" */ - public currencyCodeISO4217: string + public currencyCodeISO4217!: string /** * Whether tax is included in the prices. Required to display correct pricing info - gross/net. */ - public taxIncluded: boolean + public taxIncluded!: boolean } /** diff --git a/src/widget/AppWidget.ts b/src/widget/AppWidget.ts index 3baebb8..9421c8e 100644 --- a/src/widget/AppWidget.ts +++ b/src/widget/AppWidget.ts @@ -15,7 +15,7 @@ export class AppWidget extends BaseWidget implements IAppWidget { * The app widget allows any origin defined in the config, so it does not have a uiPath * @see {@link AppWidget.setUrl} {@link AppWidget.getUrl} */ - protected url: string + protected url!: string /** * Sets the url of the widget @@ -73,7 +73,9 @@ export class AppWidget extends BaseWidget implements IAppWidget { * This ensures if a user refreshes the page, they will be taken back to the same place. */ protected handlePageLoad(): void { - const url = this.iframe.contentWindow.location.href + const iframe = this.iframe + if (!iframe?.contentWindow) return + const url = iframe.contentWindow.location.href const { origin } = new URL(url) if (!this.config.allowedOrigins.includes(origin)) { this.destroy() @@ -90,6 +92,7 @@ export class AppWidget extends BaseWidget implements IAppWidget { * @inheritdoc */ protected postShow(): void { + if (!this.iframe) return if (!this.url) { this.destroy() throw new Error(`[TECSAFE] Widget ${this.el} cannot show without a url`) diff --git a/tsconfig.json b/tsconfig.json index 17ab3e7..003f18b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,8 @@ "target": "ESNext", "module": "ESNext", "moduleResolution": "Node", - "baseUrl": "." + "baseUrl": ".", + "ignoreDeprecations": "6.0" }, "exclude": [ "dist", From 9ef0be06b2308150f5fb2255021f8ca2021a5473 Mon Sep 17 00:00:00 2001 From: Joschua Becker Date: Thu, 23 Jul 2026 11:26:59 +0200 Subject: [PATCH 5/8] fix: update widgetBaseURL and expand allowedOrigins for SDK communication --- src/types/WidgetManagerConfig.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/types/WidgetManagerConfig.ts b/src/types/WidgetManagerConfig.ts index 6f650e5..28ab6aa 100644 --- a/src/types/WidgetManagerConfig.ts +++ b/src/types/WidgetManagerConfig.ts @@ -42,12 +42,20 @@ export class WidgetManagerConfig extends RequiredWidgetManagerConfig { /** * The base URL for the widget UIs */ - public widgetBaseURL: string = 'https://tecsafe.github.io/app-ui/iframe' // TODO: update to live page + public widgetBaseURL: string = 'https://app-ui.tecsafe.de/iframe' /** * A list of allowed origins for the SDK to communicate with */ - public allowedOrigins: string[] = ['https://tecsafe.github.io'] // TODO: update to live page + public allowedOrigins: string[] = [ + 'tecsafe.github.io', + 'tecsafe.de', + 'editor.tecsafe.de', + 'ofcp-editor.stage.tecsafe.de', + 'ofcp-editor.testing.tecsafe.de', + 'ofcp-editor.tecsafe-local.de', + 'example.com', + ] // TODO: update to live page /** * Iframe styles.transition property From deed03ecc6fb6753b6cef5a317f2d4559e543d95 Mon Sep 17 00:00:00 2001 From: Joschua Becker Date: Thu, 23 Jul 2026 11:28:20 +0200 Subject: [PATCH 6/8] fix: add packageManager field to package.json and update pnpm-workspace.yaml --- package.json | 3 ++- pnpm-workspace.yaml | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index adeb263..1106128 100644 --- a/package.json +++ b/package.json @@ -48,5 +48,6 @@ }, "alias": { "jose": false - } + }, + "packageManager": "pnpm@11.16.0+sha512.b767e9a98fc87aec0f42daefcd0e84941c2cdb45d73c4e1e68860fb2bdfdbe032ab592105479e5e05c237f740c8a5ffdbbb52385797ddc2296745a1bbb883e8d" } diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5eafda9..fbb070d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,8 @@ +allowBuilds: + '@swc/core': true + lmdb: true + msgpackr-extract: true + puppeteer: true onlyBuiltDependencies: - '@swc/core' - lmdb From 3411a24475fd01ea654bbc15bbc0d2dd780a53a1 Mon Sep 17 00:00:00 2001 From: Joschua Becker Date: Thu, 23 Jul 2026 11:41:38 +0200 Subject: [PATCH 7/8] fix: add 'app-ui.tecsafe.de' to allowedOrigins for SDK communication --- src/types/WidgetManagerConfig.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/WidgetManagerConfig.ts b/src/types/WidgetManagerConfig.ts index 28ab6aa..58aa3a8 100644 --- a/src/types/WidgetManagerConfig.ts +++ b/src/types/WidgetManagerConfig.ts @@ -48,6 +48,7 @@ export class WidgetManagerConfig extends RequiredWidgetManagerConfig { * A list of allowed origins for the SDK to communicate with */ public allowedOrigins: string[] = [ + 'app-ui.tecsafe.de', 'tecsafe.github.io', 'tecsafe.de', 'editor.tecsafe.de', From 3de104144a3c68f75d18c74021c414623bde085e Mon Sep 17 00:00:00 2001 From: Joschua Becker Date: Thu, 23 Jul 2026 11:43:32 +0200 Subject: [PATCH 8/8] fix: linting --- src/TecsafeWidgetSDK.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/TecsafeWidgetSDK.ts b/src/TecsafeWidgetSDK.ts index f65e511..a3d4c6d 100644 --- a/src/TecsafeWidgetSDK.ts +++ b/src/TecsafeWidgetSDK.ts @@ -83,7 +83,8 @@ export class TecsafeWidgetManager extends EventBus { }) // To don't make it to obvious thats a "browserID" // We shorten it to "bid" - this.browserId = localStorage.getItem('tecsafe-bid') ?? Math.random().toString(36).slice(2) + this.browserId = + localStorage.getItem('tecsafe-bid') ?? Math.random().toString(36).slice(2) if (!localStorage.getItem('tecsafe-bid')) { localStorage.setItem('tecsafe-bid', this.browserId) }