-
-
Notifications
You must be signed in to change notification settings - Fork 39
/
screenshot.js
138 lines (116 loc) · 5.4 KB
/
screenshot.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
const { builder } = require("@netlify/functions");
const chromium = require("chrome-aws-lambda");
function isFullUrl(url) {
try {
new URL(url);
return true;
} catch(e) {
// invalid url OR local path
return false;
}
}
async function screenshot(url, format, viewportSize, dpr = 1, withJs = true) {
const browser = await chromium.puppeteer.launch({
executablePath: await chromium.executablePath,
args: chromium.args,
defaultViewport: {
width: viewportSize[0],
height: viewportSize[1],
deviceScaleFactor: parseFloat(dpr),
},
headless: chromium.headless,
});
const page = await browser.newPage();
if(!withJs) {
page.setJavaScriptEnabled(false);
}
// TODO is there a way to bail at timeout and still show what’s rendered on the page?
await page.goto(url, {
waitUntil: ["load"],
timeout: 8500
});
let options = {
type: format,
encoding: "base64"
};
if(format === "jpeg") {
quality = 80;
}
let output = await page.screenshot(options);
await browser.close();
return output;
}
// Based on https://github.com/DavidWells/netlify-functions-workshop/blob/master/lessons-code-complete/use-cases/13-returning-dynamic-images/functions/return-image.js
async function handler(event, context) {
// e.g. /https%3A%2F%2Fwww.11ty.dev%2F/small/1:1/smaller/
let pathSplit = event.path.split("/").filter(entry => !!entry);
let [url, size, aspectratio, zoom] = pathSplit;
let format = "jpeg"; // hardcoded for now
let viewport = [];
// Set Defaults
format = format || "jpeg";
aspectratio = aspectratio || "1:1";
size = size || "small";
let dpr = 1;
if(zoom === "bigger") {
dpr = 1.4;
} else if(zoom === "smaller") {
dpr = 0.71428571;
}
if(size === "small") {
if(aspectratio === "1:1") {
viewport = [375, 375];
} else if(aspectratio === "9:16") {
viewport = [375, 667];
}
} else if(size === "medium") {
if(aspectratio === "1:1") {
viewport = [650, 650];
} else if(aspectratio === "9:16") {
viewport = [650, 1156];
}
} else if(size === "large") {
// 0.5625 aspect ratio not supported on large
if(aspectratio === "1:1") {
viewport = [1024, 1024];
}
} else if(size === "opengraph") {
// ignores aspectratio and zoom/dpr
viewport = [857, 450];
dpr = 1.4; // the math calculates to a 1200×630 final image
}
url = decodeURIComponent(url);
try {
if(!isFullUrl(url)) {
throw new Error(`Invalid \`url\`: ${url}`);
}
if(!viewport || viewport.length !== 2) {
throw new Error("Incorrect API usage. Expects one of: /:url/ or /:url/:size/ or /:url/:size/:aspectratio/")
}
let output = await screenshot(url, format, viewport, dpr);
// output to Function logs
console.log(url, format, { viewport }, { size }, { dpr }, { aspectratio });
return {
statusCode: 200,
headers: {
"content-type": `image/${format}`
},
body: output,
isBase64Encoded: true
};
} catch (error) {
console.log("Error", error);
return {
// We need to return 200 here or Firefox won’t display the image
// HOWEVER a 200 means that if it times out on the first attempt it will stay the default image until the next build.
statusCode: 200,
headers: {
"content-type": "image/svg+xml",
"x-error-message": error.message
},
body: `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="${viewport[0]}" height="${viewport[1]}" x="0" y="0" viewBox="0 0 1569.4 2186" xml:space="preserve" aria-hidden="true" focusable="false"><style>.st0{fill:#bbb;stroke:#bbb;stroke-width:28;stroke-miterlimit:10}</style><g><path class="st0" d="M562.2 1410.1c-9 0-13.5-12-13.5-36.1V778.9c0-11.5-2.3-16.9-7-16.2-28.4 7.2-42.7 10.8-43.1 10.8-7.9.7-11.8-7.2-11.8-23.7v-51.7c0-14.3 4.3-22.4 12.9-24.2l142.2-36.6c1.1-.3 2.7-.5 4.8-.5 7.9 0 11.8 8.4 11.8 25.3v712c0 24.1-4.7 36.1-14 36.1l-82.3-.1zM930.5 1411.2c-14.4 0-26.8-1-37.4-3-10.6-2-21.6-6.5-33.1-13.5s-20.9-16.6-28.3-28.8-13.4-29.3-18-51.2-7-47.9-7-78.1V960.4c0-7.2-2-10.8-5.9-10.8h-33.4c-9 0-13.5-8.6-13.5-25.8v-29.1c0-17.6 4.5-26.4 13.5-26.4h33.4c3.9 0 5.9-4.8 5.9-14.5l9.7-209.5c1.1-19 5.7-28.5 14-28.5h53.9c9 0 13.5 9.5 13.5 28.5v209.5c0 9.7 2.1 14.5 6.5 14.5H973c9 0 13.5 8.8 13.5 26.4v29.1c0 17.2-4.5 25.8-13.5 25.8h-68.9c-2.5 0-4.2.6-5.1 1.9-.9 1.2-1.3 4.2-1.3 8.9v277.9c0 20.8 1.3 38.2 4 52s6.6 24 11.8 30.4 10.4 10.8 15.6 12.9c5.2 2.2 11.6 3.2 19.1 3.2h38.2c9.7 0 14.5 6.7 14.5 19.9v32.3c0 14.7-5.2 22.1-15.6 22.1l-54.8.1zM1137.2 1475.8c8.2 0 15.4-6.7 21.5-20.2s9.2-32.6 9.2-57.4c0-5.8-3.6-25.7-10.8-59.8l-105.6-438.9c-.7-5-1.1-9-1.1-11.9 0-12.9 2.7-19.4 8.1-19.4h65.2c5 0 9.1 1.7 12.4 5.1s5.8 10.3 7.5 20.7l70 370.5c1.4 4.3 2.3 6.5 2.7 6.5 1.4 0 2.2-2 2.2-5.9l54.9-369.5c1.4-10.8 3.7-18 6.7-21.8s6.9-5.7 11.6-5.7h45.2c6.1 0 9.2 7 9.2 21 0 3.2-.4 7.4-1.1 12.4l-95.9 499.3c-7.5 41.3-15.8 72.9-24.8 94.8s-19 36.8-30.2 44.7c-11.1 7.9-25.8 12-44.2 12.4h-5.4c-29.1 0-48.8-7.7-59.2-23.2-2.9-3.2-4.3-11.5-4.3-24.8 0-26.6 4.3-39.9 12.9-39.9.7 0 7.2 1.8 19.4 5.4 12.4 3.8 20.3 5.6 23.9 5.6z"/><g><path class="st0" d="M291.2 1411.1c-9 0-13.5-12-13.5-36.1V779.9c0-11.5-2.3-16.9-7-16.2-28.4 7.2-42.7 10.8-43.1 10.8-7.9.7-11.8-7.2-11.8-23.7v-51.7c0-14.3 4.3-22.4 12.9-24.2L371 638.2c1.1-.3 2.7-.5 4.8-.5 7.9 0 11.8 8.4 11.8 25.3v712c0 24.1-4.7 36.1-14 36.1h-82.4z"/></g></g></svg>`,
isBase64Encoded: false,
};
}
}
exports.handler = builder(handler);