diff --git a/extensions/chrome/manifest.json b/extensions/chrome/manifest.json index a29e330d..186e463f 100644 --- a/extensions/chrome/manifest.json +++ b/extensions/chrome/manifest.json @@ -23,9 +23,16 @@ "side_panel": { "default_path": "panel.html" }, - "permissions": ["sidePanel"], - "host_permissions": ["https://*.iblai.app/*", "https://*.ibl.ai/*"], + "permissions": ["sidePanel", "identity", "scripting", "tabs"], + "host_permissions": [ + "https://*.iblai.app/*", + "https://*.ibl.ai/*", + "http://localhost:3001/*", + "https://*.ngrok-free.app/*", + "http://*/*", + "https://*/*" + ], "content_security_policy": { - "extension_pages": "script-src 'self'; object-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://*.iblai.app https://*.ibl.ai; frame-src https://*.iblai.app https://*.ibl.ai https://accounts.google.com https://appleid.apple.com; child-src https://*.iblai.app https://*.ibl.ai" + "extension_pages": "script-src 'self'; object-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://*.iblai.app https://*.ibl.ai http://localhost:3001 https://*.ngrok-free.app; frame-src https://*.iblai.app https://*.ibl.ai https://accounts.google.com https://appleid.apple.com http://localhost:3001 https://*.ngrok-free.app; child-src https://*.iblai.app https://*.ibl.ai http://localhost:3001 https://*.ngrok-free.app" } } diff --git a/extensions/chrome/panel.css b/extensions/chrome/panel.css index d78387c8..45796ebd 100644 --- a/extensions/chrome/panel.css +++ b/extensions/chrome/panel.css @@ -3,9 +3,11 @@ body { height: 100%; margin: 0; padding: 0; + overflow: hidden; } -/* The agent fills the full height of the side panel. */ +/* The agent fills the entire side panel — no fixed width or overlay. The + component sizes its own (shadow-DOM) iframe to 100%, so this is all we need. */ agent-ai { display: block; width: 100%; diff --git a/extensions/chrome/panel.html b/extensions/chrome/panel.html index e18b39ab..cc895f9f 100644 --- a/extensions/chrome/panel.html +++ b/extensions/chrome/panel.html @@ -7,28 +7,22 @@ ibl.ai Agent - + + + diff --git a/extensions/chrome/panel.js b/extensions/chrome/panel.js new file mode 100644 index 00000000..eb79edf9 --- /dev/null +++ b/extensions/chrome/panel.js @@ -0,0 +1,142 @@ +// Host-side authentication for the ibl.ai side-panel agent. +// +// reads `axd_token` / `axd_token_expires` from THIS +// page's localStorage and forwards them to the mentor iframe (no in-iframe auth +// redirect — which browsers block in extension side panels via storage +// partitioning). We obtain those tokens here with chrome.identity.launchWebAuthFlow. +// +// Must be an external script: MV3's `script-src 'self'` forbids inline scripts. + +const AUTH_URL = 'https://login.iblai.app'; + +function isAuthed() { + return Boolean(localStorage.getItem('axd_token')); +} + +// Extract ibl's session payload (a JSON `data` param — the same shape the web +// app's /mobile-sso-login consumes) and store its keys in localStorage. +function storeSessionFromRedirect(responseUrl) { + const u = new URL(responseUrl); + const data = + u.searchParams.get('data') || + new URLSearchParams(u.hash.replace(/^#/, '')).get('data'); + if (!data) { + throw new Error(`no "data" param in auth redirect: ${responseUrl}`); + } + const session = JSON.parse(data); + Object.entries(session).forEach(([key, value]) => + localStorage.setItem(key, String(value)), + ); +} + +async function authenticate() { + // Chrome intercepts navigations to this URL and hands the full URL back to us. + const redirectUri = chrome.identity.getRedirectURL(); // https://.chromiumapp.org/ + const authUrl = + `${AUTH_URL}/login` + `?redirect-to=${encodeURIComponent(redirectUri)}`; + + const responseUrl = await chrome.identity.launchWebAuthFlow({ + url: authUrl, + interactive: true, + }); + if (!responseUrl) throw new Error('auth flow returned no redirect URL'); + storeSessionFromRedirect(responseUrl); +} + +(async () => { + if (isAuthed()) return; // reads the stored token + try { + await authenticate(); + // Re-init now that the token is in localStorage. + location.reload(); + } catch (err) { + console.error('[ibl.ai panel] sign-in failed:', err); + } +})(); + +// ---- Page context: feed the ACTIVE TAB's content to the mentor iframe -------- +// The side panel runs in its own document, so can only see panel.html +// (that's why `iscontextaware` is left OFF — it would otherwise flood the mentor +// with the panel's own DOM every second). Instead we read the browsed tab with +// chrome.scripting and post the mentor's own MENTOR:CONTEXT_UPDATE message +// straight into its (open) shadow-DOM iframe. + +// Runs in the target tab's context. +function extractPageContent() { + const text = document.body ? document.body.innerText : ''; + return { + title: document.title, + href: location.href, + text: text.slice(0, 100000), + }; +} + +async function readActiveTab() { + const [tab] = await chrome.tabs.query({ + active: true, + lastFocusedWindow: true, + }); + // Can't script the browser's own pages, the web store, or extension pages. + if ( + !tab?.id || + !tab.url || + /^(chrome|edge|about|chrome-extension|view-source|devtools):/.test(tab.url) + ) { + return null; + } + try { + const [injection] = await chrome.scripting.executeScript({ + target: { tabId: tab.id }, + func: extractPageContent, + }); + return injection?.result ?? null; + } catch (err) { + console.warn('[ibl.ai panel] cannot read tab content:', err); + return null; + } +} + +function mentorIframe() { + return document + .querySelector('agent-ai') + ?.shadowRoot?.querySelector('#ibl-chat-widget-container iframe'); +} + +let latestContext = null; + +function pushContext() { + const iframe = mentorIframe(); + if (!iframe?.contentWindow || !latestContext) return; + iframe.contentWindow.postMessage( + { + type: 'MENTOR:CONTEXT_UPDATE', + hostInfo: { title: latestContext.title, href: latestContext.href }, + pageContent: latestContext.text, + }, + '*', + ); +} + +async function refreshContext() { + const content = await readActiveTab(); + if (content) { + latestContext = content; + pushContext(); + } +} + +// Refresh as the user switches tabs or a page finishes loading, plus a periodic +// re-read to catch in-page (SPA) navigations — mirrors the SDK's own cadence. +chrome.tabs.onActivated.addListener(() => refreshContext()); +chrome.tabs.onUpdated.addListener((_id, info, tab) => { + if (info.status === 'complete' && tab.active) refreshContext(); +}); +setInterval(refreshContext, 5000); + +// The mentor iframe posts a `loaded` signal to this window when ready; (re)push +// the current context immediately so it lands as soon as the chat is up. +window.addEventListener('message', (event) => { + if (event.data?.loaded || event.data?.type === 'MENTOR:READY') pushContext(); +}); + +refreshContext(); diff --git a/extensions/chrome/vendor/agent-ai.umd.js b/extensions/chrome/vendor/agent-ai.umd.js index 77963198..95244ffc 100644 --- a/extensions/chrome/vendor/agent-ai.umd.js +++ b/extensions/chrome/vendor/agent-ai.umd.js @@ -84,24 +84,25 @@ t && (this.iblData = t), this.attachShadow({ mode: 'open' }); this.shadowRoot && (this.shadowRoot.innerHTML = - '\n \n
\n
\n
\n
\n
\n \n \n \n
\n

Screen Sharing Active

\n
\n \n Your screen is being shared\n
\n

The mentor can now see your screen in the popup window.

\n \n
\n
\n
\n \n Mentor audio on\n
\n
\n \n \n \n \n
\n
\n
\n
\n \n Mic on\n
\n
\n \n \n \n \n
\n
\n
\n
\n \n
\n '); + '\n \n
\n
\n
\n
\n
\n \n \n \n
\n

Screen Sharing Active

\n
\n \n Your screen is being shared\n
\n

The mentor can now see your screen in the popup window.

\n \n
\n
\n
\n \n Mentor audio on\n
\n
\n \n \n \n \n
\n
\n
\n
\n \n Mic on\n
\n
\n \n \n \n \n
\n
\n
\n
\n \n
\n '); } async onPostMessage(t) { - var e, n; - let o = t.data; - if ('string' == typeof o) + var e, n, o; + console.log('################# message event ', t.data); + let d = t.data; + if ('string' == typeof d) try { - o = JSON.parse(o); + d = JSON.parse(d); } catch (t) { return; } - if ('context' === (null == o ? void 0 : o.type)) { + if ('context' === (null == d ? void 0 : d.type)) { const e = t.origin; - this.contextOrigins.includes(e) && (this.iframeContexts[e] = o.data); + this.contextOrigins.includes(e) && (this.iframeContexts[e] = d.data); } if ( - 'MENTOR:CHAT_ACTION_VOICECALL' === (null == o ? void 0 : o.type) || - 'MENTOR:CHAT_ACTION_SCREENSHARE' === (null == o ? void 0 : o.type) + 'MENTOR:CHAT_ACTION_VOICECALL' === (null == d ? void 0 : d.type) || + 'MENTOR:CHAT_ACTION_SCREENSHARE' === (null == d ? void 0 : d.type) ) { const t = null === (e = this.shadowRoot) || void 0 === e @@ -109,10 +110,10 @@ : e.querySelector('iframe'); if (t && t.src) { let e = ''; - 'MENTOR:CHAT_ACTION_VOICECALL' === (null == o ? void 0 : o.type) + 'MENTOR:CHAT_ACTION_VOICECALL' === (null == d ? void 0 : d.type) ? (e = 'voice-call') : 'MENTOR:CHAT_ACTION_SCREENSHARE' === - (null == o ? void 0 : o.type) && (e = 'screen-share'); + (null == d ? void 0 : d.type) && (e = 'screen-share'); let n = this.iblData; if (!n && this.userObject) { const t = {}; @@ -120,67 +121,67 @@ 'tenants' !== e && (t[e] = this.userObject[e]); n = JSON.stringify(t); } - const s = `${t.src}&ibl-data=${n}&chat-action=${e}&session-id=${null == o ? void 0 : o.sessionId}`; + const o = `${t.src}&ibl-data=${n}&chat-action=${e}&session-id=${null == d ? void 0 : d.sessionId}`; if (this.isInIframe()) 'MENTOR:CHAT_ACTION_SCREENSHARE' === - (null == o ? void 0 : o.type) && + (null == d ? void 0 : d.type) && (this.sentOpenNewWindowForScreenShare = !0), window.parent.postMessage( - { type: 'ACTION:OPEN_NEW_WINDOW', payload: { url: s } }, + { type: 'ACTION:OPEN_NEW_WINDOW', payload: { url: o } }, '*', ); else { 'MENTOR:CHAT_ACTION_SCREENSHARE' === - (null == o ? void 0 : o.type) && + (null == d ? void 0 : d.type) && (this.sentOpenNewWindowForScreenShare = !0); const t = 375, e = 667, n = (window.screen.width - t) / 2, - i = (window.screen.height - e) / 2, - r = `MentorAI_${Date.now()}`, - d = window.open( - s, - r, - `width=${t},height=${e},left=${n},top=${i},toolbar=no,location=no,directories=no,status=no,menubar=no,resizable=yes,scrollbars=yes`, + s = (window.screen.height - e) / 2, + i = `MentorAI_${Date.now()}`, + r = window.open( + o, + i, + `width=${t},height=${e},left=${n},top=${s},toolbar=no,location=no,directories=no,status=no,menubar=no,resizable=yes,scrollbars=yes`, ); - d && - (localStorage.setItem(a, r), d.focus(), (this.popupWindow = d)); + r && + (localStorage.setItem(a, i), r.focus(), (this.popupWindow = r)); } } } if ( - ((null == o ? void 0 : o.closeEmbed) && - window.parent.postMessage(JSON.stringify(o), '*'), - null == o ? void 0 : o.height) + ((null == d ? void 0 : d.closeEmbed) && + window.parent.postMessage(JSON.stringify(d), '*'), + null == d ? void 0 : d.height) ) { const t = null === (n = this.shadowRoot) || void 0 === n ? void 0 : n.querySelector('#ibl-chat-widget-container'); - t && (t.style.height = `${o.height}px`); + t && (t.style.height = `${d.height}px`); } if ( - ('MENTOR:SCREENSHARING_STARTED' === (null == o ? void 0 : o.type) && + ('MENTOR:SCREENSHARING_STARTED' === (null == d ? void 0 : d.type) && (this.sentOpenNewWindowForScreenShare || 'true' === localStorage.getItem(r)) && (localStorage.setItem(r, 'true'), this.showScreenSharingOverlay()), - 'MENTOR:SCREENSHARING_STOPPED' === (null == o ? void 0 : o.type) && + 'MENTOR:SCREENSHARING_STOPPED' === (null == d ? void 0 : d.type) && this.sentOpenNewWindowForScreenShare && this.stopScreenSharing(), - 'MENTOR:SCREENSHARING_SPEAKING' === (null == o ? void 0 : o.type) && - this.updateMicSpeakingState(o.speaking), - 'MENTOR:SCREENSHARING_MUTED' === (null == o ? void 0 : o.type) && - this.updateMicMutedState(o.muted), + 'MENTOR:SCREENSHARING_SPEAKING' === (null == d ? void 0 : d.type) && + this.updateMicSpeakingState(d.speaking), + 'MENTOR:SCREENSHARING_MUTED' === (null == d ? void 0 : d.type) && + this.updateMicMutedState(d.muted), 'MENTOR:SCREENSHARING_MENTOR_SPEAKING' === - (null == o ? void 0 : o.type) && - this.updateMentorSpeakingState(o.speaking), - 'MENTOR:SCREENSHARING_MENTOR_MUTED' === (null == o ? void 0 : o.type) && - this.updateMentorMutedState(o.muted), - 'MENTOR:FOCUS_PARENT' === (null == o ? void 0 : o.type) && + (null == d ? void 0 : d.type) && + this.updateMentorSpeakingState(d.speaking), + 'MENTOR:SCREENSHARING_MENTOR_MUTED' === (null == d ? void 0 : d.type) && + this.updateMentorMutedState(d.muted), + 'MENTOR:FOCUS_PARENT' === (null == d ? void 0 : d.type) && window.focus(), !this.isAnonymous) ) { - if (null == o ? void 0 : o.authExpired) + if (null == d ? void 0 : d.authExpired) try { const t = this.getEdxJwtToken(), e = await s(this.lmsUrl, t), @@ -200,65 +201,77 @@ this.sendAuthDataToIframe(this.userObject); } } catch (t) { - console.error('Error fetching user tenants or tokens:', t), + console.log('ibl data', this.iblData), + console.error('Error fetching user tenants or tokens:', t), this.authRelyOnHost - ? this.showRefreshInstruction() - : this.redirectToAuthSPA(); + ? localStorage.getItem('axd_token') && + localStorage.getItem('dm_token') && + localStorage.getItem('edx_jwt_token') + ? this.sendAuthDataToIframe(JSON.stringify(localStorage)) + : this.showRefreshInstruction() + : this.iblData + ? this.sendAuthDataToIframe(this.iblData) + : this.redirectToAuthSPA(); } - if ((null == o ? void 0 : o.loaded) && o.auth.userData) - try { - if ( - this.edxUserId && - this.edxUserId != JSON.parse(o.auth.userData).user_id.toString() - ) - if (this.iblData) this.sendAuthDataToIframe(this.iblData); - else - try { - const t = this.getEdxJwtToken(), - e = await s(this.lmsUrl, t), - n = e.find((t) => t.key === this.tenant); - if (n) { - const o = await i(this.lmsUrl, n.key, t); - (this.userObject = { - axd_token: o.axd_token.token, - axd_token_expires: o.axd_token.expires, - userData: JSON.stringify(o.user), - dm_token_expires: o.dm_token.expires, - edx_jwt_token: t, - tenant: n.key, - tenants: JSON.stringify(e), - dm_token: o.dm_token.token, - }), - this.sendAuthDataToIframe(this.userObject); + if (null == d ? void 0 : d.loaded) + if (null === (o = d.auth) || void 0 === o ? void 0 : o.userData) + try { + if ( + this.edxUserId && + this.edxUserId != JSON.parse(d.auth.userData).user_id.toString() + ) + if (this.iblData) this.sendAuthDataToIframe(this.iblData); + else + try { + const t = this.getEdxJwtToken(), + e = await s(this.lmsUrl, t), + n = e.find((t) => t.key === this.tenant); + if (n) { + const o = await i(this.lmsUrl, n.key, t); + (this.userObject = { + axd_token: o.axd_token.token, + axd_token_expires: o.axd_token.expires, + userData: JSON.stringify(o.user), + dm_token_expires: o.dm_token.expires, + edx_jwt_token: t, + tenant: n.key, + tenants: JSON.stringify(e), + dm_token: o.dm_token.token, + }), + this.sendAuthDataToIframe(this.userObject); + } + } catch (t) { + this.authRelyOnHost + ? this.showRefreshInstruction() + : this.redirectToAuthSPA(); } - } catch (t) { - this.authRelyOnHost - ? this.showRefreshInstruction() - : this.redirectToAuthSPA(); - } - else - this.userObject = { - axd_token: o.auth.axd_token, - axd_token_expires: o.auth.axd_token_expires, - userData: o.auth.userData, - dm_token_expires: o.auth.dm_token_expires, - edx_jwt_token: o.auth.edx_jwt_token, - tenant: o.auth.tenant, - tenants: o.auth.tenants, - dm_token: o.auth.dm_token, - }; - } catch (t) { - console.error('Error parsing userData from auth:', t); - } + else + this.userObject = { + axd_token: d.auth.axd_token, + axd_token_expires: d.auth.axd_token_expires, + userData: d.auth.userData, + dm_token_expires: d.auth.dm_token_expires, + edx_jwt_token: d.auth.edx_jwt_token, + tenant: d.auth.tenant, + tenants: d.auth.tenants, + dm_token: d.auth.dm_token, + }; + } catch (t) { + console.error('Error parsing userData from auth:', t); + } + else + console.log('ibl data', this.iblData), + console.log('local storage', localStorage), + this.iblData && this.sendAuthDataToIframe(this.iblData); } - (null == o ? void 0 : o.ready) && + (null == d ? void 0 : d.ready) && ((this.isEmbeddedMentorReady = !0), this.iblData ? this.sendAuthDataToIframe(this.iblData) : this.authRelyOnHost || this.isAnonymous || this.redirectToAuthSPA()), - (null == o ? void 0 : o.loaded) && + (null == d ? void 0 : d.loaded) && ((this.isEmbeddedMentorReady = !0), this.isContextAware && this.sendHostInfoToIframe(), this.theme && this.switchTheme(this.theme), @@ -279,14 +292,38 @@ data: { edxCourseId: this.edxCourseId }, })); } - connectedCallback() { - var t, e, n, o; + async connectedCallback() { + var t, e, n, o, i; if (this.contextSettings) return void this.renderContextSettingsView(); - if (this.iblData) { + if ( + (window.addEventListener('message', (t) => this.onPostMessage(t)), + this.iblData) + ) { const t = new URL(window.location.href); t.searchParams.delete('ibl-data'), window.history.replaceState({}, document.title, t); } + if ( + !this.iblData && + this.authRelyOnHost && + 'undefined' != typeof localStorage && + !localStorage.getItem('tenants') && + localStorage.getItem('axd_token') && + localStorage.getItem('dm_token') && + localStorage.getItem('edx_jwt_token') && + localStorage.getItem('tenant') + ) + try { + const e = await s( + this.lmsUrl, + null !== (t = localStorage.getItem('edx_jwt_token')) && void 0 !== t + ? t + : void 0, + ); + localStorage.setItem('tenants', JSON.stringify(e)); + } catch (t) { + console.error('Error fetching user tenants from host token:', t); + } if ( (!this.iblData && this.authRelyOnHost && @@ -309,13 +346,15 @@ const t = JSON.parse(this.iblData).userData; document.cookie = `userData=${t}; domain=${document.domain}; path=/;`; } - window.addEventListener('message', (t) => this.onPostMessage(t)); - const s = - null === (t = this.shadowRoot) || void 0 === t + this.isEmbeddedMentorReady && + this.iblData && + this.sendAuthDataToIframe(this.iblData); + const a = + null === (e = this.shadowRoot) || void 0 === e ? void 0 - : t.querySelector('iframe'); - s && - ((s.onloadstart = () => { + : e.querySelector('iframe'); + a && + ((a.onloadstart = () => { var t; const e = null === (t = this.shadowRoot) || void 0 === t @@ -323,7 +362,7 @@ : t.querySelector('#loading-spinner'); e && (e.style.display = 'block'); }), - (s.onload = () => { + (a.onload = () => { var t; const e = null === (t = this.shadowRoot) || void 0 === t @@ -331,28 +370,28 @@ : t.querySelector('#loading-spinner'); e && (e.style.display = 'none'); })); - const i = - null === (e = this.shadowRoot) || void 0 === e + const d = + null === (n = this.shadowRoot) || void 0 === n ? void 0 - : e.querySelector('#stop-screensharing-btn'); - i && - i.addEventListener('click', () => { + : n.querySelector('#stop-screensharing-btn'); + d && + d.addEventListener('click', () => { this.stopScreenSharing(); }); - const a = - null === (n = this.shadowRoot) || void 0 === n + const l = + null === (o = this.shadowRoot) || void 0 === o ? void 0 - : n.querySelector('#mic-audio-btn'); - a && - a.addEventListener('click', () => { + : o.querySelector('#mic-audio-btn'); + l && + l.addEventListener('click', () => { this.toggleMute(); }); - const d = - null === (o = this.shadowRoot) || void 0 === o + const c = + null === (i = this.shadowRoot) || void 0 === i ? void 0 - : o.querySelector('#mentor-audio-btn'); - d && - d.addEventListener('click', () => { + : i.querySelector('#mentor-audio-btn'); + c && + c.addEventListener('click', () => { this.toggleMentorMute(); }); if (localStorage.getItem(r)) @@ -560,27 +599,32 @@ null === (o = this.shadowRoot) || void 0 === o ? void 0 : o.querySelector('iframe'); - this.shadowRoot && - t && - (t.src = `${this.mentorUrl}/platform/${this.tenant}${((t, e) => { - switch (t) { - case 'analytics-overview': - return `/${e}/analytics`; - case 'analytics-users': - return `/${e}/analytics/users`; - case 'analytics-topics': - return `/${e}/analytics/topics`; - case 'prompt-gallery': - return `/${e}/prompt-gallery`; - case 'explore': - return '/explore'; - default: - return `/${e}`; - } - })( - this.component, - this.mentor, - )}/${this.modal ? this.modal : ''}?embed=true&mode=anonymous&extra-body-classes=iframed-externally${this.isAdvanced ? '&chat=advanced' : ''}${this.modal ? '&modal=' + this.modal : ''}${((s = this.component), s ? (['analytics-overview', 'analytics-users', 'analytics-topics', 'prompt-gallery', 'explore'].includes(null != s ? s : '') ? `&hide_side_nav=true&hide_header=true&component=${s}` : 'recent-messages' === s ? `&hide_header=true&component=${s}` : `&component=${s}`) : '')}${this.extraParams ? '&' + this.extraParams : ''}`); + if (this.shadowRoot && t) { + let e = '/'; + this.tenant && + ((e += `platform/${this.tenant}`), + this.mentor && + (e += `/${((t, e) => { + switch (t) { + case 'analytics-overview': + return `/${e}/analytics`; + case 'analytics-users': + return `/${e}/analytics/users`; + case 'analytics-topics': + return `/${e}/analytics/topics`; + case 'prompt-gallery': + return `/${e}/prompt-gallery`; + case 'explore': + return '/explore'; + default: + return `/${e}`; + } + })( + this.component, + this.mentor, + )}/${this.modal ? this.modal : ''}`)), + (t.src = `${this.mentorUrl}${e}?embed=true&mode=anonymous&extra-body-classes=iframed-externally${this.isAdvanced ? '&chat=advanced' : ''}${this.modal ? '&modal=' + this.modal : ''}${((s = this.component), s ? (['analytics-overview', 'analytics-users', 'analytics-topics', 'prompt-gallery', 'explore'].includes(null != s ? s : '') ? `&hide_side_nav=true&hide_header=true&component=${s}` : 'recent-messages' === s ? `&hide_header=true&component=${s}` : `&component=${s}`) : '')}${this.extraParams ? '&' + this.extraParams : ''}`); + } } this.isContextAware && ((this.lastUrl = window.location.href), @@ -941,7 +985,10 @@ null === (e = this.shadowRoot) || void 0 === e ? void 0 : e.querySelector('#ibl-chat-widget-container iframe'); - n && n.contentWindow && n.contentWindow.postMessage(t, '*'); + n && + n.contentWindow && + (console.log('################ sending ibl data to iframe'), + n.contentWindow.postMessage(t, '*')); } isTokenExpired(t) { const e = new Date(t); @@ -961,7 +1008,7 @@ ); } const n = window.location.pathname + window.location.search; - window.location.href = `${this.authUrl}/login?redirect-path=${n}&tenant=${this.tenant}${t ? '&logout=true' : ''}&redirect-token=${this.redirectToken}`; + window.location.href = `${this.authUrl}/login?redirect-path=${n}${this.tenant ? `&tenant=${this.tenant}` : ''}${t ? '&logout=true' : ''}${this.redirectToken ? `&redirect-token=${this.redirectToken}` : ''}`; } toggleWidget() { const t = document.getElementById('ibl-chat-widget-container'); diff --git a/scripts/check-test-coverage.sh b/scripts/check-test-coverage.sh index a970c460..c83f8804 100755 --- a/scripts/check-test-coverage.sh +++ b/scripts/check-test-coverage.sh @@ -35,9 +35,11 @@ SKIP_COVERAGE_FILES=( "[sessionId]/[tenantKey]/[mentorId]/page.tsx" "[sessionId]/page.tsx" "[sessionId]/share-chat-redirect-content.tsx" - # Third-party vendored bundle and the chrome extension service worker - # (uses chrome.* APIs) are not unit-testable. + # Third-party vendored bundle and the chrome extension scripts (service + # worker + side-panel host: chrome.* APIs, shadow-DOM postMessage glue) are + # not unit-testable in the app's vitest/jsdom environment. "extensions/chrome/background.js" + "extensions/chrome/panel.js" "extensions/chrome/vendor/agent-ai.umd.js" # i18n/locale runtime glue (server-only request config, cookie helpers). "i18n/config.ts"