session-desktop/js/link_previews_helper.js

143 lines
3.3 KiB
JavaScript
Raw Normal View History

2019-02-08 01:51:34 +01:00
/* global
Signal,
textsecure,
2019-02-11 05:00:30 +01:00
StringView
2019-02-08 01:51:34 +01:00
*/
2019-02-11 03:08:17 +01:00
/* eslint-disable no-bitwise */
2019-02-08 01:51:34 +01:00
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Signal = window.Signal || {};
window.Signal.LinkPreviews = window.Signal.LinkPreviews || {};
async function makeChunkedRequest(url) {
const PARALLELISM = 3;
const size = await textsecure.messaging.getProxiedSize(url);
const chunks = await Signal.LinkPreviews.getChunkPattern(size);
let results = [];
const jobs = chunks.map(chunk => async () => {
const { start, end } = chunk;
const result = await textsecure.messaging.makeProxiedRequest(url, {
start,
end,
returnArrayBuffer: true,
});
return {
...chunk,
...result,
};
});
while (jobs.length > 0) {
const activeJobs = [];
for (let i = 0, max = PARALLELISM; i < max; i += 1) {
if (!jobs.length) {
break;
}
const job = jobs.shift();
activeJobs.push(job());
}
// eslint-disable-next-line no-await-in-loop
results = results.concat(await Promise.all(activeJobs));
}
if (!results.length) {
throw new Error('No responses received');
}
const { contentType } = results[0];
const data = Signal.LinkPreviews.assembleChunks(results);
return {
contentType,
data,
};
}
2019-02-11 05:00:30 +01:00
async function sha256(string) {
const arraybuffer = new TextEncoder('utf-8').encode(string);
const digest = await window.crypto.subtle.digest('SHA-256', arraybuffer);
return StringView.arrayBufferToHex(digest);
2019-02-11 03:08:17 +01:00
}
2019-02-08 01:51:34 +01:00
async function getPreview(url) {
let html;
try {
html = await textsecure.messaging.makeProxiedRequest(url);
} catch (error) {
if (error.code >= 300) {
return null;
}
}
2019-02-08 02:51:00 +01:00
const title = Signal.LinkPreviews.getTitleMetaTag(html);
const imageUrl = Signal.LinkPreviews.getImageMetaTag(html);
2019-02-08 01:51:34 +01:00
let image;
let objectUrl;
try {
if (imageUrl) {
if (!Signal.LinkPreviews.isMediaLinkInWhitelist(imageUrl)) {
const primaryDomain = Signal.LinkPreviews.getDomain(url);
const imageDomain = Signal.LinkPreviews.getDomain(imageUrl);
throw new Error(
`imageUrl for domain ${primaryDomain} did not match media whitelist. Domain: ${imageDomain}`
);
}
const data = await makeChunkedRequest(imageUrl);
// Calculate dimensions
const file = new Blob([data.data], {
type: data.contentType,
});
objectUrl = URL.createObjectURL(file);
const dimensions = await Signal.Types.VisualAttachment.getImageDimensions(
{
objectUrl,
logger: window.log,
}
);
image = {
...data,
...dimensions,
contentType: file.type,
};
}
} catch (error) {
// We still want to show the preview if we failed to get an image
window.log.error(
'getPreview failed to get image for link preview:',
error.message
);
} finally {
if (objectUrl) {
URL.revokeObjectURL(objectUrl);
}
}
2019-02-11 05:00:30 +01:00
const hash = await sha256(url);
2019-02-08 01:51:34 +01:00
return {
title,
url,
image,
2019-02-11 05:00:30 +01:00
hash,
2019-02-08 01:51:34 +01:00
};
}
window.Signal.LinkPreviews.helper = {
getPreview,
};
2019-02-08 01:51:34 +01:00
})();