Zum Inhalt springen

Embedding Widgets

sm-widgets is a single bundle that registers a family of Angular Web-Components on first DOM appearance. Customer websites embed only the markup — the runtime lazy-loads each component chunk on demand.

The bundle ships under /sm-widgets/ on the SwissMentor web container and is loaded by every Astro customer site (e.g. apps/customer-websites/scs) plus any external website that wires up the loader script.

TagPurpose
<sm-course-enrollment course-guid="...">Multi-step OTP-protected course sign-up form.
<sm-portal initial-section="courses">Logged-in self-service portal: Meine Kurse / Adressen / Rechnungen.
<sm-login>Stand-alone Magic-Link / OTP login form.
<sm-support-chat>Floating help bubble. Slide-in side-panel hosting the AI-Chat. Embed once globally.
<sm-ai-chat>Bare AI-Chat component (without the floating-panel chrome).

Each tag is registered lazily — main.js only loads when the element is actually present in the DOM, individual feature chunks (e.g. Syncfusion-heavy bills view) load on first interaction.

<link rel="stylesheet" href="/sm-widgets/styles.css" />
<script type="module" src="/sm-widgets/polyfills.js"></script>
<script type="module" src="/sm-widgets/main.js"></script>
<!-- Optional: tells the bundle the API is reachable on the same origin. -->
<script>
window.__SM_API_BASE__ = ''; // empty = same origin
window.__SM_EMBED__ = true; // suppress full-page redirects in embed mode
</script>

polyfills.js must load before main.js — Astro customer-sites chain them in SmBundleLoader.astro.

Customer site runs on the same nginx as the SwissMentor web container (typical for SCS-style mandants). All paths share an origin, so cookies, CORS and JWT are non-issues.

location /sm-widgets/ { alias /app/www/sm-widgets/; }
location /graphql { proxy_pass http://api-svc; }
location /auth/ { proxy_pass http://api-svc; }

Then drop the loader snippet plus tags into any HTML page:

<sm-course-enrollment course-guid="ab12-..."></sm-course-enrollment>

Customer keeps their own marketing site on www.kunde.ch but proxies the relevant SwissMentor paths through their existing webserver:

location /sm-widgets/ { proxy_pass https://widgets.swissmentor.ch/sm-widgets/; }
location /graphql { proxy_pass https://api.swissmentor.ch/graphql; }
location /auth/ { proxy_pass https://api.swissmentor.ch/auth/; }

Browser sees everything as same-origin — cookies, Apollo and CSRF behave identically to mode 1. Recommended for any serious external customer.

Embed <script src="https://widgets.swissmentor.ch/sm-widgets/main.js"> directly. Only works for read-only widgets (e.g. course catalogue). Side-effects:

  • The API must allow the customer’s origin via CORS — today the allowlist is hardcoded; until Phase 1 hardening lands, dynamic CORS from CmsCollection.prodDomain is not yet wired up.
  • SM_JWT_TOKEN cookie cannot be set cross-site without SameSite=None; Secure. Plan to switch to Authorization: Bearer headers fed from localStorage for cross-origin embeds.
  • Customer’s CSP must allow script-src https://widgets.swissmentor.ch.

Use only for low-risk integrations until the hardening lands.

<sm-course-enrollment> and <sm-portal> use Angular’s HashLocationStrategy so internal route changes (e.g. #/login, #/otp) don’t fight the host page’s URL.

Set course-overview-url on <sm-course-enrollment> when its back buttons should leave the widget and return to the host website’s course list, for example <sm-course-enrollment course-guid="ab12-..." course-overview-url="/de/akademie/"></sm-course-enrollment>. Without this attribute, navigation stays within the widget.

Customer-side anchor links should therefore use ?courseGuid=... style query parameters for deep-linking instead of #-paths.

When the visitor triggers a login from within <sm-course-enrollment> or <sm-login>, the widget calls POST /auth/otp-message-request, then POST /auth/otp-login. After verification the API sets SM_JWT_TOKEN and SM_USER_INFO cookies on the request origin. Both cookies are read by AuthHeader.astro (vanilla JS) to render the logged-in user dropdown — no widget needed for the header.

The Astro customer-sites set up a Vite dev-time proxy (see astro.config.mjs) that mirrors the production nginx routing in mode 1.

<sm-support-chat> listens for a global CustomEvent. Any other script can open it:

window.dispatchEvent(new CustomEvent('sm:open-support-chat'));

The vanilla-JS AuthHeader.astro uses this exact mechanism so a “Support-Chat” item in the user dropdown opens the floating panel.

The bundle is built by the standard Linux Docker-Compose pipeline:

  • yarn nx build sm-widgets --configuration=webcomponent produces dist/apps/sm-widgets/ (incl. Dockerfile + nginx snippet as build assets).
  • docker compose --profile build-linux build web-init-sm-widgets packages the busybox preparer image.
  • Helm flag smWidgetsEnabled=true enables the init-sm-widgets init-container which copies /app/www/sm-widgets/ and /app/conf.d/sm-widgets.conf onto the shared web volume.

No standalone CI pipeline — sm-widgets is part of every release like every other web app.

Terminal-Fenster
yarn nx serve sm-widgets # starts the dev-server on :4204 with /sm-widgets/ prefix
yarn nx serve scs # Astro dev-server on :4087, proxies /sm-widgets and /graphql

Then open http://localhost:4087/. Widget edits hot-reload through the Astro proxy.