diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dcede41..04d2fb6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,10 @@ and this project adheres to ## Changed - 🧑‍💻(frontend) change literal section open source #702 +- ♻️(frontend) replace cors proxy for export #695 ## Fixed + - 🐛(frontend) remove scroll listener table content #688 - 🔒️(back) restrict access to favorite_list endpoint #690 - 🐛(backend) refactor to fix filtering on children diff --git a/src/backend/demo/data/template/code.txt b/src/backend/demo/data/template/code.txt index 56f6736b..229af08d 100644 --- a/src/backend/demo/data/template/code.txt +++ b/src/backend/demo/data/template/code.txt @@ -1,2 +1,2 @@ - +
\ No newline at end of file diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts index 42f3ed90..70acd37e 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts @@ -136,6 +136,11 @@ test.describe('Doc Export', () => { test('it exports the docs with images', async ({ page, browserName }) => { const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1); + const responseCorsPromise = page.waitForResponse( + (response) => + response.url().includes('/cors-proxy/') && response.status() === 200, + ); + const fileChooserPromise = page.waitForEvent('filechooser'); const downloadPromise = page.waitForEvent('download', (download) => { return download.suggestedFilename().includes(`${randomDoc}.pdf`); @@ -160,6 +165,14 @@ test.describe('Doc Export', () => { await expect(image).toBeVisible(); + await page.locator('.bn-block-outer').last().fill('/'); + await page.getByText('Resizable image with caption').click(); + await page.getByRole('tab', { name: 'Embed' }).click(); + await page + .getByRole('textbox', { name: 'Enter URL' }) + .fill('https://docs.numerique.gouv.fr/assets/logo-gouv.png'); + await page.getByText('Embed image').click(); + await page .getByRole('button', { name: 'download', @@ -188,6 +201,8 @@ test.describe('Doc Export', () => { }) .click(); + const responseCors = await responseCorsPromise; + expect(responseCors.ok()).toBe(true); const download = await downloadPromise; expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`); diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/FileDownloadButton.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/FileDownloadButton.tsx index c34eb244..0cb00c8f 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/FileDownloadButton.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/FileDownloadButton.tsx @@ -57,7 +57,7 @@ export const FileDownloadButton = ({ * If not hosted on our domain, means not a file uploaded by the user, * we do what Blocknote was doing initially. */ - if (!url.includes(window.location.hostname)) { + if (!url.includes(window.location.hostname) && !url.includes('base64')) { if (!editor.resolveFileUrl) { window.open(url); } else { @@ -70,11 +70,11 @@ export const FileDownloadButton = ({ } if (!url.includes('-unsafe')) { - const blob = (await exportResolveFileUrl(url, undefined)) as Blob; + const blob = (await exportResolveFileUrl(url)) as Blob; downloadFile(blob, url.split('/').pop() || 'file'); } else { const onConfirm = async () => { - const blob = (await exportResolveFileUrl(url, undefined)) as Blob; + const blob = (await exportResolveFileUrl(url)) as Blob; downloadFile(blob, url.split('/').pop() || 'file (unsafe)'); }; diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/api/exportResolveFileUrl.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/api/exportResolveFileUrl.tsx new file mode 100644 index 00000000..531291d1 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-export/api/exportResolveFileUrl.tsx @@ -0,0 +1,30 @@ +import { baseApiUrl } from '@/api'; +import { Doc } from '@/features/docs/doc-management'; + +export const exportCorsResolveFileUrl = async ( + docId: Doc['id'], + url: string, +) => { + let resolvedUrl = url; + // If the url is not from the same origin, better to proxy the request + // to avoid CORS issues + if (!url.includes(window.location.hostname) && !url.includes('base64')) { + resolvedUrl = `${baseApiUrl()}documents/${docId}/cors-proxy/?url=${encodeURIComponent(url)}`; + } + + return exportResolveFileUrl(resolvedUrl); +}; + +export const exportResolveFileUrl = async (url: string) => { + try { + const response = await fetch(url, { + credentials: 'include', + }); + + return response.blob(); + } catch { + console.error(`Failed to fetch image: ${url}`); + } + + return url; +}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/api/index.ts b/src/frontend/apps/impress/src/features/docs/doc-export/api/index.ts new file mode 100644 index 00000000..59fc0c17 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-export/api/index.ts @@ -0,0 +1 @@ +export * from './exportResolveFileUrl'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx index a2ba6f8a..d35205dd 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-export/components/ModalExport.tsx @@ -18,10 +18,11 @@ import { Box, Text } from '@/components'; import { useEditorStore } from '@/features/docs/doc-editor'; import { Doc, useTrans } from '@/features/docs/doc-management'; +import { exportCorsResolveFileUrl } from '../api/exportResolveFileUrl'; import { TemplatesOrdering, useTemplates } from '../api/useTemplates'; import { docxDocsSchemaMappings } from '../mappingDocx'; import { pdfDocsSchemaMappings } from '../mappingPDF'; -import { downloadFile, exportResolveFileUrl } from '../utils'; +import { downloadFile } from '../utils'; enum DocDownloadFormat { PDF = 'pdf', @@ -88,26 +89,14 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => { let blobExport: Blob; if (format === DocDownloadFormat.PDF) { - const defaultExporter = new PDFExporter( - editor.schema, - pdfDocsSchemaMappings, - ); - const exporter = new PDFExporter(editor.schema, pdfDocsSchemaMappings, { - resolveFileUrl: async (url) => - exportResolveFileUrl(url, defaultExporter.options.resolveFileUrl), + resolveFileUrl: async (url) => exportCorsResolveFileUrl(doc.id, url), }); const pdfDocument = await exporter.toReactPDFDocument(exportDocument); blobExport = await pdf(pdfDocument).toBlob(); } else { - const defaultExporter = new DOCXExporter( - editor.schema, - docxDocsSchemaMappings, - ); - const exporter = new DOCXExporter(editor.schema, docxDocsSchemaMappings, { - resolveFileUrl: async (url) => - exportResolveFileUrl(url, defaultExporter.options.resolveFileUrl), + resolveFileUrl: async (url) => exportCorsResolveFileUrl(doc.id, url), }); blobExport = await exporter.toBlob(exportDocument); diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/index.ts b/src/frontend/apps/impress/src/features/docs/doc-export/index.ts index 590a7f4c..527c58f0 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/index.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-export/index.ts @@ -1,2 +1,3 @@ +export * from './api'; export * from './components'; export * from './utils'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/utils.ts b/src/frontend/apps/impress/src/features/docs/doc-export/utils.ts index 54a15a25..248b7033 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/utils.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-export/utils.ts @@ -17,27 +17,6 @@ export function downloadFile(blob: Blob, filename: string) { window.URL.revokeObjectURL(url); } -export const exportResolveFileUrl = async ( - url: string, - resolveFileUrl: ((url: string) => Promise) | undefined, -) => { - if (!url.includes(window.location.hostname) && resolveFileUrl) { - return resolveFileUrl(url); - } - - try { - const response = await fetch(url, { - credentials: 'include', - }); - - return response.blob(); - } catch { - console.error(`Failed to fetch image: ${url}`); - } - - return url; -}; - export function docxBlockPropsToStyles( props: Partial, colors: typeof COLORS_DEFAULT,