From 8f476d404c20bb2edbbe040dafcca398d21700a8 Mon Sep 17 00:00:00 2001 From: Ivan Vilanculo Date: Tue, 17 Jan 2023 21:01:09 +0200 Subject: [PATCH] feat: add goto option (#420) * feat: add goto option * chore: update README.md * style: apply lint suggestions Co-authored-by: Ivan Vilanculo --- README.md | 5 +++-- src/__test__/payloadValidator.spec.ts | 10 ++++++++++ src/payloadValidator.ts | 4 ++-- src/services/Pdf.ts | 3 ++- src/services/PdfOptions.ts | 5 ++++- src/services/__test__/PdfOptions.spec.ts | 6 ++++++ src/util/__test__/index.spec.ts | 11 ++++++++++- src/util/index.ts | 9 +++++++++ 8 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 src/__test__/payloadValidator.spec.ts diff --git a/README.md b/README.md index a2de7654..b40f4a39 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,9 @@ The webserver started by express.js has one JSON endpoint to generate PDFs. Will generate a PDF based on the given `payload` data and returns the pdf file as a stream ```json5 { - "content": "", // required - HTML string/handlebars template to be converted to PDF, - "context": {}, // object with the data to be passed to handlebars template engine + "goto": "", // optional - URL to the HTML content/handlebars template to be converted to PDF. This option overrides the content when present. + "content": "", // required when goto is not present - HTML string/handlebars template to be converted to PDF, + "context": {}, // object with the data to be passed to handlebars template engine "orientation": "portrait", // optional - possible values ["portrait", "landscape"] "format": "A4", // optional - possible values ["Letter", "Legal", "Tabloid", "Ledger", "A0", "A1", "A2", "A3", "A4", "A5", "A6"] "header": "", // optional - HTML template for the print header. See https://github.com/puppeteer/puppeteer/blob/main/docs/api.md#pagepdfoptions diff --git a/src/__test__/payloadValidator.spec.ts b/src/__test__/payloadValidator.spec.ts new file mode 100644 index 00000000..91496799 --- /dev/null +++ b/src/__test__/payloadValidator.spec.ts @@ -0,0 +1,10 @@ +import { validatePayload } from '../payloadValidator' + +describe('src/payloadValidator.ts', () => { + it('should only allow payload with one of content or goto options exclusively', () => { + let errors = validatePayload({}) + expect(errors.content[0]).toMatch(/html filed is required /) + errors = validatePayload({ content: '>', goto: 'https://example.com' }) + expect(Object.keys(errors).length).toBe(0) + }) +}) diff --git a/src/payloadValidator.ts b/src/payloadValidator.ts index eb6e2d7e..dd5a4e2a 100644 --- a/src/payloadValidator.ts +++ b/src/payloadValidator.ts @@ -1,7 +1,7 @@ export function validatePayload(body: Record = {}): Record { const errors: Record = {} - if (!body.content) { - errors.content = ['html filed is required.'] + if (!body.content && !body.goto) { + errors.content = ['html filed is required when goto is not present.'] } if (body.content && body.content.includes(' { - await page.setContent(options.content, { waitUntil: 'networkidle0' }) + if (options.goto) await page.goto(options.goto, { waitUntil: 'networkidle0' }) + else await page.setContent(options.content, { waitUntil: 'networkidle0' }) const pdfOptions = Pdf.buildPdfArguments(options, false) return await page.pdf(pdfOptions) } diff --git a/src/services/PdfOptions.ts b/src/services/PdfOptions.ts index 38534b93..f73e32f5 100644 --- a/src/services/PdfOptions.ts +++ b/src/services/PdfOptions.ts @@ -1,5 +1,6 @@ import defaults from 'lodash.defaults' import { PDFMargin, PaperFormat } from 'puppeteer' +import { isValidURL } from '../util' export type PDFOrientation = 'landscape' | 'portrait' export type TocEntry = { @@ -10,6 +11,7 @@ export type TocEntry = { page?: number } export interface PdfOptions { + goto?: string orientation?: PDFOrientation format?: PaperFormat content: string @@ -25,7 +27,8 @@ export interface PdfOptions { } export function pdfOptionsFactory(options: Partial): PdfOptions { - if (!options.content || !options.content.length) { + if (options.goto && !isValidURL(options.goto)) throw new Error('invalid value passed to goto option') + if ((!options.content || !options.content.length) && !options.goto) { throw new Error('content should not be empty') } diff --git a/src/services/__test__/PdfOptions.spec.ts b/src/services/__test__/PdfOptions.spec.ts index b165c1a7..dc240572 100644 --- a/src/services/__test__/PdfOptions.spec.ts +++ b/src/services/__test__/PdfOptions.spec.ts @@ -3,6 +3,12 @@ import { throws } from 'assert' import { PaperFormat } from 'puppeteer' describe('PdfOptions', () => { + it('must not throw any errors when content is not present and got is valid url', () => { + pdfOptionsFactory({ goto: 'https://www.example.com' }) + }) + it('must throw errors when goto is not valid URL', () => { + throws(() => pdfOptionsFactory({ goto: 'www.example.com' }), /invalid value passed to goto option/) + }) it('must trow error with message `content should not be empty` when content is empty', () => { throws(() => pdfOptionsFactory({ content: '' }), /content should not be empty/) }) diff --git a/src/util/__test__/index.spec.ts b/src/util/__test__/index.spec.ts index da94fdcd..d28e9e00 100644 --- a/src/util/__test__/index.spec.ts +++ b/src/util/__test__/index.spec.ts @@ -1,5 +1,5 @@ import { pdfOptionsFactory } from '../../services/PdfOptions' -import { compileHeaderOrFooterTemplate, enhanceContent, prepareToc } from '..' +import { compileHeaderOrFooterTemplate, enhanceContent, isValidURL, prepareToc } from '..' describe('utils.ts', () => { it('should wrap the template with a div with margin and font size', () => { @@ -83,4 +83,13 @@ describe('prepareToc from src/utils.ts', () => { expect(options.content).toContain(`class="removeAfterTocExtraction">${data.id}<`) ) }) + + it('should validate urls', () => { + expect(isValidURL('some random string')).toBe(false) + expect(isValidURL('www.example.com')).toBe(false) + expect(isValidURL('example.com')).toBe(false) + expect(isValidURL('http://example.com')).toBe(true) + expect(isValidURL('http://example.com:80')).toBe(true) + expect(isValidURL('https://example.com')).toBe(true) + }) }) diff --git a/src/util/index.ts b/src/util/index.ts index 297e8eb9..83f5abe0 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -85,3 +85,12 @@ export async function enhanceContent(options: PdfOptions): Promise { prepareToc(options) } + +export const isValidURL = (url: string): boolean => { + try { + new URL(url) + return true + } catch (e) { + return false + } +}