blob: d7c5d425eea43b905a2b69eee4f2153a6e60709f [file] [log] [blame]
idillona3c2fad2022-12-18 23:49:10 -05001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
18import { Router } from 'express';
19import asyncHandler from 'express-async-handler';
20import { HttpStatusCode } from 'jami-web-common';
21import { getLinkPreview } from 'link-preview-js';
22import * as linkify from 'linkifyjs';
23
24import { authenticateToken } from '../middleware/auth.js';
25
26export const linkPreviewRouter = Router();
27
28// Result of getLinkPreview from link-preview-js
29type LinkPreviewJs = {
30 url: string;
31 title: string;
32 siteName: string | undefined;
33 description: string | undefined;
34 mediaType: string;
35 contentType: string | undefined;
36 images: string[];
37 videos: {
38 url: string | undefined;
39 secureUrl: string | null | undefined;
40 type: string | null | undefined;
41 width: string | undefined;
42 height: string | undefined;
43 }[];
44 favicons: string[];
45};
46
47linkPreviewRouter.use(authenticateToken);
48
49const linkPreviewOptions = {
50 // Allowing redirection from http to https
51 // Code from doc: https://github.com/ospfranco/link-preview-js#redirections
52 followRedirects: 'manual',
53 handleRedirects: (baseURL: string, forwardedURL: string) => {
54 const urlObj = new URL(baseURL);
55 const forwardedURLObj = new URL(forwardedURL);
56 if (
57 forwardedURLObj.hostname === urlObj.hostname ||
58 forwardedURLObj.hostname === 'www.' + urlObj.hostname ||
59 'www.' + forwardedURLObj.hostname === urlObj.hostname
60 ) {
61 return true;
62 } else {
63 return false;
64 }
65 },
66} as const;
67
68linkPreviewRouter.get(
69 '/',
70 asyncHandler(async (req, res) => {
71 const url = req.query.url;
72
73 if (typeof url !== 'string') {
74 res.status(HttpStatusCode.BadRequest).send('Invalid query parameters');
75 return;
76 }
77
78 // Add 'http' or 'https' if absent. This is required by getLinkPreview
79 const sanitizedUrl = linkify.find(url)[0]?.href;
80
81 if (!sanitizedUrl) {
82 res.status(HttpStatusCode.BadRequest).send('Invalid url');
83 return;
84 }
85
86 try {
87 const detailedLinkPreview = (await getLinkPreview(sanitizedUrl, linkPreviewOptions)) as LinkPreviewJs;
88 const linkPreview = {
89 title: detailedLinkPreview.title,
90 description: detailedLinkPreview.description,
91 // We might eventualy want to compare the images in order to select the best fit
92 // https://andrejgajdos.com/how-to-create-a-link-preview/
93 image: detailedLinkPreview.images[0] ?? detailedLinkPreview.favicons[0],
94 };
95 res.json(linkPreview).end();
96 } catch (e) {
97 res.status(HttpStatusCode.NotFound).send('Could not access url');
98 }
99 })
100);