Widen the set of link previews which can be received
This commit is contained in:
parent
243ed832ba
commit
2e1e6e847a
10 changed files with 296 additions and 16 deletions
|
@ -778,6 +778,7 @@
|
||||||
isStickerPack: window.Signal.LinkPreviews.isStickerPack(preview.url),
|
isStickerPack: window.Signal.LinkPreviews.isStickerPack(preview.url),
|
||||||
domain: window.Signal.LinkPreviews.getDomain(preview.url),
|
domain: window.Signal.LinkPreviews.getDomain(preview.url),
|
||||||
image: preview.image ? this.getPropsForAttachment(preview.image) : null,
|
image: preview.image ? this.getPropsForAttachment(preview.image) : null,
|
||||||
|
date: preview.date ? preview.date.toNumber() : null,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
getPropsForQuote() {
|
getPropsForQuote() {
|
||||||
|
@ -2327,7 +2328,7 @@
|
||||||
item =>
|
item =>
|
||||||
(item.image || item.title) &&
|
(item.image || item.title) &&
|
||||||
urls.includes(item.url) &&
|
urls.includes(item.url) &&
|
||||||
window.Signal.LinkPreviews.isLinkInWhitelist(item.url)
|
window.Signal.LinkPreviews.isLinkSafeToPreview(item.url)
|
||||||
);
|
);
|
||||||
if (preview.length < incomingPreview.length) {
|
if (preview.length < incomingPreview.length) {
|
||||||
window.log.info(
|
window.log.info(
|
||||||
|
|
2
js/modules/link_previews.d.ts
vendored
2
js/modules/link_previews.d.ts
vendored
|
@ -1 +1,3 @@
|
||||||
|
export function isLinkSafeToPreview(link: string): boolean;
|
||||||
|
|
||||||
export function isLinkSneaky(link: string): boolean;
|
export function isLinkSneaky(link: string): boolean;
|
||||||
|
|
|
@ -15,12 +15,23 @@ module.exports = {
|
||||||
getDomain,
|
getDomain,
|
||||||
getTitleMetaTag,
|
getTitleMetaTag,
|
||||||
getImageMetaTag,
|
getImageMetaTag,
|
||||||
|
isLinkSafeToPreview,
|
||||||
isLinkInWhitelist,
|
isLinkInWhitelist,
|
||||||
isMediaLinkInWhitelist,
|
isMediaLinkInWhitelist,
|
||||||
isLinkSneaky,
|
isLinkSneaky,
|
||||||
isStickerPack,
|
isStickerPack,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function isLinkSafeToPreview(link) {
|
||||||
|
let url;
|
||||||
|
try {
|
||||||
|
url = new URL(link);
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return url.protocol === 'https:' && !isLinkSneaky(link);
|
||||||
|
}
|
||||||
|
|
||||||
const SUPPORTED_DOMAINS = [
|
const SUPPORTED_DOMAINS = [
|
||||||
'youtube.com',
|
'youtube.com',
|
||||||
'www.youtube.com',
|
'www.youtube.com',
|
||||||
|
@ -41,6 +52,10 @@ const SUPPORTED_DOMAINS = [
|
||||||
'signal.art',
|
'signal.art',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// This function will soon be removed in favor of `isLinkSafeToPreview`. It is
|
||||||
|
// currently used because outbound-from-Desktop link previews only support a
|
||||||
|
// few domains (see the list above). We will soon remove this restriction to
|
||||||
|
// allow link previews from all domains, making this function obsolete.
|
||||||
function isLinkInWhitelist(link) {
|
function isLinkInWhitelist(link) {
|
||||||
try {
|
try {
|
||||||
const url = new URL(link);
|
const url = new URL(link);
|
||||||
|
@ -69,6 +84,9 @@ function isStickerPack(link) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const SUPPORTED_MEDIA_DOMAINS = /^([^.]+\.)*(ytimg\.com|cdninstagram\.com|redd\.it|imgur\.com|fbcdn\.net|pinimg\.com)$/i;
|
const SUPPORTED_MEDIA_DOMAINS = /^([^.]+\.)*(ytimg\.com|cdninstagram\.com|redd\.it|imgur\.com|fbcdn\.net|pinimg\.com)$/i;
|
||||||
|
|
||||||
|
// This function will soon be removed. See the comment in `isLinkInWhitelist`
|
||||||
|
// for more info.
|
||||||
function isMediaLinkInWhitelist(link) {
|
function isMediaLinkInWhitelist(link) {
|
||||||
try {
|
try {
|
||||||
const url = new URL(link);
|
const url = new URL(link);
|
||||||
|
|
|
@ -189,6 +189,8 @@ message DataMessage {
|
||||||
optional string url = 1;
|
optional string url = 1;
|
||||||
optional string title = 2;
|
optional string title = 2;
|
||||||
optional AttachmentPointer image = 3;
|
optional AttachmentPointer image = 3;
|
||||||
|
optional string description = 4;
|
||||||
|
optional uint64 date = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Sticker {
|
message Sticker {
|
||||||
|
|
|
@ -870,6 +870,8 @@
|
||||||
|
|
||||||
.module-message__link-preview__content {
|
.module-message__link-preview__content {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
border: 1px solid transparent; /* Color overwritten below. */
|
||||||
|
border-bottom: 0;
|
||||||
border-top-left-radius: 16px;
|
border-top-left-radius: 16px;
|
||||||
border-top-right-radius: 16px;
|
border-top-right-radius: 16px;
|
||||||
background-color: $color-white;
|
background-color: $color-white;
|
||||||
|
@ -878,11 +880,11 @@
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
border: 1px solid $color-black-alpha-20;
|
border-color: $color-black-alpha-20;
|
||||||
}
|
}
|
||||||
@include dark-theme {
|
@include dark-theme {
|
||||||
background-color: $color-gray-95;
|
background-color: $color-gray-95;
|
||||||
border: 1px solid $color-gray-60;
|
border-color: $color-gray-60;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -919,11 +921,29 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-message__link-preview__location {
|
.module-message__link-preview__description {
|
||||||
@include font-body-2;
|
@include font-body-2;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 5;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
|
||||||
|
@include light-theme {
|
||||||
|
color: $color-gray-90;
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
color: $color-gray-05;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-message__link-preview__footer {
|
||||||
|
@include font-body-2;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
align-items: center;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
text-transform: uppercase;
|
|
||||||
|
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
color: $color-gray-60;
|
color: $color-gray-60;
|
||||||
|
@ -931,6 +951,21 @@
|
||||||
@include dark-theme {
|
@include dark-theme {
|
||||||
color: $color-gray-25;
|
color: $color-gray-25;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> *:not(:first-child) {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '•';
|
||||||
|
font-size: 50%;
|
||||||
|
margin-left: 0.2rem;
|
||||||
|
margin-right: 0.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-message__link-preview__location {
|
||||||
|
text-transform: lowercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-message__author {
|
.module-message__author {
|
||||||
|
|
|
@ -4,12 +4,43 @@ const {
|
||||||
findLinks,
|
findLinks,
|
||||||
getTitleMetaTag,
|
getTitleMetaTag,
|
||||||
getImageMetaTag,
|
getImageMetaTag,
|
||||||
|
isLinkSafeToPreview,
|
||||||
isLinkInWhitelist,
|
isLinkInWhitelist,
|
||||||
isLinkSneaky,
|
isLinkSneaky,
|
||||||
isMediaLinkInWhitelist,
|
isMediaLinkInWhitelist,
|
||||||
} = require('../../js/modules/link_previews');
|
} = require('../../js/modules/link_previews');
|
||||||
|
|
||||||
describe('Link previews', () => {
|
describe('Link previews', () => {
|
||||||
|
describe('#isLinkSafeToPreview', () => {
|
||||||
|
it('returns false for invalid URLs', () => {
|
||||||
|
assert.isFalse(isLinkSafeToPreview(''));
|
||||||
|
assert.isFalse(isLinkSafeToPreview('https'));
|
||||||
|
assert.isFalse(isLinkSafeToPreview('https://'));
|
||||||
|
assert.isFalse(isLinkSafeToPreview('https://bad url'));
|
||||||
|
assert.isFalse(isLinkSafeToPreview('example.com'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false for non-HTTPS URLs', () => {
|
||||||
|
assert.isFalse(isLinkSafeToPreview('http://example.com'));
|
||||||
|
assert.isFalse(isLinkSafeToPreview('ftp://example.com'));
|
||||||
|
assert.isFalse(isLinkSafeToPreview('file://example'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if the link is "sneaky"', () => {
|
||||||
|
// See `isLinkSneaky` tests below for more thorough checking.
|
||||||
|
assert.isFalse(isLinkSafeToPreview('https://user:pass@example.com'));
|
||||||
|
assert.isFalse(isLinkSafeToPreview('https://aquí.example'));
|
||||||
|
assert.isFalse(isLinkSafeToPreview('https://aqu%C3%AD.example'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true for "safe" urls', () => {
|
||||||
|
assert.isTrue(isLinkSafeToPreview('https://example.com'));
|
||||||
|
assert.isTrue(
|
||||||
|
isLinkSafeToPreview('https://example.com/foo/bar?query=string#hash')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#isLinkInWhitelist', () => {
|
describe('#isLinkInWhitelist', () => {
|
||||||
it('returns true for valid links', () => {
|
it('returns true for valid links', () => {
|
||||||
assert.strictEqual(isLinkInWhitelist('https://youtube.com/blah'), true);
|
assert.strictEqual(isLinkInWhitelist('https://youtube.com/blah'), true);
|
||||||
|
|
|
@ -358,7 +358,10 @@ story.add('Link Preview', () => {
|
||||||
},
|
},
|
||||||
isStickerPack: false,
|
isStickerPack: false,
|
||||||
title: 'Signal',
|
title: 'Signal',
|
||||||
|
description:
|
||||||
|
'Say "hello" to a different messaging experience. An unexpected focus on privacy, combined with all of the features you expect.',
|
||||||
url: 'https://www.signal.org',
|
url: 'https://www.signal.org',
|
||||||
|
date: new Date(2020, 2, 10).valueOf(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
status: 'sent',
|
status: 'sent',
|
||||||
|
@ -382,7 +385,10 @@ story.add('Link Preview with Small Image', () => {
|
||||||
},
|
},
|
||||||
isStickerPack: false,
|
isStickerPack: false,
|
||||||
title: 'Signal',
|
title: 'Signal',
|
||||||
|
description:
|
||||||
|
'Say "hello" to a different messaging experience. An unexpected focus on privacy, combined with all of the features you expect.',
|
||||||
url: 'https://www.signal.org',
|
url: 'https://www.signal.org',
|
||||||
|
date: new Date(2020, 2, 10).valueOf(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
status: 'sent',
|
status: 'sent',
|
||||||
|
@ -399,7 +405,161 @@ story.add('Link Preview without Image', () => {
|
||||||
domain: 'signal.org',
|
domain: 'signal.org',
|
||||||
isStickerPack: false,
|
isStickerPack: false,
|
||||||
title: 'Signal',
|
title: 'Signal',
|
||||||
|
description:
|
||||||
|
'Say "hello" to a different messaging experience. An unexpected focus on privacy, combined with all of the features you expect.',
|
||||||
url: 'https://www.signal.org',
|
url: 'https://www.signal.org',
|
||||||
|
date: new Date(2020, 2, 10).valueOf(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
status: 'sent',
|
||||||
|
text: 'Be sure to look at https://www.signal.org',
|
||||||
|
});
|
||||||
|
|
||||||
|
return renderBothDirections(props);
|
||||||
|
});
|
||||||
|
|
||||||
|
story.add('Link Preview with no description', () => {
|
||||||
|
const props = createProps({
|
||||||
|
previews: [
|
||||||
|
{
|
||||||
|
domain: 'signal.org',
|
||||||
|
isStickerPack: false,
|
||||||
|
title: 'Signal',
|
||||||
|
url: 'https://www.signal.org',
|
||||||
|
date: Date.now(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
status: 'sent',
|
||||||
|
text: 'Be sure to look at https://www.signal.org',
|
||||||
|
});
|
||||||
|
|
||||||
|
return renderBothDirections(props);
|
||||||
|
});
|
||||||
|
|
||||||
|
story.add('Link Preview with long description', () => {
|
||||||
|
const props = createProps({
|
||||||
|
previews: [
|
||||||
|
{
|
||||||
|
domain: 'signal.org',
|
||||||
|
isStickerPack: false,
|
||||||
|
title: 'Signal',
|
||||||
|
description: Array(10)
|
||||||
|
.fill(
|
||||||
|
'Say "hello" to a different messaging experience. An unexpected focus on privacy, combined with all of the features you expect.'
|
||||||
|
)
|
||||||
|
.join(' '),
|
||||||
|
url: 'https://www.signal.org',
|
||||||
|
date: Date.now(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
status: 'sent',
|
||||||
|
text: 'Be sure to look at https://www.signal.org',
|
||||||
|
});
|
||||||
|
|
||||||
|
return renderBothDirections(props);
|
||||||
|
});
|
||||||
|
|
||||||
|
story.add('Link Preview with small image, long description', () => {
|
||||||
|
const props = createProps({
|
||||||
|
previews: [
|
||||||
|
{
|
||||||
|
domain: 'signal.org',
|
||||||
|
image: {
|
||||||
|
contentType: IMAGE_PNG,
|
||||||
|
fileName: 'the-sax.png',
|
||||||
|
height: 50,
|
||||||
|
url: pngUrl,
|
||||||
|
width: 50,
|
||||||
|
},
|
||||||
|
isStickerPack: false,
|
||||||
|
title: 'Signal',
|
||||||
|
description: Array(10)
|
||||||
|
.fill(
|
||||||
|
'Say "hello" to a different messaging experience. An unexpected focus on privacy, combined with all of the features you expect.'
|
||||||
|
)
|
||||||
|
.join(' '),
|
||||||
|
url: 'https://www.signal.org',
|
||||||
|
date: Date.now(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
status: 'sent',
|
||||||
|
text: 'Be sure to look at https://www.signal.org',
|
||||||
|
});
|
||||||
|
|
||||||
|
return renderBothDirections(props);
|
||||||
|
});
|
||||||
|
|
||||||
|
story.add('Link Preview with no date', () => {
|
||||||
|
const props = createProps({
|
||||||
|
previews: [
|
||||||
|
{
|
||||||
|
domain: 'signal.org',
|
||||||
|
image: {
|
||||||
|
contentType: IMAGE_PNG,
|
||||||
|
fileName: 'the-sax.png',
|
||||||
|
height: 240,
|
||||||
|
url: pngUrl,
|
||||||
|
width: 320,
|
||||||
|
},
|
||||||
|
isStickerPack: false,
|
||||||
|
title: 'Signal',
|
||||||
|
description:
|
||||||
|
'Say "hello" to a different messaging experience. An unexpected focus on privacy, combined with all of the features you expect.',
|
||||||
|
url: 'https://www.signal.org',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
status: 'sent',
|
||||||
|
text: 'Be sure to look at https://www.signal.org',
|
||||||
|
});
|
||||||
|
|
||||||
|
return renderBothDirections(props);
|
||||||
|
});
|
||||||
|
|
||||||
|
story.add('Link Preview with too old a date', () => {
|
||||||
|
const props = createProps({
|
||||||
|
previews: [
|
||||||
|
{
|
||||||
|
domain: 'signal.org',
|
||||||
|
image: {
|
||||||
|
contentType: IMAGE_PNG,
|
||||||
|
fileName: 'the-sax.png',
|
||||||
|
height: 240,
|
||||||
|
url: pngUrl,
|
||||||
|
width: 320,
|
||||||
|
},
|
||||||
|
isStickerPack: false,
|
||||||
|
title: 'Signal',
|
||||||
|
description:
|
||||||
|
'Say "hello" to a different messaging experience. An unexpected focus on privacy, combined with all of the features you expect.',
|
||||||
|
url: 'https://www.signal.org',
|
||||||
|
date: 123,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
status: 'sent',
|
||||||
|
text: 'Be sure to look at https://www.signal.org',
|
||||||
|
});
|
||||||
|
|
||||||
|
return renderBothDirections(props);
|
||||||
|
});
|
||||||
|
|
||||||
|
story.add('Link Preview with too new a date', () => {
|
||||||
|
const props = createProps({
|
||||||
|
previews: [
|
||||||
|
{
|
||||||
|
domain: 'signal.org',
|
||||||
|
image: {
|
||||||
|
contentType: IMAGE_PNG,
|
||||||
|
fileName: 'the-sax.png',
|
||||||
|
height: 240,
|
||||||
|
url: pngUrl,
|
||||||
|
width: 320,
|
||||||
|
},
|
||||||
|
isStickerPack: false,
|
||||||
|
title: 'Signal',
|
||||||
|
description:
|
||||||
|
'Say "hello" to a different messaging experience. An unexpected focus on privacy, combined with all of the features you expect.',
|
||||||
|
url: 'https://www.signal.org',
|
||||||
|
date: Date.now() + 3000000000,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
status: 'sent',
|
status: 'sent',
|
||||||
|
|
|
@ -4,6 +4,7 @@ import classNames from 'classnames';
|
||||||
import Measure from 'react-measure';
|
import Measure from 'react-measure';
|
||||||
import { drop, groupBy, orderBy, take } from 'lodash';
|
import { drop, groupBy, orderBy, take } from 'lodash';
|
||||||
import { Manager, Popper, Reference } from 'react-popper';
|
import { Manager, Popper, Reference } from 'react-popper';
|
||||||
|
import moment, { Moment } from 'moment';
|
||||||
|
|
||||||
import { Avatar } from '../Avatar';
|
import { Avatar } from '../Avatar';
|
||||||
import { Spinner } from '../Spinner';
|
import { Spinner } from '../Spinner';
|
||||||
|
@ -50,15 +51,19 @@ interface Trigger {
|
||||||
|
|
||||||
// Same as MIN_WIDTH in ImageGrid.tsx
|
// Same as MIN_WIDTH in ImageGrid.tsx
|
||||||
const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;
|
const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;
|
||||||
|
const MINIMUM_LINK_PREVIEW_DATE = new Date(1990, 0, 1).valueOf();
|
||||||
const STICKER_SIZE = 200;
|
const STICKER_SIZE = 200;
|
||||||
const SELECTED_TIMEOUT = 1000;
|
const SELECTED_TIMEOUT = 1000;
|
||||||
|
const ONE_DAY = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
interface LinkPreviewType {
|
interface LinkPreviewType {
|
||||||
title: string;
|
title: string;
|
||||||
|
description?: string;
|
||||||
domain: string;
|
domain: string;
|
||||||
url: string;
|
url: string;
|
||||||
isStickerPack: boolean;
|
isStickerPack: boolean;
|
||||||
image?: AttachmentType;
|
image?: AttachmentType;
|
||||||
|
date?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MessageStatuses = [
|
export const MessageStatuses = [
|
||||||
|
@ -791,6 +796,15 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
width &&
|
width &&
|
||||||
width >= MINIMUM_LINK_PREVIEW_IMAGE_WIDTH;
|
width >= MINIMUM_LINK_PREVIEW_IMAGE_WIDTH;
|
||||||
|
|
||||||
|
// Don't show old dates or dates too far in the future. This is predicated on the
|
||||||
|
// idea that showing an invalid dates is worse than hiding valid ones.
|
||||||
|
const maximumLinkPreviewDate = Date.now() + ONE_DAY;
|
||||||
|
const isDateValid: boolean =
|
||||||
|
typeof first.date === 'number' &&
|
||||||
|
first.date > MINIMUM_LINK_PREVIEW_DATE &&
|
||||||
|
first.date < maximumLinkPreviewDate;
|
||||||
|
const dateMoment: Moment | null = isDateValid ? moment(first.date) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
@ -860,9 +874,24 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
<div className="module-message__link-preview__title">
|
<div className="module-message__link-preview__title">
|
||||||
{first.title}
|
{first.title}
|
||||||
</div>
|
</div>
|
||||||
|
{first.description && (
|
||||||
|
<div className="module-message__link-preview__description">
|
||||||
|
{first.description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="module-message__link-preview__footer">
|
||||||
<div className="module-message__link-preview__location">
|
<div className="module-message__link-preview__location">
|
||||||
{first.domain}
|
{first.domain}
|
||||||
</div>
|
</div>
|
||||||
|
{dateMoment && (
|
||||||
|
<time
|
||||||
|
className="module-message__link-preview__date"
|
||||||
|
dateTime={dateMoment.toISOString()}
|
||||||
|
>
|
||||||
|
{dateMoment.format('ll')}
|
||||||
|
</time>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
2
ts/textsecure.d.ts
vendored
2
ts/textsecure.d.ts
vendored
|
@ -315,6 +315,8 @@ export declare namespace DataMessageClass {
|
||||||
url?: string;
|
url?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
image?: AttachmentPointerClass;
|
image?: AttachmentPointerClass;
|
||||||
|
description?: string;
|
||||||
|
date?: ProtoBigNumberType;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProtocolVersion {
|
class ProtocolVersion {
|
||||||
|
|
|
@ -10670,34 +10670,34 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/conversation/Message.js",
|
"path": "ts/components/conversation/Message.js",
|
||||||
"line": " this.audioRef = react_1.default.createRef();",
|
"line": " this.audioRef = react_1.default.createRef();",
|
||||||
"lineNumber": 57,
|
"lineNumber": 60,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-01-21T15:46:51.245Z"
|
"updated": "2020-08-28T19:36:40.817Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/conversation/Message.js",
|
"path": "ts/components/conversation/Message.js",
|
||||||
"line": " this.reactionsContainerRef = react_1.default.createRef();",
|
"line": " this.reactionsContainerRef = react_1.default.createRef();",
|
||||||
"lineNumber": 59,
|
"lineNumber": 62,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-01-21T15:46:51.245Z",
|
"updated": "2020-08-28T19:36:40.817Z",
|
||||||
"reasonDetail": "Used for detecting clicks outside reaction viewer"
|
"reasonDetail": "Used for detecting clicks outside reaction viewer"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/conversation/Message.tsx",
|
"path": "ts/components/conversation/Message.tsx",
|
||||||
"line": " public audioRef: React.RefObject<HTMLAudioElement> = React.createRef();",
|
"line": " public audioRef: React.RefObject<HTMLAudioElement> = React.createRef();",
|
||||||
"lineNumber": 205,
|
"lineNumber": 210,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-05-21T16:56:07.875Z"
|
"updated": "2020-08-28T19:36:40.817Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/conversation/Message.tsx",
|
"path": "ts/components/conversation/Message.tsx",
|
||||||
"line": " > = React.createRef();",
|
"line": " > = React.createRef();",
|
||||||
"lineNumber": 209,
|
"lineNumber": 214,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-05-21T16:56:07.875Z"
|
"updated": "2020-08-28T19:36:40.817Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
|
|
Loading…
Reference in a new issue