move linkPreviews.js to ts

pull/2239/head
Audric Ackermann 2 years ago
parent 0e2cf98d96
commit 7d570fec52
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -1,3 +0,0 @@
export function isLinkSafeToPreview(link: string): boolean;
export function isLinkSneaky(link: string): boolean;

@ -1,93 +0,0 @@
const { isObject, isString } = require('lodash');
const ITEMS_STORE_NAME = 'items';
const LAST_PROCESSED_INDEX_KEY = 'attachmentMigration_lastProcessedIndex';
const IS_MIGRATION_COMPLETE_KEY = 'attachmentMigration_isComplete';
const MESSAGE_LAST_INDEX_KEY = 'sqlMigration_messageLastIndex';
const MESSAGE_COUNT_KEY = 'sqlMigration_messageCount';
const UNPROCESSED_LAST_INDEX_KEY = 'sqlMigration_unprocessedLastIndex';
// Public API
exports.getAttachmentMigrationLastProcessedIndex = connection =>
exports._getItem(connection, LAST_PROCESSED_INDEX_KEY);
exports.setAttachmentMigrationLastProcessedIndex = (connection, value) =>
exports._setItem(connection, LAST_PROCESSED_INDEX_KEY, value);
exports.deleteAttachmentMigrationLastProcessedIndex = connection =>
exports._deleteItem(connection, LAST_PROCESSED_INDEX_KEY);
exports.isAttachmentMigrationComplete = async connection =>
Boolean(await exports._getItem(connection, IS_MIGRATION_COMPLETE_KEY));
exports.markAttachmentMigrationComplete = connection =>
exports._setItem(connection, IS_MIGRATION_COMPLETE_KEY, true);
exports.getMessageExportLastIndex = connection =>
exports._getItem(connection, MESSAGE_LAST_INDEX_KEY);
exports.setMessageExportLastIndex = (connection, lastIndex) =>
exports._setItem(connection, MESSAGE_LAST_INDEX_KEY, lastIndex);
exports.getMessageExportCount = connection => exports._getItem(connection, MESSAGE_COUNT_KEY);
exports.setMessageExportCount = (connection, count) =>
exports._setItem(connection, MESSAGE_COUNT_KEY, count);
exports.getUnprocessedExportLastIndex = connection =>
exports._getItem(connection, UNPROCESSED_LAST_INDEX_KEY);
exports.setUnprocessedExportLastIndex = (connection, lastIndex) =>
exports._setItem(connection, UNPROCESSED_LAST_INDEX_KEY, lastIndex);
// Private API
exports._getItem = (connection, key) => {
if (!isObject(connection)) {
throw new TypeError("'connection' is required");
}
if (!isString(key)) {
throw new TypeError("'key' must be a string");
}
const transaction = connection.transaction(ITEMS_STORE_NAME, 'readonly');
const itemsStore = transaction.objectStore(ITEMS_STORE_NAME);
const request = itemsStore.get(key);
return new Promise((resolve, reject) => {
request.onerror = event => reject(event.target.error);
request.onsuccess = event => resolve(event.target.result ? event.target.result.value : null);
});
};
exports._setItem = (connection, key, value) => {
if (!isObject(connection)) {
throw new TypeError("'connection' is required");
}
if (!isString(key)) {
throw new TypeError("'key' must be a string");
}
const transaction = connection.transaction(ITEMS_STORE_NAME, 'readwrite');
const itemsStore = transaction.objectStore(ITEMS_STORE_NAME);
const request = itemsStore.put({ id: key, value }, key);
return new Promise((resolve, reject) => {
request.onerror = event => reject(event.target.error);
request.onsuccess = () => resolve();
});
};
exports._deleteItem = (connection, key) => {
if (!isObject(connection)) {
throw new TypeError("'connection' is required");
}
if (!isString(key)) {
throw new TypeError("'key' must be a string");
}
const transaction = connection.transaction(ITEMS_STORE_NAME, 'readwrite');
const itemsStore = transaction.objectStore(ITEMS_STORE_NAME);
const request = itemsStore.delete(key);
return new Promise((resolve, reject) => {
request.onerror = event => reject(event.target.error);
request.onsuccess = () => resolve();
});
};

