Skip to content

Commit c53453f

Browse files
committed
feat: ✨ Use custom browserless server
1 parent da9029a commit c53453f

File tree

8 files changed

+129
-133
lines changed

8 files changed

+129
-133
lines changed

apps/api/bun.lockb

392 Bytes
Binary file not shown.

apps/api/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"postinstall": "bunx puppeteer browsers install chrome"
1111
},
1212
"dependencies": {
13+
"@elysiajs/cors": "^1.1.0",
1314
"@elysiajs/swagger": "^1.1.0",
1415
"cloudinary": "^2.4.0",
1516
"elysia": "latest",

apps/api/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { Elysia } from "elysia";
22
import { swagger } from '@elysiajs/swagger'
33
import invoice from "./invoice";
44
import preview from "./preview";
5+
import { cors } from '@elysiajs/cors'
56
const { version } = require("../package.json");
67

78
const app = new Elysia({
8-
}).use(invoice).use(preview).use(swagger({
9+
}).use(invoice).use(preview).use(cors({ origin: /.*\.invoicelink\.io$/ })).use(swagger({
910
path: "/",
1011
documentation: {
1112
info: {

apps/api/src/invoice.ts

Lines changed: 66 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,72 @@
1-
import { Elysia, t } from 'elysia'
2-
import puppeteer from 'puppeteer';
1+
import { Elysia, t } from 'elysia';
2+
import { getBrowser } from './lib/browserless';
33

4-
const plugin = new Elysia()
5-
.get('/invoice', async ({
6-
query
7-
}: {
8-
query: {
9-
id: string;
10-
type: string;
11-
download?: string;
12-
}
13-
}) => {
14-
const { id, type, download } = query
4+
const plugin = new Elysia().get(
5+
'/invoice',
6+
async ({
7+
query
8+
}: {
9+
query: {
10+
id: string;
11+
type: string;
12+
download?: string;
13+
};
14+
}) => {
15+
const { id, type, download } = query;
1516

16-
let browser = await puppeteer.launch({
17-
headless: true, args: [
18-
'--no-sandbox',
19-
'--disable-setuid-sandbox',
20-
'--disable-dev-shm-usage',
21-
'--disable-session-crashed-bubble',
22-
'--disable-accelerated-2d-canvas',
23-
'--no-first-run',
24-
'--no-zygote',
25-
'--single-process',
26-
'--noerrdialogs',
27-
'--disable-gpu'
28-
]
29-
});
30-
const page = await browser.newPage();
17+
let browser = await getBrowser();
18+
const page = await browser.newPage();
3119

32-
await page.goto(`https://app.invoicelink.io/invoice?id=${id}&type=${type}&download=${download}`, { waitUntil: 'domcontentloaded' });
33-
const pdf = await page.pdf({
34-
format: 'A4',
35-
printBackground: true
36-
});
20+
await page.goto(
21+
`https://app.invoicelink.io/invoice?id=${id}&type=${type}&download=${download}`,
22+
{ waitUntil: 'domcontentloaded' }
23+
);
24+
const pdf = await page.pdf({
25+
format: 'A4',
26+
printBackground: true
27+
});
3728

38-
await browser.close();
29+
await browser.close();
3930

40-
return new Response(pdf as unknown as File, {
41-
headers: {
42-
'Content-Type': 'application/pdf',
43-
'Content-Disposition': download ? `attachment; filename="invoice.pdf"` : `inline`
44-
},
45-
status: 200
46-
});
47-
}, {
48-
query: t.Object({
49-
id: t.String({
50-
description: 'An invoice or quicklink id',
51-
error: 'Please provide an id'
52-
}),
53-
type: t.String({
54-
description: 'The type of document to generate',
55-
enum: ['invoice', 'quicklink'],
56-
default: 'invoice',
57-
error: 'Please provide a type'
58-
}),
59-
download: t.Optional(t.Boolean({
60-
description: 'Whether to download the file or display it in the browser',
61-
default: false,
62-
error: 'Please provide a boolean download option'
63-
}))
64-
}),
65-
response: t.Any({
66-
description: 'The generated invoice or quicklink',
67-
type: 'application/pdf',
68-
example: 'invoice.pdf',
69-
error: 'An error occurred generating the PDF'
70-
}),
71-
detail: {
72-
summary: 'Generate PDF Invoice',
73-
description: 'Create a PDF for an invoice or quicklink',
74-
}
75-
});
31+
return new Response(pdf as unknown as File, {
32+
headers: {
33+
'Content-Type': 'application/pdf',
34+
'Content-Disposition': download ? `attachment; filename="invoice.pdf"` : `inline`
35+
},
36+
status: 200
37+
});
38+
},
39+
{
40+
query: t.Object({
41+
id: t.String({
42+
description: 'An invoice or quicklink id',
43+
error: 'Please provide an id'
44+
}),
45+
type: t.String({
46+
description: 'The type of document to generate',
47+
enum: ['invoice', 'quicklink'],
48+
default: 'invoice',
49+
error: 'Please provide a type'
50+
}),
51+
download: t.Optional(
52+
t.Boolean({
53+
description: 'Whether to download the file or display it in the browser',
54+
default: false,
55+
error: 'Please provide a boolean download option'
56+
})
57+
)
58+
}),
59+
response: t.Any({
60+
description: 'The generated invoice or quicklink',
61+
type: 'application/pdf',
62+
example: 'invoice.pdf',
63+
error: 'An error occurred generating the PDF'
64+
}),
65+
detail: {
66+
summary: 'Generate PDF Invoice',
67+
description: 'Create a PDF for an invoice or quicklink'
68+
}
69+
}
70+
);
7671

77-
export default plugin
72+
export default plugin;

apps/api/src/lib/browserless.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import puppeteer from 'puppeteer';
2+
3+
// Connect to browserless so we don't run Chrome on the same hardware in production
4+
export const getBrowser = () =>
5+
puppeteer.connect({
6+
browserWSEndpoint: `wss://browserless.looped.co.za?token=${process.env.BROWSERLESS_API_TOKEN}`
7+
});

apps/api/src/preview.ts

Lines changed: 51 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,57 @@
1-
import { Elysia, t } from 'elysia'
2-
import puppeteer from 'puppeteer';
1+
import { Elysia, t } from 'elysia';
2+
import { getBrowser } from './lib/browserless';
33

4-
const plugin = new Elysia()
5-
.get('/preview', async ({
6-
query
7-
}: {
8-
query: {
9-
styleId: string;
10-
}
11-
}) => {
12-
const { styleId } = query
4+
const plugin = new Elysia().get(
5+
'/preview',
6+
async ({
7+
query
8+
}: {
9+
query: {
10+
styleId: string;
11+
};
12+
}) => {
13+
const { styleId } = query;
1314

14-
let browser = await puppeteer.launch({
15-
headless: true, args: [
16-
'--no-sandbox',
17-
'--disable-setuid-sandbox',
18-
'--disable-dev-shm-usage',
19-
'--disable-session-crashed-bubble',
20-
'--disable-accelerated-2d-canvas',
21-
'--no-first-run',
22-
'--no-zygote',
23-
'--single-process',
24-
'--noerrdialogs',
25-
'--disable-gpu'
26-
],
27-
});
28-
const page = await browser.newPage();
15+
let browser = await getBrowser();
16+
const page = await browser.newPage();
2917

30-
await page.goto(`https://app.invoicelink.io/invoice?styleId=${styleId}`, { waitUntil: 'domcontentloaded' });
31-
const img = await page.screenshot({
32-
fullPage: true,
33-
optimizeForSpeed: true,
34-
quality: 70,
35-
omitBackground: true,
36-
type: 'webp'
37-
});
18+
await page.goto(`https://app.invoicelink.io/invoice?styleId=${styleId}`, {
19+
waitUntil: 'domcontentloaded'
20+
});
21+
const img = await page.screenshot({
22+
fullPage: true,
23+
optimizeForSpeed: true,
24+
quality: 70,
25+
omitBackground: true,
26+
type: 'webp'
27+
});
3828

39-
await browser.close();
29+
await browser.close();
4030

41-
return new Response(img, {
42-
headers: {
43-
'Content-Type': 'image/webp'
44-
},
45-
status: 200
46-
});
47-
}, {
48-
query: t.Object({
49-
styleId: t.String({
50-
description: 'An invoice template style id',
51-
error: 'Please provide an invoice template style id'
52-
}),
53-
}),
54-
response: t.Any({
55-
description: 'Screenshot of the template styles',
56-
type: 'image/webp',
57-
error: 'An error occurred generating the preview'
58-
}),
59-
detail: {
60-
summary: 'Generate Style Preview',
61-
description: 'Create a preview image for invoice template styles',
62-
}
63-
});
31+
return new Response(img, {
32+
headers: {
33+
'Content-Type': 'image/webp'
34+
},
35+
status: 200
36+
});
37+
},
38+
{
39+
query: t.Object({
40+
styleId: t.String({
41+
description: 'An invoice template style id',
42+
error: 'Please provide an invoice template style id'
43+
})
44+
}),
45+
response: t.Any({
46+
description: 'Screenshot of the template styles',
47+
type: 'image/webp',
48+
error: 'An error occurred generating the preview'
49+
}),
50+
detail: {
51+
summary: 'Generate Style Preview',
52+
description: 'Create a preview image for invoice template styles'
53+
}
54+
}
55+
);
6456

65-
export default plugin
57+
export default plugin;

apps/app/src/routes/(app)/api/invoice/+server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const getBrowser = () =>
1111
})
1212
: // Connect to browserless so we don't run Chrome on the same hardware in production
1313
puppeteer.connect({
14-
browserWSEndpoint: `wss://chrome.browserless.io?token=${BROWSERLESS_API_TOKEN}`
14+
browserWSEndpoint: `wss://browserless.looped.co.za?token=${BROWSERLESS_API_TOKEN}`
1515
});
1616

1717
export async function GET({ url }) {

apps/app/src/routes/(app)/api/templatePreview/+server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const getBrowser = () =>
1212
})
1313
: // Connect to browserless so we don't run Chrome on the same hardware in production
1414
puppeteer.connect({
15-
browserWSEndpoint: `wss://chrome.browserless.io?token=${BROWSERLESS_API_TOKEN}`
15+
browserWSEndpoint: `wss://browserless.looped.co.za?token=${BROWSERLESS_API_TOKEN}`
1616
});
1717

1818
export async function GET({ url }) {

0 commit comments

Comments
 (0)