mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
Added local link preview
This commit is contained in:
parent
0417f0ffad
commit
8f8e25bb3e
3 changed files with 217 additions and 5 deletions
|
@ -730,6 +730,7 @@
|
|||
<script type='text/javascript' src='js/expire.js'></script>
|
||||
<script type='text/javascript' src='js/conversation_controller.js'></script>
|
||||
<script type='text/javascript' src='js/blocked_number_controller.js'></script>
|
||||
<script type='text/javascript' src='js/link_previews_helper.js'></script>
|
||||
|
||||
<script type='text/javascript' src='js/views/react_wrapper_view.js'></script>
|
||||
<script type='text/javascript' src='js/views/whisper_view.js'></script>
|
||||
|
|
151
js/link_previews_helper.js
Normal file
151
js/link_previews_helper.js
Normal file
|
@ -0,0 +1,151 @@
|
|||
/* global
|
||||
Signal,
|
||||
textsecure,
|
||||
dcodeIO,
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.Signal = window.Signal || {};
|
||||
window.Signal.LinkPreviews = window.Signal.LinkPreviews || {};
|
||||
|
||||
const base64ImageCache = {};
|
||||
|
||||
function getBase64Image(preview) {
|
||||
const { url, image } = preview;
|
||||
if (!url || !image || !image.data) return null;
|
||||
|
||||
// Return the cached value
|
||||
if (base64ImageCache[url]) return base64ImageCache[url];
|
||||
|
||||
// Set the cache and return the value
|
||||
const contentType = image.contentType || 'image/jpeg';
|
||||
const base64 = dcodeIO.ByteBuffer.wrap(image.data).toString('base64');
|
||||
|
||||
const data = `data:${contentType};base64, ${base64}`;
|
||||
base64ImageCache[url] = data;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
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 getPreview(url) {
|
||||
let html;
|
||||
try {
|
||||
html = await textsecure.messaging.makeProxiedRequest(url);
|
||||
} catch (error) {
|
||||
if (error.code >= 300) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const title = window.Signal.LinkPreviews.getTitleMetaTag(html);
|
||||
const imageUrl = window.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);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
url,
|
||||
image,
|
||||
};
|
||||
}
|
||||
|
||||
window.Signal.LinkPreviews.helper = {
|
||||
getPreview,
|
||||
getBase64Image,
|
||||
}
|
||||
})();
|
|
@ -8,6 +8,7 @@
|
|||
/* global Signal: false */
|
||||
/* global textsecure: false */
|
||||
/* global Whisper: false */
|
||||
/* global dcodeIO: false */
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
|
||||
|
@ -84,6 +85,8 @@
|
|||
this.on('unload', this.unload);
|
||||
this.on('expired', this.onExpired);
|
||||
this.setToExpire();
|
||||
|
||||
this.updatePreviews();
|
||||
},
|
||||
idForLogging() {
|
||||
return `${this.get('source')}.${this.get('sourceDevice')} ${this.get(
|
||||
|
@ -109,6 +112,40 @@
|
|||
// eslint-disable-next-line no-bitwise
|
||||
return !!(this.get('flags') & flag);
|
||||
},
|
||||
async updatePreviews() {
|
||||
if (this.updatingPreview) return;
|
||||
|
||||
// Only update the preview if we don't have any set
|
||||
const preview = this.get('preview');
|
||||
if (!_.isEmpty(preview)) return;
|
||||
|
||||
// Make sure we have links we can preview
|
||||
const links = Signal.LinkPreviews.findLinks(this.get('body'));
|
||||
const firstLink = links.find(link => Signal.LinkPreviews.isLinkInWhitelist(link));
|
||||
if (!firstLink) return;
|
||||
|
||||
this.updatingPreview = true;
|
||||
|
||||
try {
|
||||
const result = await Signal.LinkPreviews.helper.getPreview(firstLink);
|
||||
if (!result) {
|
||||
this.updatingPreview = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.image && !result.title) {
|
||||
// A link preview isn't worth showing unless we have either a title or an image
|
||||
this.updatingPreview = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.set({ preview: [result] });
|
||||
} catch (e) {
|
||||
window.log.warn(`Failed to load previews for message: ${this.id}`);
|
||||
} finally {
|
||||
this.updatingPreview = false;
|
||||
}
|
||||
},
|
||||
getEndSessionTranslationKey() {
|
||||
const sessionType = this.get('endSessionType');
|
||||
if (sessionType === 'ongoing') {
|
||||
|
@ -616,11 +653,34 @@
|
|||
getPropsForPreview() {
|
||||
const previews = this.get('preview') || [];
|
||||
|
||||
return previews.map(preview => ({
|
||||
return previews.map(preview => {
|
||||
let image = {};
|
||||
|
||||
// Try set the image from the attachment otherwise just pass in the object
|
||||
if (preview.image) {
|
||||
try {
|
||||
const attachmentProps = this.getPropsForAttachment(preview.image);
|
||||
if (attachmentProps.url) {
|
||||
image = attachmentProps;
|
||||
}
|
||||
} catch (e) {
|
||||
// Only set the image if we have a url to display
|
||||
const url = Signal.LinkPreviews.helper.getBase64Image(preview);
|
||||
if (preview.image.url || url) {
|
||||
image = {
|
||||
...preview.image,
|
||||
url: preview.image.url || url,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...preview,
|
||||
domain: window.Signal.LinkPreviews.getDomain(preview.url),
|
||||
image: preview.image ? this.getPropsForAttachment(preview.image) : null,
|
||||
}));
|
||||
image,
|
||||
};
|
||||
});
|
||||
},
|
||||
getPropsForQuote() {
|
||||
const quote = this.get('quote');
|
||||
|
|
Loading…
Reference in a new issue