Embedding Widgets
Embedding Widgets
Abschnitt betitelt „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.
Available Custom Elements
Abschnitt betitelt „Available Custom Elements“| Tag | Purpose |
|---|---|
<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.
Loader Snippet
Abschnitt betitelt „Loader Snippet“<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.
Three Embedding Modes
Abschnitt betitelt „Three Embedding Modes“1. Same-origin (recommended)
Abschnitt betitelt „1. Same-origin (recommended)“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>2. Reverse-proxy from external domain
Abschnitt betitelt „2. Reverse-proxy from external domain“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.
3. Cross-origin (limited)
Abschnitt betitelt „3. Cross-origin (limited)“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.prodDomainis not yet wired up. SM_JWT_TOKENcookie cannot be set cross-site withoutSameSite=None; Secure. Plan to switch toAuthorization: Bearerheaders fed fromlocalStoragefor cross-origin embeds.- Customer’s CSP must allow
script-src https://widgets.swissmentor.ch.
Use only for low-risk integrations until the hardening lands.
Hash-Routing
Abschnitt betitelt „Hash-Routing“<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.
Authentication Flow Inside Widgets
Abschnitt betitelt „Authentication Flow Inside Widgets“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.
Triggering the Support-Chat from Outside
Abschnitt betitelt „Triggering the Support-Chat from Outside“<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.
Deployment
Abschnitt betitelt „Deployment“The bundle is built by the standard Linux Docker-Compose pipeline:
yarn nx build sm-widgets --configuration=webcomponentproducesdist/apps/sm-widgets/(incl. Dockerfile + nginx snippet as build assets).docker compose --profile build-linux build web-init-sm-widgetspackages the busybox preparer image.- Helm flag
smWidgetsEnabled=trueenables theinit-sm-widgetsinit-container which copies/app/www/sm-widgets/and/app/conf.d/sm-widgets.confonto the shared web volume.
No standalone CI pipeline — sm-widgets is part of every release like every other web app.
Local Development
Abschnitt betitelt „Local Development“yarn nx serve sm-widgets # starts the dev-server on :4204 with /sm-widgets/ prefixyarn nx serve scs # Astro dev-server on :4087, proxies /sm-widgets and /graphqlThen open http://localhost:4087/. Widget edits hot-reload through the Astro proxy.