diff --git a/app/api/package.json b/app/api/package.json index b56a4c1..a57d377 100644 --- a/app/api/package.json +++ b/app/api/package.json @@ -3,11 +3,15 @@ "version": "0.0.0", "private": true, "dependencies": { + "@netlify/functions": "^0.7.2", "@redwoodjs/api": "^0.34.1", "@sentry/node": "^6.5.1", + "chrome-aws-lambda": "^10.1.0", "cloudinary": "^1.23.0", "human-id": "^2.0.1", - "nodemailer": "^6.6.2" + "nodemailer": "^6.6.2", + "puppeteer": "^10.1.0", + "puppeteer-core": "^10.1.0" }, "devDependencies": { "@types/nodemailer": "^6.4.2" diff --git a/app/api/src/functions/og-image-generator.ts b/app/api/src/functions/og-image-generator.ts new file mode 100644 index 0000000..d82b84b --- /dev/null +++ b/app/api/src/functions/og-image-generator.ts @@ -0,0 +1,63 @@ +import { builder } from '@netlify/functions' +import type { HandlerResponse } from '@netlify/functions' +import chromium from 'chrome-aws-lambda' + +const captureWidth = 1200 +const captureHeight = 630 +const clipY = 0 + +async function unwrappedHandler (event, context): Promise { + let path = event.path + .replace(/.+\/og-image-generator/, '') + .replace(/\/og-image-.+\.jpg/, '') + + const url = `${process.env.URL}/u${path}/social-card` + + const browser = await chromium.puppeteer.launch({ + executablePath: process.env.URL?.includes('localhost') + ? null + : await chromium.executablePath, + args: ['--no-sandbox','--disable-web-security','--disable-gpu', '--hide-scrollbars', '--disable-setuid-sandbox'], + // args: chromium.args, + defaultViewport: { + width: captureWidth, + height: captureHeight + clipY + }, + headless: chromium.headless + }) + const page = await browser.newPage() + + await page.goto(url, {"waitUntil" : "networkidle0"}); + + const screenshot = await page.screenshot({ + type: 'jpeg', + // netlify functions can only return strings, so base64 it is + encoding: 'base64', + quality: 70, + clip: { + x: 0, + y: clipY, + width: captureWidth, + height: captureHeight + } + }) + + await browser.close() + + if (typeof screenshot !== 'string') { + return { + statusCode: 400, + } + } + + return { + statusCode: 200, + headers: { + 'Content-Type': 'image/jpg' + }, + body: screenshot, + isBase64Encoded: true + } +} + +export const handler = builder(unwrappedHandler) diff --git a/app/web/src/components/Seo/Seo.js b/app/web/src/components/Seo/Seo.tsx similarity index 71% rename from app/web/src/components/Seo/Seo.js rename to app/web/src/components/Seo/Seo.tsx index 69d97f0..1697b8d 100644 --- a/app/web/src/components/Seo/Seo.js +++ b/app/web/src/components/Seo/Seo.tsx @@ -1,6 +1,6 @@ import { Helmet } from 'react-helmet' -const Seo = ({ title, description, lang }) => { +const Seo = ({ title, description, lang, socialImageUrl}: { title: string; description: string; lang: string; socialImageUrl?: string}) => { return ( <> { + Cadhub - {title} diff --git a/app/web/src/components/SocialCardCell/SocialCardCell.tsx b/app/web/src/components/SocialCardCell/SocialCardCell.tsx index 8182221..01ee464 100644 --- a/app/web/src/components/SocialCardCell/SocialCardCell.tsx +++ b/app/web/src/components/SocialCardCell/SocialCardCell.tsx @@ -41,7 +41,7 @@ export const Success = ({ const image = userProject?.Project?.mainImage const gravatar = userProject?.image return ( -
+
-
+
(

404 Page Not Found +
{location.href} 🤷

diff --git a/app/web/src/pages/ProjectPage/ProjectPage.tsx b/app/web/src/pages/ProjectPage/ProjectPage.tsx index 7e401ca..24577be 100644 --- a/app/web/src/pages/ProjectPage/ProjectPage.tsx +++ b/app/web/src/pages/ProjectPage/ProjectPage.tsx @@ -9,9 +9,11 @@ import { Toaster } from '@redwoodjs/web/toast' const ProjectPage = ({ userName, projectTitle }) => { const { currentUser } = useAuth() const [state, thunkDispatch] = useIdeState() + const cacheInvalidator = new Date().toISOString().split('-').slice(0, 2).join('-') + const socialImageUrl = `/.netlify/functions/og-image-generator/${userName}/${projectTitle}/og-image-${cacheInvalidator}.jpg` return ( <> - +