export function runtimeBootstrapScript(basePath = "") { return `(() => { const runtime = { basePath: ${JSON.stringify(basePath)}, hooks: {}, mounts: {}, registerMount(pluginId, hookName, mountId, mountFn) { const key = pluginId + ':' + hookName + ':' + mountId; this.mounts[key] = mountFn; }, async loadScript(url) { if (document.querySelector('script[data-runtime-src="' + url + '"]')) return; await new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = url; script.async = true; script.dataset.runtimeSrc = url; script.onload = () => resolve(); script.onerror = reject; document.head.appendChild(script); }); }, ensureCss(url) { if (!url || document.querySelector('link[data-runtime-css="' + url + '"]')) return; const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = url; link.dataset.runtimeCss = url; document.head.appendChild(link); }, async fetchHooks() { const res = await fetch(this.basePath + '/api/runtime/hooks', { credentials: 'include' }); if (!res.ok) throw new Error('hook fetch failed: ' + res.status); const data = await res.json(); this.hooks = data.hooks || {}; return data; }, toolbarAnchor() { return document.querySelector('[data-mailcow-plugin-hook="mail.compose.toolbar"]') || document.querySelector('.compose-toolbar') || document.querySelector('[role="toolbar"]') || document.body; }, attachmentsAnchor() { return document.querySelector('[data-mailcow-plugin-hook="mail.compose.attachments"]') || document.querySelector('.compose-attachments') || document.querySelector('[data-attachments]') || document.body; }, async mountContribution(contribution) { if (contribution.cssUrl) this.ensureCss(contribution.cssUrl); if (contribution.mountUrl) await this.loadScript(contribution.mountUrl); const key = contribution.pluginId + ':' + contribution.hook + ':' + contribution.id; const mountFn = this.mounts[key]; if (!mountFn) return; const parent = contribution.hook === 'mail.compose.attachments' ? this.attachmentsAnchor() : this.toolbarAnchor(); let target = parent.querySelector('[data-plugin-target="' + key + '"]'); if (!target) { target = document.createElement('div'); target.dataset.pluginTarget = key; target.className = 'mailcow-plugin-target'; parent.appendChild(target); } const context = { basePath: this.basePath, contribution, hooks: this.hooks, apiBase: contribution.apiBase, uiBase: contribution.uiBase, user: window.MAILCOW_USER || null, }; await mountFn(target, context); }, async renderHook(hookName) { const list = this.hooks[hookName] || []; for (const contribution of list) { try { await this.mountContribution(contribution); } catch (error) { console.error('Failed to mount contribution', contribution, error); } } }, async init() { window.MAILCOW_PLUGIN_RUNTIME = this; await this.fetchHooks(); await this.renderHook('mail.compose.toolbar'); await this.renderHook('mail.compose.attachments'); } }; const start = () => runtime.init().catch((err) => console.error('Mailcow plugin runtime bootstrap failed', err)); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', start, { once: true }); } else { start(); } })();`; }