fix(dashboard): load keycloak-js 26 as an ES module

keycloak-js dropped the UMD build in 26.0.0. The package now ships only
lib/keycloak.js with "type": "module" and a default export, so the CDN
path dist/keycloak.js resolves to a file that no longer exists, and a
classic script tag could not parse the file that does. The adapter never
loaded, window.__oidcLoginReady was never set, and every app job that
co-deploys the dashboard failed its Playwright run on the resulting
console error.

The loader now uses a dynamic import and takes the constructor from the
module's default export. That also retires the window.Keycloak check,
which had nothing left to check once the global stopped existing.

Module fetches always run in CORS mode, unlike classic script loads. The
internal CDN emitted no Access-Control-Allow-Origin at all, so it could
not have served an ES module to the dashboard's origin. jsdelivr sends
"*", which is why the external flavor would have hidden the gap.

Every init option the template passes carries the same default in 24.0.5
and 26.2.4, and the silent-SSO postMessage contract is unchanged, so the
adapter's behaviour is untouched beyond the packaging.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-08-10 20:54:14 +02:00
1 parent 87a0535189
commit 5153f30309
3 files changed
+12 -16

No files matched your search

@@ -63,14 +63,12 @@ function setItemVisible(el, visible) {
/* ==============================================
3) Dynamically load the Keycloak.js adapter
============================================== */
function loadKeycloakAdapter(src) {
return new Promise((resolve, reject) => {
const s = document.createElement("script");
s.src = src;
s.onload = resolve;
s.onerror = () => reject(new Error("Failed to load keycloak.js from " + src));
document.head.appendChild(s);
});
async function loadKeycloakAdapter(src) {
const mod = await import(src);
if (typeof mod.default !== "function") {
throw new Error("keycloak.js exposed no default export at " + src);
}
return mod.default;
}
/* ==============================================
@@ -137,19 +135,15 @@ async function initAuthUI() {
syncAuthMenuVisibility(false);
// Load keycloak.js
const kcJsUrl = "{{ lookup('asset', application_id, 'keycloak-js', 'dist/keycloak.js') }}";
const kcJsUrl = "{{ lookup('asset', application_id, 'keycloak-js', 'lib/keycloak.js') }}";
let Keycloak;
try {
await loadKeycloakAdapter(kcJsUrl);
Keycloak = await loadKeycloakAdapter(kcJsUrl);
} catch (e) {
console.error("[oidc] Failed to load adapter:", e);
return; // nothing else to do
}
if (typeof window.Keycloak !== "function") {
console.error("[oidc] window.Keycloak is not available after loading:", kcJsUrl);
return;
}
// Initialize the Keycloak instance
try {
keycloak = new Keycloak({ url: KC_CONFIG.url, realm: KC_CONFIG.realm, clientId: KC_CONFIG.clientId });
+2
View File
@@ -11,6 +11,8 @@
- name: "🧩 Deploy the CDN stack (one-shot npm-mirror build) + front proxy"
include_role:
name: sys-stk-full
vars:
aca_origin: "* always"
- name: "🧩 Front proxy only (external CDN flavor)"
include_role:
+1 -1
View File
@@ -202,7 +202,7 @@ class TestCommittedRoleLockfiles(unittest.TestCase):
return lookup.run([app, package, path], variables={"group_names": []})[0]
def test_dashboard_keycloak_js(self):
url = self._url("web-app-dashboard", "keycloak-js", "dist/keycloak.js")
url = self._url("web-app-dashboard", "keycloak-js", "lib/keycloak.js")
self.assertRegex(url, self.URL_RE)
def test_littlejs_bootstrap(self):