@ -3,9 +3,7 @@
const Crypto = require('./crypto');
const Data = require('../../ts/data/data');
const OS = require('../../ts/OS');
const Settings = require('./settings');
const Util = require('../../ts/util');
const LinkPreviews = require('./link_previews');
const { Message } = require('../../ts/components/conversation/message/message-item/Message');
// Components
@ -15,9 +13,6 @@ const {
const { SessionInboxView } = require('../../ts/components/SessionInboxView');
// Types
const SettingsType = require('../../ts/types/Settings');
exports.setup = () => {
Data.init();
@ -27,18 +22,11 @@ exports.setup = () => {
Message,
};
const Types = {
Settings: SettingsType,
};
return {
Components,
Crypto,
Data,
LinkPreviews,
OS,
Settings,
Types,
Util,
};
};

@ -3,11 +3,11 @@ import React from 'react';
import LinkifyIt from 'linkify-it';
import { RenderTextCallbackType } from '../../types/Util';
import { isLinkSneaky } from '../../../js/modules/link_previews';
import { updateConfirmModal } from '../../state/ducks/modalDialog';
import { shell } from 'electron';
import { MessageInteraction } from '../../interactions';
import { useDispatch } from 'react-redux';
import { LinkPreviews } from '../../util/linkPreviews';
const linkify = LinkifyIt();
@ -73,7 +73,7 @@ export const Linkify = (props: Props): JSX.Element => {
}
const { url, text: originalText } = match;
const isLink = SUPPORTED_PROTOCOLS.test(url) && !isLinkSneaky(url);
const isLink = SUPPORTED_PROTOCOLS.test(url) && !LinkPreviews.isLinkSneaky(url);
if (isLink) {
results.push(
<a key={count++} href={url} onClick={handleClick}>

@ -7,6 +7,7 @@ import { AttachmentUtil, LinkPreviewUtil } from '../../util';
import { fetchLinkPreviewImage } from '../../util/linkPreviewFetch';
import { StagedLinkPreview } from './StagedLinkPreview';
import { getImageDimensions } from '../../types/attachments/VisualAttachment';
import { LinkPreviews } from '../../util/linkPreviews';
export interface StagedLinkPreviewProps extends StagedLinkPreviewData {
onClose: (url: string) => void;
@ -33,7 +34,7 @@ export const getPreview = async (
abortSignal: AbortSignal
): Promise<null | GetLinkPreviewResult> => {
// This is already checked elsewhere, but we want to be extra-careful.
if (!window.Signal.LinkPreviews.isLinkSafeToPreview(url)) {
if (!LinkPreviews.isLinkSafeToPreview(url)) {
throw new Error('Link not safe for preview');
}
@ -50,7 +51,7 @@ export const getPreview = async (
const { title, imageHref, date } = linkPreviewMetadata;
let image;
if (imageHref && window.Signal.LinkPreviews.isLinkSafeToPreview(imageHref)) {
if (imageHref && LinkPreviews.isLinkSafeToPreview(imageHref)) {
let objectUrl: void | string;
try {
window?.log?.info('insecureNodeFetch => plaintext for getPreview()');

@ -54,6 +54,7 @@ import {
styleForCompositionBoxSuggestions,
} from './UserMentions';
import { renderEmojiQuickResultRow, searchEmojiForQuery } from './EmojiQuickResult';
import { LinkPreviews } from '../../../util/linkPreviews';
export interface ReplyingToMessageProps {
convoId: string;
@ -553,7 +554,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
return null;
}
// we try to match the first link found in the current message
const links = window.Signal.LinkPreviews.findLinks(this.state.draft, undefined);
const links = LinkPreviews.findLinks(this.state.draft, undefined);
if (!links || links.length === 0 || ignoredLink === links[0]) {
if (this.state.stagedLinkPreview) {
this.setState({
@ -621,7 +622,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
isLoaded: true,
title: ret?.title || null,
url: ret?.url || null,
domain: (ret?.url && window.Signal.LinkPreviews.getDomain(ret.url)) || '',
domain: (ret?.url && LinkPreviews.getDomain(ret.url)) || '',
image: ret?.image,
},
});

@ -8,6 +8,7 @@ import { ToastUtils } from '../../../session/utils';
import { updateConfirmModal } from '../../../state/ducks/modalDialog';
import { toggleAudioAutoplay } from '../../../state/ducks/userConfig';
import { getAudioAutoplay } from '../../../state/selectors/userConfig';
import { isHideMenuBarSupported } from '../../../types/Settings';
import { SessionButtonColor } from '../../basic/SessionButton';
import { SessionSettingButtonItem, SessionToggleWithDescription } from '../SessionSettingListItem';
@ -70,7 +71,7 @@ export const SettingsCategoryAppearance = (props: { hasPassword: boolean | null
return (
<>
{window.Signal.Types.Settings.isHideMenuBarSupported() && (
{isHideMenuBarSupported() && (
<SessionToggleWithDescription
onClickToggle={() => {
window.toggleMenuBar();

@ -63,6 +63,7 @@ import {
import { ExpirationTimerOptions } from '../util/expiringMessages';
import { Notifications } from '../util/notifications';
import { Storage } from '../util/storage';
import { LinkPreviews } from '../util/linkPreviews';
// tslint:disable: cyclomatic-complexity
/**
@ -565,7 +566,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
return {
...preview,
domain: window.Signal.LinkPreviews.getDomain(preview.url),
domain: LinkPreviews.getDomain(preview.url),
image,
};
});

@ -13,6 +13,7 @@ import { SignalService } from '../protobuf';
import { UserUtils } from '../session/utils';
import { showMessageRequestBanner } from '../state/ducks/userConfig';
import { MessageDirection } from '../models/messageType';
import { LinkPreviews } from '../util/linkPreviews';
function contentTypeSupported(type: string): boolean {
const Chrome = window.Signal.Util.GoogleChrome;
@ -110,7 +111,7 @@ async function copyFromQuotedMessage(
}
function handleLinkPreviews(messageBody: string, messagePreview: any, message: MessageModel) {
const urls = window.Signal.LinkPreviews.findLinks(messageBody);
const urls = LinkPreviews.findLinks(messageBody);
const incomingPreview = messagePreview || [];
const preview = incomingPreview.filter(
(item: any) => (item.image || item.title) && urls.includes(item.url)

@ -1,19 +1,12 @@
/* global URL */
const { isNumber, compact, isEmpty, range } = require('lodash');
const nodeUrl = require('url');
const LinkifyIt = require('linkify-it');
import { compact, isEmpty, isNumber, range } from 'lodash';
import nodeUrl from 'url';
import LinkifyIt from 'linkify-it';
const linkify = LinkifyIt();
module.exports = {
findLinks,
getDomain,
isLinkSafeToPreview,
isLinkSneaky,
};
function maybeParseHref(href) {
function maybeParseHref(href: string) {
try {
return new URL(href);
} catch (err) {
@ -21,12 +14,12 @@ function maybeParseHref(href) {
}
}
function isLinkSafeToPreview(href) {
function isLinkSafeToPreview(href: string) {
const url = maybeParseHref(href);
return Boolean(url && url.protocol === 'https:' && !isLinkSneaky(href));
}
function findLinks(text, caretLocation) {
function findLinks(text: string, caretLocation?: number) {
const haveCaretLocation = isNumber(caretLocation);
const textLength = text ? text.length : 0;
@ -50,7 +43,7 @@ function findLinks(text, caretLocation) {
);
}
function getDomain(href) {
function getDomain(href: string) {
const url = maybeParseHref(href);
return url ? url.hostname : null;
}
@ -89,7 +82,7 @@ const VALID_URI_CHARACTERS = new Set([
const ASCII_PATTERN = new RegExp('[\\u0020-\\u007F]', 'g');
const MAX_HREF_LENGTH = 2 ** 12;
function isLinkSneaky(href) {
function isLinkSneaky(href: string) {
// This helps users avoid extremely long links (which could be hiding something
// sketchy) and also sidesteps the performance implications of extremely long hrefs.
if (href.length > MAX_HREF_LENGTH) {
@ -154,3 +147,5 @@ function isLinkSneaky(href) {
const pathAndHash = startOfPathAndHash === -1 ? '' : href.substr(startOfPathAndHash);
return [...pathAndHash].some(character => !VALID_URI_CHARACTERS.has(character));
}
export const LinkPreviews = { isLinkSneaky, getDomain, findLinks, isLinkSafeToPreview };
Loading…
Cancel
Save