apps/newsletters-api/src/services/image/image-signer.ts (59 lines of code) (raw):
import { format } from '@guardian/image';
import type {
NewsletterData,
NewsletterDataWithoutMeta,
} from '@newsletters-nx/newsletters-data-client';
import { getConfigValue } from '@newsletters-nx/util';
interface Props {
dpr?: number;
width: number;
}
export const signImage = async (
src: string,
{ width = 650, dpr = 2 }: Props,
): Promise<string> => {
const salt = await getConfigValue('imageSalt');
if (!salt) throw new Error(`imageSalt not found`);
return format(src, salt, {
quality: 85,
dpr,
width,
});
};
export const signTemplateImages = async (
newsletterData: NewsletterDataWithoutMeta,
): Promise<NewsletterData> => {
let signedImages = {};
const { renderingOptions } = newsletterData;
if (!renderingOptions) return newsletterData;
const { mainBannerUrl, subheadingBannerUrl, darkSubheadingBannerUrl } =
renderingOptions;
const imageKeyValueMapping = [
{ key: 'mainBannerUrl', imageUrl: mainBannerUrl },
{ key: 'subheadingBannerUrl', imageUrl: subheadingBannerUrl },
{ key: 'darkSubheadingBannerUrl', imageUrl: darkSubheadingBannerUrl },
];
for await (const image of imageKeyValueMapping) {
const { key, imageUrl } = image;
if (imageUrl) {
signedImages = shouldSignImage(imageUrl)
? {
...signedImages,
[key]: await signImage(imageUrl, { dpr: 2, width: 650 }),
}
: { ...signedImages, [key]: imageUrl };
}
}
return {
...newsletterData,
renderingOptions: {
...renderingOptions,
...signedImages,
},
};
};
const supportedSigningDomains = ['uploads.guim.co.uk'];
export const shouldSignImage = (imageUrl: string): boolean => {
const asUrl = new URL(imageUrl);
return supportedSigningDomains.includes(asUrl.hostname);
};