diff --git a/web/__tests__/embed.spec.ts b/web/__tests__/embed.spec.ts new file mode 100644 index 00000000000..1abd41a294d --- /dev/null +++ b/web/__tests__/embed.spec.ts @@ -0,0 +1,52 @@ +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { Script } from 'node:vm' +import { waitFor } from '@testing-library/react' + +const embedArtifacts = [ + { + name: 'embed.js', + source: readFileSync(resolve(process.cwd(), 'public/embed.js'), 'utf8'), + }, + { + name: 'embed.min.js', + source: readFileSync(resolve(process.cwd(), 'public/embed.min.js'), 'utf8'), + }, +] + +function executeEmbedScript(source: string) { + Object.defineProperty(window, 'difyChatbotConfig', { + configurable: true, + value: { + token: 'test-token', + baseUrl: 'about:blank', + dynamicScript: true, + }, + }) + + new Script(source).runInThisContext() +} + +describe.each(embedArtifacts)('$name', ({ source }) => { + beforeEach(() => { + document.head.replaceChildren() + document.body.replaceChildren() + }) + + afterEach(() => { + Reflect.deleteProperty(window, 'difyChatbotConfig') + document.head.replaceChildren() + document.body.replaceChildren() + }) + + it('should delegate clipboard-write permission to the chatbot iframe', async () => { + executeEmbedScript(source) + + await waitFor(() => { + const iframe = document.querySelector('#dify-chatbot-bubble-window') + const permissions = iframe?.allow.split(';').map((permission) => permission.trim()) + + expect(permissions).toContain('clipboard-write') + }) + }) +}) diff --git a/web/app/components/app/overview/__tests__/app-card-utils.spec.ts b/web/app/components/app/overview/__tests__/app-card-utils.spec.ts index 115a4f971b6..f33aea01bbc 100644 --- a/web/app/components/app/overview/__tests__/app-card-utils.spec.ts +++ b/web/app/components/app/overview/__tests__/app-card-utils.spec.ts @@ -296,7 +296,7 @@ describe('app-card-utils', () => { const snippet = getEmbeddedIframeSnippet('https://example.com/chatbot/token-1') expect(snippet).toContain('src="https://example.com/chatbot/token-1"') expect(snippet).toContain('frameborder="0"') - expect(snippet).toContain('allow="microphone"') + expect(snippet).toContain('allow="microphone;clipboard-write"') }) it('should generate an embedded script snippet with inputs', () => { diff --git a/web/app/components/app/overview/app-card-utils.ts b/web/app/components/app/overview/app-card-utils.ts index 6d08b49da80..bf170524d38 100644 --- a/web/app/components/app/overview/app-card-utils.ts +++ b/web/app/components/app/overview/app-card-utils.ts @@ -142,7 +142,7 @@ export const getEmbeddedIframeSnippet = (iframeUrl: string) => src="${iframeUrl}" style="width: 100%; height: 100%; min-height: 700px" frameborder="0" - allow="microphone"> + allow="microphone;clipboard-write"> ` const getScriptInputsContent = (values: Record) => { diff --git a/web/public/embed.js b/web/public/embed.js index ca1f180f344..311ccd91848 100644 --- a/web/public/embed.js +++ b/web/public/embed.js @@ -155,7 +155,7 @@ // Function to create the iframe for the chatbot function createIframe() { const iframe = document.createElement('iframe') - iframe.allow = 'fullscreen;microphone' + iframe.allow = 'fullscreen;microphone;clipboard-write' iframe.title = 'dify chatbot bubble window' iframe.id = iframeId iframe.src = iframeUrl diff --git a/web/public/embed.min.js b/web/public/embed.min.js index afbd1d24fed..fa1dde0c2d6 100644 --- a/web/public/embed.min.js +++ b/web/public/embed.min.js @@ -19,7 +19,7 @@ transition-property: width, height; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; - `;async function e(){let u=!1;if(y&&y.token){var e=new URLSearchParams({...await(async()=>{var e=y?.inputs||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n[e]=await r(t)})),n})(),...await(async()=>{var e=y?.systemVariables||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n["sys."+e]=await r(t)})),n})(),...await(async()=>{var e=y?.userVariables||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n["user."+e]=await r(t)})),n})()}),n=y.baseUrl||`https://${y.isDev?"dev.":""}udify.app`,i=(y.routeSegment||"chatbot").replace(/^\/+|\/+$/g,"")||"chatbot";let o=new URL(n).origin,t=(!1===y.sendOnEnter&&e.set("sendOnEnter","false"),`${n}/${i}/${y.token}?`+e);n=s();async function r(e){e=(new TextEncoder).encode(e),e=new Response(new Blob([e]).stream().pipeThrough(new CompressionStream("gzip"))).arrayBuffer(),e=new Uint8Array(await e);return btoa(String.fromCharCode(...e))}function s(){var e=document.createElement("iframe");return e.allow="fullscreen;microphone",e.title="dify chatbot bubble window",e.id=h,e.src=t,e.style.cssText=c,e}function d(){var e,t,n;window.innerWidth<=640||(e=document.getElementById(h),t=document.getElementById(m),e&&t&&(t=t.getBoundingClientRect(),n=window.innerHeight/2,t.top+t.height/2{"className"===e?n.classList.add(...t.split(" ")):"style"===e?"object"==typeof t?Object.assign(n.style,t):n.style.cssText=t:"function"==typeof t?n.addEventListener(e.replace(/^on/,"").toLowerCase(),t):n[e]=t}),n.id=m;var e=document.createElement("style"),e=(document.head.appendChild(e),e.sheet.insertRule(` + `;async function e(){let u=!1;if(y&&y.token){var e=new URLSearchParams({...await(async()=>{var e=y?.inputs||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n[e]=await r(t)})),n})(),...await(async()=>{var e=y?.systemVariables||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n["sys."+e]=await r(t)})),n})(),...await(async()=>{var e=y?.userVariables||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n["user."+e]=await r(t)})),n})()}),n=y.baseUrl||`https://${y.isDev?"dev.":""}udify.app`,i=(y.routeSegment||"chatbot").replace(/^\/+|\/+$/g,"")||"chatbot";let o=new URL(n).origin,t=(!1===y.sendOnEnter&&e.set("sendOnEnter","false"),`${n}/${i}/${y.token}?`+e);n=s();async function r(e){e=(new TextEncoder).encode(e),e=new Response(new Blob([e]).stream().pipeThrough(new CompressionStream("gzip"))).arrayBuffer(),e=new Uint8Array(await e);return btoa(String.fromCharCode(...e))}function s(){var e=document.createElement("iframe");return e.allow="fullscreen;microphone;clipboard-write",e.title="dify chatbot bubble window",e.id=h,e.src=t,e.style.cssText=c,e}function d(){var e,t,n;window.innerWidth<=640||(e=document.getElementById(h),t=document.getElementById(m),e&&t&&(t=t.getBoundingClientRect(),n=window.innerHeight/2,t.top+t.height/2{"className"===e?n.classList.add(...t.split(" ")):"style"===e?"object"==typeof t?Object.assign(n.style,t):n.style.cssText=t:"function"==typeof t?n.addEventListener(e.replace(/^on/,"").toLowerCase(),t):n[e]=t}),n.id=m;var e=document.createElement("style"),e=(document.head.appendChild(e),e.sheet.insertRule(` #${n.id} { position: fixed; bottom: var(--${n.id}-bottom, 1rem);