commit
c5fbbe963b
|
@ -1,19 +1,14 @@
|
||||||
const { parseRequest } = require('./parser');
|
import { IncomingMessage, ServerResponse } from 'http';
|
||||||
const { getScreenshot } = require('./chromium');
|
import { parseRequest } from './parser';
|
||||||
const { getHtml } = require('./template');
|
import { getScreenshot } from './chromium';
|
||||||
const { writeTempFile, pathToFileURL } = require('./file');
|
import { getHtml } from './template';
|
||||||
|
import { writeTempFile, pathToFileURL } from './file';
|
||||||
|
|
||||||
async function handler(req, res) {
|
async function handler(req: IncomingMessage, res: ServerResponse) {
|
||||||
try {
|
try {
|
||||||
let {
|
const { type, text, fontWeight, fontSize, images } = parseRequest(req);
|
||||||
type = 'png',
|
const html = getHtml(text, fontWeight, fontSize, images);
|
||||||
text = 'Hello',
|
const filePath = await writeTempFile(text, html);
|
||||||
fontWeight = 'bold',
|
|
||||||
image = 'now-black',
|
|
||||||
} = parseRequest(req);
|
|
||||||
const name = decodeURIComponent(text);
|
|
||||||
const html = getHtml(name, fontWeight, image);
|
|
||||||
const filePath = await writeTempFile(name, html);
|
|
||||||
const fileUrl = pathToFileURL(filePath);
|
const fileUrl = pathToFileURL(filePath);
|
||||||
const file = await getScreenshot(fileUrl, type);
|
const file = await getScreenshot(fileUrl, type);
|
||||||
res.statusCode = 200;
|
res.statusCode = 200;
|
|
@ -1,8 +1,9 @@
|
||||||
const chrome = require('chrome-aws-lambda');
|
import * as chromeAwsLambda from 'chrome-aws-lambda';
|
||||||
const puppeteer = require('puppeteer-core');
|
import { launch } from 'puppeteer-core';
|
||||||
|
const chrome = chromeAwsLambda as any;
|
||||||
|
|
||||||
async function getScreenshot(url, type) {
|
export async function getScreenshot(url: string, type: ScreenshotType) {
|
||||||
const browser = await puppeteer.launch({
|
const browser = await launch({
|
||||||
args: chrome.args,
|
args: chrome.args,
|
||||||
executablePath: await chrome.executablePath,
|
executablePath: await chrome.executablePath,
|
||||||
headless: chrome.headless,
|
headless: chrome.headless,
|
||||||
|
@ -15,5 +16,3 @@ async function getScreenshot(url, type) {
|
||||||
await browser.close();
|
await browser.close();
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { getScreenshot };
|
|
|
@ -1,21 +1,19 @@
|
||||||
const { writeFile } = require('fs');
|
import { writeFile } from 'fs';
|
||||||
const { join } = require('path');
|
import { join } from 'path';
|
||||||
const { promisify } = require('util');
|
import { promisify } from 'util';
|
||||||
|
import { tmpdir } from 'os';
|
||||||
|
import { URL } from 'url';
|
||||||
const writeFileAsync = promisify(writeFile);
|
const writeFileAsync = promisify(writeFile);
|
||||||
const { tmpdir } = require('os');
|
|
||||||
const { URL } = require('url');
|
|
||||||
|
|
||||||
async function writeTempFile(name, contents) {
|
export async function writeTempFile(name: string, contents: string) {
|
||||||
const randomPath = join(tmpdir(), `${name}.html`);
|
const randomPath = join(tmpdir(), `${name}.html`);
|
||||||
console.log('Writing file to ' + randomPath);
|
console.log('Writing file to ' + randomPath);
|
||||||
await writeFileAsync(randomPath, contents);
|
await writeFileAsync(randomPath, contents);
|
||||||
return randomPath;
|
return randomPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pathToFileURL(path) {
|
export function pathToFileURL(path: string) {
|
||||||
const { href } = new URL(path, 'file:');
|
const { href } = new URL(path, 'file:');
|
||||||
console.log('File url is ' + href);
|
console.log('File url is ' + href);
|
||||||
return href;
|
return href;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { writeTempFile, pathToFileURL }
|
|
4
now.json
4
now.json
|
@ -3,9 +3,9 @@
|
||||||
"alias": "og-image.now.sh",
|
"alias": "og-image.now.sh",
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"builds": [
|
"builds": [
|
||||||
{ "src": "card.js", "use": "@now/node", "config": { "maxLambdaSize": "40mb" } }
|
{ "src": "card.ts", "use": "@now/node@canary", "config": { "maxLambdaSize": "40mb" } }
|
||||||
],
|
],
|
||||||
"routes": [
|
"routes": [
|
||||||
{ "src": "/(.*)", "dest": "/card.js" }
|
{ "src": "/(.*)", "dest": "/card.ts" }
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -12,5 +12,9 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chrome-aws-lambda": "1.11.1",
|
"chrome-aws-lambda": "1.11.1",
|
||||||
"puppeteer-core": "1.11.0"
|
"puppeteer-core": "1.11.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/puppeteer-core": "^1.9.0",
|
||||||
|
"typescript": "^3.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
parser.js
13
parser.js
|
@ -1,13 +0,0 @@
|
||||||
const { parse } = require('url');
|
|
||||||
|
|
||||||
function parseRequest(req) {
|
|
||||||
const { pathname = '/', query = {} } = parse(req.url, true);
|
|
||||||
const { fontWeight, image } = query;
|
|
||||||
console.log('Hit ' + pathname, query);
|
|
||||||
const arr = pathname.slice(1).split('.');
|
|
||||||
const type = arr.pop();
|
|
||||||
const text = arr.join('.');
|
|
||||||
return { type, text, fontWeight, image };
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { parseRequest }
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { IncomingMessage } from 'http';
|
||||||
|
import { parse } from 'url';
|
||||||
|
|
||||||
|
interface ParsedRequest {
|
||||||
|
type: ScreenshotType;
|
||||||
|
text: string;
|
||||||
|
fontWeight: FontWeight;
|
||||||
|
fontSize: string;
|
||||||
|
images: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function parseRequest(req: IncomingMessage) {
|
||||||
|
console.log('HTTP ' + req.url);
|
||||||
|
const { pathname = '/', query = {} } = parse(req.url || '', true);
|
||||||
|
const { fontWeight, fontSize, images } = query;
|
||||||
|
if (Array.isArray(fontWeight)) {
|
||||||
|
throw new Error('Expected a single fontWeight');
|
||||||
|
}
|
||||||
|
if (Array.isArray(fontSize)) {
|
||||||
|
throw new Error('Expected a single fontSize');
|
||||||
|
}
|
||||||
|
const arr = pathname.slice(1).split('.');
|
||||||
|
const type = arr.pop();
|
||||||
|
const text = arr.join('.');
|
||||||
|
const parsedRequest: ParsedRequest = {
|
||||||
|
type: type as ScreenshotType,
|
||||||
|
text: decodeURIComponent(text),
|
||||||
|
fontWeight: fontWeight as FontWeight,
|
||||||
|
fontSize: fontSize || '75px',
|
||||||
|
images: Array.isArray(images) && images.length > 0
|
||||||
|
? images
|
||||||
|
: ['https://assets.zeit.co/image/upload/front/assets/design/now-black.svg'],
|
||||||
|
};
|
||||||
|
return parsedRequest;
|
||||||
|
}
|
|
@ -1,14 +1,10 @@
|
||||||
|
|
||||||
const { readFileSync } = require('fs');
|
import { readFileSync } from 'fs';
|
||||||
|
|
||||||
/**
|
function getCss(fontWeight: FontWeight, fontSize: string) {
|
||||||
*
|
|
||||||
* @param {'bold' | 'normal'} fontWeight
|
|
||||||
*/
|
|
||||||
function getCss(fontWeight) {
|
|
||||||
const regular = `${__dirname}/fonts/Inter-UI-Regular.woff2`;
|
const regular = `${__dirname}/fonts/Inter-UI-Regular.woff2`;
|
||||||
const bold = `${__dirname}/fonts/Inter-UI-Bold.woff2`;
|
const bold = `${__dirname}/fonts/Inter-UI-Bold.woff2`;
|
||||||
const buffer = readFileSync(fontWeight === 'bold' ? bold : regular);
|
const buffer = readFileSync(fontWeight === 'normal' ? regular : bold);
|
||||||
const base64 = buffer.toString('base64');
|
const base64 = buffer.toString('base64');
|
||||||
return `
|
return `
|
||||||
@font-face {
|
@font-face {
|
||||||
|
@ -40,33 +36,24 @@ function getCss(fontWeight) {
|
||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
font-family: 'Inter UI', sans-serif;
|
font-family: 'Inter UI', sans-serif;
|
||||||
font-size: 75px;
|
font-size: ${fontSize};
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: ${fontWeight};
|
font-weight: ${fontWeight};
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function getHtml(text: string, fontWeight: FontWeight, fontSize: string, images: string[]) {
|
||||||
*
|
|
||||||
* @param {string} text
|
|
||||||
* @param {'bold' | 'normal'} fontWeight
|
|
||||||
* @param {'now-black' | 'now-white' | 'zeit-black-triangle' | 'zeit-white-triangle'} image
|
|
||||||
*/
|
|
||||||
function getHtml(text, fontWeight, image) {
|
|
||||||
const logo = `https://assets.zeit.co/image/upload/front/assets/design/${image}.svg`;
|
|
||||||
return `<html>
|
return `<html>
|
||||||
<style>
|
<style>
|
||||||
${getCss(fontWeight)}
|
${getCss(fontWeight, fontSize)}
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<div>
|
<div>
|
||||||
<div class="spacer">
|
<div class="spacer">
|
||||||
<img class="logo" src="${logo}" />
|
<img class="logo" src="${images[0]}" />
|
||||||
<div class="spacer">
|
<div class="spacer">
|
||||||
<div class="heading">${text}</div>
|
<div class="heading">${text}</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>`;
|
</html>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { getHtml }
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"jsx": "react",
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noEmitOnError": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"preserveConstEnums": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
type FontWeight = 'normal' | 'bold';
|
||||||
|
type ScreenshotType = 'png' | 'jpeg' | undefined;
|
Loading…
Reference in New Issue