From 7d570fec521ca222773a6d61f55445ae40eefa24 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 23 Mar 2022 16:19:38 +1100 Subject: [PATCH] move linkPreviews.js to ts --- js/modules/link_previews.d.ts | 3 - js/modules/settings.js | 93 ------------------- js/modules/signal.js | 12 --- ts/components/conversation/Linkify.tsx | 4 +- .../conversation/SessionStagedLinkPreview.tsx | 5 +- .../composition/CompositionBox.tsx | 5 +- .../settings/section/CategoryAppearance.tsx | 3 +- ts/models/message.ts | 3 +- ts/receiver/queuedJob.ts | 3 +- .../util/linkPreviews.ts | 25 ++--- 10 files changed, 24 insertions(+), 132 deletions(-) delete mode 100644 js/modules/link_previews.d.ts delete mode 100644 js/modules/settings.js rename js/modules/link_previews.js => ts/util/linkPreviews.ts (88%) diff --git a/js/modules/link_previews.d.ts b/js/modules/link_previews.d.ts deleted file mode 100644 index 183bdb1cf..000000000 --- a/js/modules/link_previews.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function isLinkSafeToPreview(link: string): boolean; - -export function isLinkSneaky(link: string): boolean; diff --git a/js/modules/settings.js b/js/modules/settings.js deleted file mode 100644 index ac8962a8b..000000000 --- a/js/modules/settings.js +++ /dev/null @@ -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(); - }); -}; diff --git a/js/modules/signal.js b/js/modules/signal.js index b0326a7dd..d0eece3d0 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -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, }; }; diff --git a/ts/components/conversation/Linkify.tsx b/ts/components/conversation/Linkify.tsx index b41a31365..ef50a69c6 100644 --- a/ts/components/conversation/Linkify.tsx +++ b/ts/components/conversation/Linkify.tsx @@ -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( diff --git a/ts/components/conversation/SessionStagedLinkPreview.tsx b/ts/components/conversation/SessionStagedLinkPreview.tsx index f577c17cd..bae8cca3e 100644 --- a/ts/components/conversation/SessionStagedLinkPreview.tsx +++ b/ts/components/conversation/SessionStagedLinkPreview.tsx @@ -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 => { // 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()'); diff --git a/ts/components/conversation/composition/CompositionBox.tsx b/ts/components/conversation/composition/CompositionBox.tsx index 70f09a1fa..f9a9944a2 100644 --- a/ts/components/conversation/composition/CompositionBox.tsx +++ b/ts/components/conversation/composition/CompositionBox.tsx @@ -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 { 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 { 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, }, }); diff --git a/ts/components/settings/section/CategoryAppearance.tsx b/ts/components/settings/section/CategoryAppearance.tsx index 8c2ce46e7..39d47d4b3 100644 --- a/ts/components/settings/section/CategoryAppearance.tsx +++ b/ts/components/settings/section/CategoryAppearance.tsx @@ -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() && ( { window.toggleMenuBar(); diff --git a/ts/models/message.ts b/ts/models/message.ts index 4f066ef2a..453481d16 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -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 { return { ...preview, - domain: window.Signal.LinkPreviews.getDomain(preview.url), + domain: LinkPreviews.getDomain(preview.url), image, }; }); diff --git a/ts/receiver/queuedJob.ts b/ts/receiver/queuedJob.ts index cf8aa8d8b..7e1ad3268 100644 --- a/ts/receiver/queuedJob.ts +++ b/ts/receiver/queuedJob.ts @@ -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) diff --git a/js/modules/link_previews.js b/ts/util/linkPreviews.ts similarity index 88% rename from js/modules/link_previews.js rename to ts/util/linkPreviews.ts index 058d42db1..bcd1d2cd5 100644 --- a/js/modules/link_previews.js +++ b/ts/util/linkPreviews.ts @@ -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 };