diff --git a/apps/remix/app/components/general/document/document-certificate-download-button.tsx b/apps/remix/app/components/general/document/document-certificate-download-button.tsx index 7584fe81b..082baa440 100644 --- a/apps/remix/app/components/general/document/document-certificate-download-button.tsx +++ b/apps/remix/app/components/general/document/document-certificate-download-button.tsx @@ -29,39 +29,30 @@ export const DocumentCertificateDownloadButton = ({ const onDownloadCertificatesClick = async () => { try { - const { url } = await downloadCertificate({ documentId }); + const { pdfData, filename } = await downloadCertificate({ documentId }); - const iframe = Object.assign(document.createElement('iframe'), { - src: url, - }); + const byteCharacters = atob(pdfData); + const byteNumbers = new Array(byteCharacters.length); - Object.assign(iframe.style, { - position: 'fixed', - top: '0', - left: '0', - width: '0', - height: '0', - }); + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } - const onLoaded = () => { - if (iframe.contentDocument?.readyState === 'complete') { - iframe.contentWindow?.print(); + const byteArray = new Uint8Array(byteNumbers); + const blob = new Blob([byteArray], { type: 'application/pdf' }); - iframe.contentWindow?.addEventListener('afterprint', () => { - document.body.removeChild(iframe); - }); - } - }; + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); - // When the iframe has loaded, print the iframe and remove it from the dom - iframe.addEventListener('load', onLoaded); + link.href = url; + link.download = filename; - document.body.appendChild(iframe); + document.body.appendChild(link); + link.click(); - onLoaded(); + document.body.removeChild(link); + URL.revokeObjectURL(url); } catch (error) { - console.error(error); - toast({ title: _(msg`Something went wrong`), description: _( diff --git a/packages/trpc/server/document-router/download-document-certificate.ts b/packages/trpc/server/document-router/download-document-certificate.ts index b59eafbf0..7b22e080c 100644 --- a/packages/trpc/server/document-router/download-document-certificate.ts +++ b/packages/trpc/server/document-router/download-document-certificate.ts @@ -1,9 +1,6 @@ -import { DateTime } from 'luxon'; - -import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; -import { AppError } from '@documenso/lib/errors/app-error'; -import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt'; +import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id'; +import { getCertificatePdf } from '@documenso/lib/server-only/htmltopdf/get-certificate-pdf'; import { isDocumentCompleted } from '@documenso/lib/utils/document'; import { authenticatedProcedure } from '../trpc'; @@ -16,7 +13,7 @@ export const downloadDocumentCertificateRoute = authenticatedProcedure .input(ZDownloadDocumentCertificateRequestSchema) .output(ZDownloadDocumentCertificateResponseSchema) .mutation(async ({ input, ctx }) => { - const { teamId } = ctx; + const { teamId, user } = ctx; const { documentId } = input; ctx.logger.info({ @@ -27,20 +24,43 @@ export const downloadDocumentCertificateRoute = authenticatedProcedure const document = await getDocumentById({ documentId, - userId: ctx.user.id, + userId: user.id, teamId, }); + if (!document || (teamId && document.teamId !== teamId)) { + throw new AppError(AppErrorCode.UNAUTHORIZED, { + message: 'You do not have access to this document.', + }); + } + if (!isDocumentCompleted(document.status)) { throw new AppError('DOCUMENT_NOT_COMPLETE'); } - const encrypted = encryptSecondaryData({ - data: document.id.toString(), - expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(), - }); + try { + const pdfBuffer = await getCertificatePdf({ + documentId: document.id, + language: document.documentMeta?.language, + }); - return { - url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encrypted}`, - }; + const base64Pdf = pdfBuffer.toString('base64'); + const filename = `${document.title.replace(/\.pdf$/, '')}_certificate.pdf`; + + return { + pdfData: base64Pdf, + filename, + contentType: 'application/pdf', + }; + } catch (error) { + ctx.logger.error({ + error, + message: 'Failed to generate certificate PDF', + documentId, + }); + + throw new AppError(AppErrorCode.UNKNOWN_ERROR, { + message: 'Failed to generate certificate PDF', + }); + } }); diff --git a/packages/trpc/server/document-router/download-document-certificate.types.ts b/packages/trpc/server/document-router/download-document-certificate.types.ts index df81f1cad..74e57e95c 100644 --- a/packages/trpc/server/document-router/download-document-certificate.types.ts +++ b/packages/trpc/server/document-router/download-document-certificate.types.ts @@ -5,7 +5,9 @@ export const ZDownloadDocumentCertificateRequestSchema = z.object({ }); export const ZDownloadDocumentCertificateResponseSchema = z.object({ - url: z.string(), + pdfData: z.string(), + filename: z.string(), + contentType: z.string(), }); export type TDownloadDocumentCertificateRequest = z.infer<