Skip to content

Instantly share code, notes, and snippets.

@Cornally
Last active June 23, 2021 21:36
Show Gist options
  • Save Cornally/219cb6bce5fa7c0fe8570c0c3f15fff8 to your computer and use it in GitHub Desktop.
Save Cornally/219cb6bce5fa7c0fe8570c0c3f15fff8 to your computer and use it in GitHub Desktop.
OpenGraph Lambda Processor — Return custom opengraph responses for your SPA with this lambda function that fetches data for a specific record.
"use strict";
const https = require("https");
// These UAs receive the special page for OpenGraph
const opengraphUAs = [
'facebookexternalhit/1.1',
'Twitterbot',
'Googlebot',
'Slack',
'LinkedInBot/1.0',
'Pinterest'
];
// This will work on Node 8.10 environment
function requestArticle(guid) {
return new Promise((resolve) => {
const options = {
host : "api.audify.fm",
port : "443",
path : `/stream/${guid}?language=en`,
headers : {
"Content-Type" : "application/json"
}
};
https.get(options, (res) => {
res.setEncoding("utf8");
let body = "";
res.on("data", data => {
body += data;
});
res.on("end", () => {
body = JSON.parse(body);
resolve(body.items[0]);
});
});
});
}
async function fetchArticle(guid) {
try {
let response = await requestArticle(guid);
return response;
}
catch(error) {
return error;
}
}
// Return full URL, 'https://app.audify.fm/article/something-title/:guid'
function getUrl(article, guid) {
const readableTitle = article.title.replace(/[\W_]\s*$/, '').replace(/[\W_]+/g, '-').toLowerCase();
return `https://app.audify.fm/article/${readableTitle}/${guid}`
}
// Generate resulting markup for clients being served OG. Replace double-quotes with html entities to ensure
// tag values are not cut short if a double-quote exists in the field's value. Add a prefix to summary and truncate
// to 240 words so we're not spilling the beans.
function getBodyHtml(data, ua, url, uri) {
const title = data.title.replace(/"/g, """);
const summary = 'TL;DR — ' + data.summary.replace(/^(.{240}[^\s]*).*/, "$1").replace(/"/g, """) + '…';
return `<!DOCTYPE html>
<html>
<head>
<!-- COMMON TAGS -->
<meta charset="utf-8">
<title>${data.title} - Audify.FM</title>
<!-- Search Engine -->
<meta name="description" content="${summary}">
<meta name="image" content="${data.image_url}">
<!-- Schema.org for Google -->
<meta itemprop="name" content="${title}">
<meta itemprop="description" content="${summary}">
<meta itemprop="image" content="${data.image_url}">
<!-- Open Graph general (Facebook, Pinterest & Google+) -->
<meta prefix="og: http://ogp.me/ns#" name="og:type" content="website">
<meta prefix="og: http://ogp.me/ns#" name="og:audio" content="${data.audio_url}" />
<meta prefix="og: http://ogp.me/ns#" name="og:title" content="${title}">
<meta prefix="og: http://ogp.me/ns#" name="og:description" content="${summary}">
<meta prefix="og: http://ogp.me/ns#" name="og:image" content="${data.image_url}">
<meta prefix="og: http://ogp.me/ns#" name="og:url" content="${url}">
<meta prefix="og: http://ogp.me/ns#" name="og:site_name" content="${title}">
</head>
<body>
<p>uri: ${uri}</p>
<p>agent: ${ua}</p>
<p>name: ${title}</p>
<p>description: ${summary} </p>
</body>
</html>`;
}
exports.handler = async (event, context, callback) => {
const request = event.Records[0].cf.request;
// Store user-agent of viewer request
const requestUA = request.headers['user-agent'][0].value;
const serveOpenGraphContent = !!opengraphUAs.find(ua => {
if (requestUA.indexOf(ua) !== -1) return true;
});
// Uncomment for debugging
console.log('serve opengraph content: ' + serveOpenGraphContent + ' to ' + requestUA);
// If the request is not for an article, return early.
const uri = request.uri;
const splitUri = uri.split('/');
if (!serveOpenGraphContent) {
callback(null, request);
}
// Get article guid. When split out, the guid is the third param in the URI, '/article/fake-title-here/:guid'
const idx = splitUri.indexOf('article') + 2;
const guid = splitUri[idx];
// Fetch a single article
let article = await fetchArticle(guid);
// Get URL ... even though we have it in the original request
const url = getUrl(article);
// Generate resulting markup for OG-enabled UAs
const body = getBodyHtml(article, requestUA, url, uri);
const response = {
status: '200',
statusDescription: 'HTTP OK',
body,
headers: {
'cache-control': [{
key: 'Cache-Control',
value: 'max-age=100'
}],
'content-type': [{
key: 'Content-Type',
value: 'text/html'
}]
}
};
return response;
};
@Cornally
Copy link
Author

Add the above function to lambda, create a CloudFront behavior for your route (e.g. /article/*) for handling viewer requests. This will render your respective og/meta tag data to round out that serverless sauce.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment