♻️(frontend) replace cors proxy for export
We were using the cors proxy of Blocknote.js to export the document. Now we use our own proxy to avoid CORS issues.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
<img width="200" src="https://impress-staging.beta.numerique.gouv.fr/assets/logo-gouv.png" />
|
||||
<img width="200" src="http://localhost:3000/assets/logo-gouv.png" />
|
||||
<br/>
|
||||
@@ -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`);
|
||||
|
||||
|
||||
@@ -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)');
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './exportResolveFileUrl';
|
||||
@@ -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);
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './api';
|
||||
export * from './components';
|
||||
export * from './utils';
|
||||
|
||||
@@ -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<string | Blob>) | 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<DefaultProps>,
|
||||
colors: typeof COLORS_DEFAULT,
|
||||
|
||||
Reference in New Issue
Block a user