/* global Signal, textsecure, StringView */ /* eslint-disable no-bitwise */ // 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, }; } 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); } async function getPreview(url) { let html; try { html = await textsecure.messaging.makeProxiedRequest(url); } catch (error) { if (error.code >= 300) { return null; } } const title = Signal.LinkPreviews.getTitleMetaTag(html); const imageUrl = Signal.LinkPreviews.getImageMetaTag(html); 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); } } const hash = await sha256(url); return { title, url, image, hash, }; } window.Signal.LinkPreviews.helper = { getPreview, }; })();