Context isolation for the debug log window

This commit is contained in:
Josh Perez 2021-10-06 12:16:51 -04:00 committed by GitHub
parent 0f9608d9a3
commit fa66ddde0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 477 additions and 686 deletions

View File

@ -470,25 +470,33 @@
"description": "Shown in conversation banner when more than one group member's safety number has changed, but they were previously verified."
},
"debugLogExplanation": {
"message": "This log will be posted publicly online for contributors to view. You may examine and edit it before submitting."
"message": "This log will be posted publicly online for contributors to view. You may download the full log before submitting."
},
"debugLogError": {
"message": "Something went wrong with the upload! Please consider manually adding your log to the bug you file."
"message": "Something went wrong with the upload! Please email support@signal.org and attach your log as a text file."
},
"debugLogSuccess": {
"message": "Debug log submitted",
"description": "Title of the success page for submitting a debug log"
},
"debugLogSuccessNextSteps": {
"message": "Debug log uploaded. When you contact support, copy the link below and attach it along with a description of the problem you saw and steps to reproduce it.",
"description": "Explanation of next steps to take when submitting debug log"
},
"debugLogCopy": {
"message": "Copy",
"message": "Copy Link",
"description": "Shown as the text for the copy button on the debug log screen"
},
"debugLogCopyAlt": {
"message": "Copy link to your clipboard",
"description": "Shown as the alt text for the copy button on the debug log screen"
"debugLogSave": {
"message": "Download",
"description": "Shown as the text for the download button on the debug log screen"
},
"debugLogLinkCopied": {
"message": "Link Copied to Your Clipboard",
"description": "Shown in a toast to let the user know that the link to the debug log has been copied to their clipboard"
},
"reportIssue": {
"message": "Report an issue",
"message": "Contact Support",
"description": "Link to open the issue tracker"
},
"gotIt": {

View File

@ -6,6 +6,8 @@
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none';
frame-src 'none';
form-action 'none';
font-src 'self';
img-src 'self' blob: data:;
media-src 'self' blob:;

View File

@ -4,7 +4,7 @@
import { join, normalize } from 'path';
import { pathToFileURL } from 'url';
import * as os from 'os';
import { chmod, realpath } from 'fs-extra';
import { chmod, realpath, writeFile } from 'fs-extra';
import { randomBytes } from 'crypto';
import pify from 'pify';
@ -1001,7 +1001,6 @@ function showSettingsWindow() {
autoHideMenuBar: true,
backgroundColor: '#3a76f0',
show: false,
modal: false,
webPreferences: {
...defaultWebPrefs,
nodeIntegration: false,
@ -1135,13 +1134,12 @@ async function showDebugLogWindow() {
autoHideMenuBar: true,
backgroundColor: '#3a76f0',
show: false,
modal: true,
webPreferences: {
...defaultWebPrefs,
nodeIntegration: false,
nodeIntegrationInWorker: false,
contextIsolation: false,
preload: join(__dirname, '../debug_log_preload.js'),
contextIsolation: true,
preload: join(__dirname, '../ts/windows/debuglog/preload.js'),
nativeWindowOpen: true,
},
parent: mainWindow,
@ -1156,13 +1154,11 @@ async function showDebugLogWindow() {
);
debugLogWindow.on('closed', () => {
removeDarkOverlay();
debugLogWindow = undefined;
});
debugLogWindow.once('ready-to-show', () => {
if (debugLogWindow) {
addDarkOverlay();
debugLogWindow.show();
}
});
@ -1834,6 +1830,17 @@ ipc.on('close-debug-log', () => {
debugLogWindow.close();
}
});
ipc.on(
'show-debug-log-save-dialog',
async (_event: Electron.Event, logText: string) => {
const { filePath } = await dialog.showSaveDialog({
defaultPath: 'debuglog.txt',
});
if (filePath) {
await writeFile(filePath, logText);
}
}
);
// Permissions Popup-related IPC calls

View File

@ -6,11 +6,9 @@
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none';
child-src 'self';
connect-src 'self' https: wss:;
font-src 'self';
form-action 'self';
frame-src 'none';
form-action 'none';
font-src 'self';
img-src 'self' blob: data:;
media-src 'self' blob:;
object-src 'none';
@ -23,40 +21,13 @@
type="text/css"
/>
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
<style></style>
</head>
<body class="debug-log-window"></body>
<script type="text/x-tmpl-mustache" id="debug-log">
<div class='content'>
<div>
<a class='x close' alt='close debug log' href='#'></a>
<h1> {{ title }} </h1>
<p> {{ debugLogExplanation }}</p>
</div>
<textarea class='textarea' spellcheck='false' rows='5'></textarea>
<div class='buttons'>
<button class='grey submit'>{{ submit }}</button>
</div>
<div class='result'>
</div>
</div>
</script>
<script type="text/x-tmpl-mustache" id="debug-log-link">
<div class='input-group clearfix'>
<input type='text' class='link' readonly value='{{ url }}' />
<a class='copy' alt='{{ debugLogCopyAlt }}' target='_blank' href='{{ url }}'>{{ debugLogCopy }}</a>
</div>
<p>
<a class='report-link' target='_blank'
href='https://support.signal.org/hc/requests/new'>
{{ reportIssue }}
</a>
</p>
</script>
<script type="text/x-tmpl-mustache" id="toast">
{{ toastMessage }}
</script>
<script type="text/javascript" src="js/components.js"></script>
<script type="text/javascript" src="ts/backboneJquery.js"></script>
<script type="text/javascript" src="js/debug_log_start.js"></script>
<body>
<div id="app"></div>
<script
type="application/javascript"
src="ts/windows/applyTheme.js"
></script>
<script type="application/javascript" src="ts/windows/init.js"></script>
</body>
</html>

View File

@ -1,41 +0,0 @@
// Copyright 2018-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global window */
const { ipcRenderer } = require('electron');
const url = require('url');
// It is important to call this as early as possible
require('./ts/windows/context');
const { setupI18n } = require('./ts/util/setupI18n');
const { createSetting } = require('./ts/util/preload');
const {
getEnvironment,
setEnvironment,
parseEnvironment,
} = require('./ts/environment');
const config = url.parse(window.location.toString(), true).query;
const { locale } = config;
const localeMessages = ipcRenderer.sendSync('locale-data');
setEnvironment(parseEnvironment(config.environment));
window.getVersion = () => config.version;
window.themeSetting = createSetting('themeSetting', { setter: false });
window.i18n = setupI18n(locale, localeMessages);
// got.js appears to need this to successfully submit debug logs to the cloud
window.nodeSetImmediate = setImmediate;
window.getNodeVersion = () => config.node_version;
window.getEnvironment = getEnvironment;
window.Backbone = require('backbone');
require('./ts/backbone/views/whisper_view');
require('./ts/logging/set_up_renderer_logging').initialize();
require('./ts/views/debug_log_view');
window.closeDebugLog = () => ipcRenderer.send('close-debug-log');
window.Backbone = require('backbone');

View File

@ -1,33 +0,0 @@
<!-- Copyright 2014-2021 Signal Messenger, LLC -->
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
<!DOCTYPE html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8" />
<meta
content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"
name="viewport"
/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Signal</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="/stylesheets/manifest.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="js/chromium.js"></script>
</head>
<body id="signal-container" class="signal index">
<div class="app-loading-screen">
<div class="content">
<div class="module-splash-screen__logo module-img--150"></div>
<div class="container">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
<div class="message"></div>
</div>
</div>
<script type="text/javascript" src="js/index.js"></script>
</body>
</html>

View File

@ -1,37 +0,0 @@
// Copyright 2018-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global $: false */
$(document).on('keydown', e => {
if (e.keyCode === 27) {
window.closeDebugLog();
}
});
const $body = $(document.body);
async function applyTheme() {
const theme = await window.themeSetting.getValue();
document.body.classList.remove('light-theme');
document.body.classList.remove('dark-theme');
document.body.classList.add(
`${
theme === 'system'
? window.SignalContext.nativeThemeListener.getSystemTheme()
: theme
}-theme`
);
}
applyTheme();
window.SignalContext.nativeThemeListener.subscribe(() => {
applyTheme();
});
// got.js appears to need this to successfully submit debug logs to the cloud
window.setImmediate = window.nodeSetImmediate;
window.view = new window.Whisper.DebugLogView();
window.view.$el.appendTo($body);

View File

@ -4,6 +4,18 @@
<!DOCTYPE html>
<html>
<head>
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none';
frame-src 'none';
form-action 'none';
font-src 'self';
img-src 'self' blob: data:;
media-src 'self' blob:;
object-src 'none';
script-src 'self';
style-src 'self' 'unsafe-inline';"
/>
<link
href="node_modules/sanitize.css/sanitize.css"
rel="stylesheet"

View File

@ -419,8 +419,6 @@
"app/*",
"preload.bundle.js",
"preload_utils.js",
"permissions_popup_preload.js",
"debug_log_preload.js",
"main.js",
"images/**",
"fonts/**",

View File

@ -6,6 +6,8 @@
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none';
frame-src 'none';
form-action 'none';
font-src 'self';
img-src 'self' blob: data:;
media-src 'self' blob:;

View File

@ -76,7 +76,6 @@ try {
}
return localBuildExpiration;
};
window.getNodeVersion = () => config.node_version;
window.getHostName = () => config.hostname;
window.getServerTrustRoot = () => config.serverTrustRoot;
window.getServerPublicParams = () => config.serverPublicParams;

View File

@ -6,6 +6,8 @@
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none';
frame-src 'none';
form-action 'none';
font-src 'self';
img-src 'self' blob: data:;
media-src 'self' blob:;

View File

@ -6,6 +6,8 @@
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none';
frame-src 'none';
form-action 'none';
font-src 'self';
img-src 'self' blob: data:;
media-src 'self' blob:;

View File

@ -1,90 +0,0 @@
// Copyright 2016-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
.debug-log {
&.modal {
padding: 50px;
.content {
margin: 0;
max-width: 100%;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
textarea {
flex-grow: 1;
width: 100%;
resize: none;
min-height: 100px;
font-family: Monaco, Consolas, 'Courier New', Courier, monospace;
font-size: 12px;
@include dark-theme {
background-color: $color-gray-90;
border: 1px solid $color-gray-45;
color: $color-gray-02;
}
}
}
}
.result {
$open-height: 36px;
text-align: center;
.input-group {
display: inline-flex;
}
.copy {
height: $open-height;
text-align: center;
line-height: $open-height;
padding: 0 30px;
color: unset;
text-decoration: none;
cursor: pointer;
border-radius: 0 5px 5px 0;
@include light-theme {
border: solid 1px $color-gray-25;
background: $color-gray-02;
&:active {
background: $color-gray-25;
}
}
@include dark-theme {
border: solid 1px $color-gray-45;
background-color: $color-gray-90;
color: $color-gray-02;
&:active {
background: $color-gray-25;
}
}
}
.link {
border-radius: 5px 0 0 5px;
flex-grow: 1;
min-width: 400px;
height: $open-height;
padding: 0 10px;
outline-offset: -4px;
@include light-theme {
border: solid 1px $color-gray-25;
border-right: none;
}
@include dark-theme {
color: $color-gray-02;
border: solid 1px $color-gray-45;
border-right: none;
background-color: $color-gray-90;
}
}
}
}

View File

@ -0,0 +1,74 @@
// Copyright 2016-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
.DebugLogWindow {
display: flex;
flex-direction: column;
height: 100%;
margin: 0;
max-width: 100%;
padding: 16px;
width: 100%;
&__container {
align-items: center;
display: flex;
flex-grow: 1;
justify-content: center;
}
&__textarea {
font-family: Monaco, Consolas, 'Courier New', Courier, monospace;
font-size: 12px;
height: 100%;
resize: none;
width: 100%;
@include dark-theme {
background-color: $color-gray-90;
border: 1px solid $color-gray-45;
color: $color-gray-02;
}
}
&__title {
@include font-title-2;
}
&__subtitle {
@include font-body-2;
@include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-25;
}
}
&__footer {
align-items: center;
display: flex;
justify-content: flex-end;
margin-top: 16px;
.module-Button {
margin-left: 8px;
}
}
&__link {
border-radius: 4px;
height: 36px;
padding: 0 10px;
width: 100%;
@include light-theme {
border: solid 1px $color-gray-25;
}
@include dark-theme {
background-color: $color-gray-90;
border: solid 1px $color-gray-45;
color: $color-gray-02;
}
}
}

View File

@ -28,7 +28,7 @@
outline: none;
}
&__footer {
&__buttons {
align-items: center;
display: flex;
flex-direction: column;

View File

@ -10,7 +10,6 @@
// Old style: components
@import 'progress';
@import 'modal';
@import 'debugLog';
@import 'recorder';
@import 'emoji';
@import 'settings';
@ -56,6 +55,7 @@
@import './components/ConversationView.scss';
@import './components/CustomColorEditor.scss';
@import './components/CustomizingPreferredReactionsModal.scss';
@import './components/DebugLogWindow.scss';
@import './components/DisappearingTimeDialog.scss';
@import './components/DisappearingTimerSelect.scss';
@import './components/EditConversationAttributesModal.scss';

View File

@ -9,7 +9,7 @@ describe('i18n', () => {
assert.strictEqual(i18n('random'), '');
});
it('returns message for given string', () => {
assert.equal(i18n('reportIssue'), ['Report an issue']);
assert.equal(i18n('reportIssue'), ['Contact Support']);
});
it('returns message with single substitution', () => {
const actual = i18n('cannotUpdateDetail', [

View File

@ -0,0 +1,32 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import enMessages from '../../_locales/en/messages.json';
import { DebugLogWindow, PropsType } from './DebugLogWindow';
import { setupI18n } from '../util/setupI18n';
import { sleep } from '../util/sleep';
const i18n = setupI18n('en', enMessages);
const createProps = (): PropsType => ({
closeWindow: action('closeWindow'),
downloadLog: action('downloadLog'),
i18n,
fetchLogs: () => {
action('fetchLogs')();
return Promise.resolve('Sample logs');
},
uploadLogs: async (logs: string) => {
action('uploadLogs')(logs);
await sleep(5000);
return 'https://picsum.photos/1800/900';
},
});
const story = storiesOf('Components/DebugLogWindow', module);
story.add('DebugLogWindow', () => <DebugLogWindow {...createProps()} />);

View File

@ -0,0 +1,207 @@
// Copyright 2015-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { MouseEvent, useEffect, useState } from 'react';
import copyText from 'copy-text-to-clipboard';
import * as log from '../logging/log';
import { Button, ButtonVariant } from './Button';
import { LocalizerType } from '../types/Util';
import { Spinner } from './Spinner';
import { ToastDebugLogError } from './ToastDebugLogError';
import { ToastLinkCopied } from './ToastLinkCopied';
import { ToastLoadingFullLogs } from './ToastLoadingFullLogs';
import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser';
import { useEscapeHandling } from '../hooks/useEscapeHandling';
enum LoadState {
NotStarted,
Started,
Loaded,
Submitting,
}
export type PropsType = {
closeWindow: () => unknown;
downloadLog: (text: string) => unknown;
i18n: LocalizerType;
fetchLogs: () => Promise<string>;
uploadLogs: (logs: string) => Promise<string>;
};
enum ToastType {
Copied,
Error,
Loading,
}
export const DebugLogWindow = ({
closeWindow,
downloadLog,
i18n,
fetchLogs,
uploadLogs,
}: PropsType): JSX.Element => {
const [loadState, setLoadState] = useState<LoadState>(LoadState.NotStarted);
const [logText, setLogText] = useState<string | undefined>();
const [publicLogURL, setPublicLogURL] = useState<string | undefined>();
const [textAreaValue, setTextAreaValue] = useState<string>(i18n('loading'));
const [toastType, setToastType] = useState<ToastType | undefined>();
useEscapeHandling(closeWindow);
useEffect(() => {
setLoadState(LoadState.Started);
let shouldCancel = false;
async function doFetchLogs() {
const fetchedLogText = await fetchLogs();
if (shouldCancel) {
return;
}
setToastType(ToastType.Loading);
setLogText(fetchedLogText);
setLoadState(LoadState.Loaded);
// This number is somewhat arbitrary; we want to show enough that it's
// clear that we need to scroll, but not so many that things get slow.
const linesToShow = Math.ceil(Math.min(window.innerHeight, 2000) / 5);
const value = fetchedLogText.split(/\n/g, linesToShow).join('\n');
setTextAreaValue(value);
setToastType(undefined);
}
doFetchLogs();
return () => {
shouldCancel = true;
};
}, [fetchLogs]);
const handleSubmit = async (ev: MouseEvent) => {
ev.preventDefault();
const text = logText;
if (!text || text.length === 0) {
return;
}
setLoadState(LoadState.Submitting);
try {
const publishedLogURL = await uploadLogs(text);
setPublicLogURL(publishedLogURL);
} catch (error) {
log.error(
'DebugLogWindow error:',
error && error.stack ? error.stack : error
);
setLoadState(LoadState.Loaded);
setToastType(ToastType.Error);
}
};
function closeToast() {
setToastType(undefined);
}
let toastElement: JSX.Element | undefined;
if (toastType === ToastType.Loading) {
toastElement = <ToastLoadingFullLogs i18n={i18n} onClose={closeToast} />;
} else if (toastType === ToastType.Copied) {
toastElement = <ToastLinkCopied i18n={i18n} onClose={closeToast} />;
} else if (toastType === ToastType.Error) {
toastElement = <ToastDebugLogError i18n={i18n} onClose={closeToast} />;
}
if (publicLogURL) {
const copyLog = (ev: MouseEvent) => {
ev.preventDefault();
copyText(publicLogURL);
setToastType(ToastType.Copied);
};
return (
<div className="DebugLogWindow">
<div>
<div className="DebugLogWindow__title">{i18n('debugLogSuccess')}</div>
<p className="DebugLogWindow__subtitle">
{i18n('debugLogSuccessNextSteps')}
</p>
</div>
<div className="DebugLogWindow__container">
<input
className="DebugLogWindow__link"
readOnly
type="text"
value={publicLogURL}
/>
</div>
<div className="DebugLogWindow__footer">
<Button
onClick={() => {
openLinkInWebBrowser(
'https://support.signal.org/hc/requests/new'
);
}}
variant={ButtonVariant.Secondary}
>
{i18n('reportIssue')}
</Button>
<Button onClick={copyLog}>{i18n('debugLogCopy')}</Button>
</div>
{toastElement}
</div>
);
}
const canSubmit = Boolean(logText) && loadState !== LoadState.Submitting;
const canSave = Boolean(logText);
const isLoading =
loadState === LoadState.Started || loadState === LoadState.Submitting;
return (
<div className="DebugLogWindow">
<div>
<div className="DebugLogWindow__title">{i18n('submitDebugLog')}</div>
<p className="DebugLogWindow__subtitle">
{i18n('debugLogExplanation')}
</p>
</div>
<div className="DebugLogWindow__container">
{isLoading ? (
<Spinner svgSize="normal" />
) : (
<textarea
className="DebugLogWindow__textarea"
readOnly
rows={5}
spellCheck={false}
value={textAreaValue}
/>
)}
</div>
<div className="DebugLogWindow__footer">
<Button
disabled={!canSave}
onClick={() => {
if (logText) {
downloadLog(logText);
}
}}
variant={ButtonVariant.Secondary}
>
{i18n('debugLogSave')}
</Button>
<Button disabled={!canSubmit} onClick={handleSubmit}>
{i18n('submit')}
</Button>
</div>
{toastElement}
</div>
);
};

View File

@ -0,0 +1,21 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { ToastDebugLogError } from './ToastDebugLogError';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const defaultProps = {
i18n,
onClose: action('onClose'),
};
const story = storiesOf('Components/ToastDebugLogError', module);
story.add('ToastDebugLogError', () => <ToastDebugLogError {...defaultProps} />);

View File

@ -0,0 +1,18 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { LocalizerType } from '../types/Util';
import { Toast } from './Toast';
type PropsType = {
i18n: LocalizerType;
onClose: () => unknown;
};
export const ToastDebugLogError = ({
i18n,
onClose,
}: PropsType): JSX.Element => {
return <Toast onClose={onClose}>{i18n('debugLogError')}</Toast>;
};

View File

@ -114,19 +114,23 @@ const headerSection = (
].join('\n');
};
const getHeader = ({
capabilities,
remoteConfig,
statistics,
user,
}: Omit<FetchLogIpcData, 'logEntries'>): string =>
const getHeader = (
{
capabilities,
remoteConfig,
statistics,
user,
}: Omit<FetchLogIpcData, 'logEntries'>,
nodeVersion: string,
appVersion: string
): string =>
[
headerSection('System info', {
Time: Date.now(),
'User agent': window.navigator.userAgent,
'Node version': window.getNodeVersion(),
'Node version': nodeVersion,
Environment: getEnvironment(),
'App version': window.getVersion(),
'App version': appVersion,
}),
headerSection('User info', user),
headerSection('Capabilities', capabilities),
@ -154,7 +158,10 @@ function formatLine(mightBeEntry: unknown): string {
return `${getLevel(entry.level)} ${entry.time} ${entry.msg}`;
}
export function fetch(): Promise<string> {
export function fetch(
nodeVersion: string,
appVersion: string
): Promise<string> {
return new Promise(resolve => {
ipc.send('fetch-log');
@ -163,7 +170,7 @@ export function fetch(): Promise<string> {
let body: string;
if (isFetchLogIpcData(data)) {
const { logEntries } = data;
header = getHeader(data);
header = getHeader(data, nodeVersion, appVersion);
body = logEntries.map(formatLine).join('\n');
} else {
header = headerSectionTitle('Partial logs');

View File

@ -106,30 +106,6 @@
"updated": "2018-09-18T19:19:27.699Z",
"reasonDetail": "Very limited in what HTML can be injected - dark/light options specify colors for the light/dark parts of QRCode"
},
{
"rule": "jQuery-$(",
"path": "js/debug_log_start.js",
"line": "$(document).on('keydown', e => {",
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/debug_log_start.js",
"line": "const $body = $(document.body);",
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-appendTo(",
"path": "js/debug_log_start.js",
"line": "window.view.$el.appendTo($body);",
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T18:13:29.628Z",
"reasonDetail": "Interacting with already-existing DOM nodes"
},
{
"rule": "jQuery-$(",
"path": "js/views/key_verification_view.js",
@ -13306,202 +13282,6 @@
"reasonCategory": "falseMatch",
"updated": "2021-09-17T21:51:57.475Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.js",
"line": " template: () => $('#debug-log-link').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.js",
"line": " template: () => $('#debug-log').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.js",
"line": " this.textarea = this.$('.textarea').get(0);",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.js",
"line": " this.$('.submit').attr('disabled', 'disabled');",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.js",
"line": " this.$('.submit').removeAttr('disabled');",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.js",
"line": " this.$('.buttons, .textarea').remove();",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.js",
"line": " this.$('.result').addClass('loading');",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.js",
"line": " el: this.$('.result'),",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.js",
"line": " this.$('.loading').removeClass('loading');",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.js",
"line": " this.$('.link').focus().select();",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.js",
"line": " this.$('.loading').removeClass('loading');",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.js",
"line": " this.$('.result').text(window.i18n('debugLogError'));",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-html(",
"path": "ts/views/debug_log_view.js",
"line": " template: () => $('#debug-log-link').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-html(",
"path": "ts/views/debug_log_view.js",
"line": " template: () => $('#debug-log').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.ts",
"line": " template: () => $('#debug-log-link').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.ts",
"line": " template: () => $('#debug-log').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.ts",
"line": " this.textarea = this.$('.textarea').get(0);",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.ts",
"line": " this.$('.submit').attr('disabled', 'disabled');",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.ts",
"line": " this.$('.submit').removeAttr('disabled');",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.ts",
"line": " this.$('.buttons, .textarea').remove();",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.ts",
"line": " this.$('.result').addClass('loading');",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.ts",
"line": " el: this.$('.result'),",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.ts",
"line": " this.$('.loading').removeClass('loading');",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.ts",
"line": " this.$('.link').focus().select();",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.ts",
"line": " this.$('.loading').removeClass('loading');",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/debug_log_view.ts",
"line": " this.$('.result').text(window.i18n('debugLogError'));",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-html(",
"path": "ts/views/debug_log_view.ts",
"line": " template: () => $('#debug-log-link').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-html(",
"path": "ts/views/debug_log_view.ts",
"line": " template: () => $('#debug-log').html(),",
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
{
"rule": "jQuery-$(",
"path": "ts/views/inbox_view.js",

View File

@ -1,192 +0,0 @@
// Copyright 2015-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import copyText from 'copy-text-to-clipboard';
import * as log from '../logging/log';
import * as debugLog from '../logging/debuglogs';
import { ToastLoadingFullLogs } from '../components/ToastLoadingFullLogs';
import { ToastLinkCopied } from '../components/ToastLinkCopied';
import { showToast } from '../util/showToast';
window.Whisper = window.Whisper || {};
const { Whisper } = window;
// This enum-like object describes the load state of `DebugLogView`. It's designed to be
// unidirectional; `NotStarted` → `Started` → `LogsFetchedButNotInTextarea`, etc.
const LoadState = {
NotStarted: 0,
Started: 1,
LogsFetchedButNotInTextarea: 2,
PuttingLogsInTextarea: 3,
LogsInTextarea: 4,
};
const DebugLogLinkView = Whisper.View.extend({
template: () => $('#debug-log-link').html(),
initialize(options: { url: string }) {
this.url = options.url;
},
events: {
'click .copy': 'copy',
},
render_attributes() {
return {
url: this.url,
reportIssue: window.i18n('reportIssue'),
debugLogCopy: window.i18n('debugLogCopy'),
debugLogCopyAlt: window.i18n('debugLogCopyAlt'),
};
},
copy(e: MouseEvent) {
e.preventDefault();
const target = e.currentTarget as HTMLAnchorElement;
copyText(target.href);
showToast(ToastLinkCopied);
},
});
/**
* The bulk of the logic in this view involves grabbing the logs from disk and putting
* them in a `<textarea>`. The first part isn't instant but is reasonably fast; setting
* the textarea's `value` takes a long time.
*
* After loading the logs into memory, we only put a small number of lines into the
* textarea. If the user clicks or scrolls the textarea, we pull the full logs, which
* can cause the system to lock up for a bit.
*
* Ideally, we'd only show a sampling of the logs and allow the user to download and
* edit them in their own editor. This is mostly a stopgap solution.
*/
export const DebugLogView = Whisper.View.extend({
template: () => $('#debug-log').html(),
className: 'debug-log modal',
initialize() {
this.render();
this.textarea = this.$('.textarea').get(0);
if (!this.textarea) {
throw new Error('textarea not found');
}
this.textarea.setAttribute('readonly', '');
this.loadState = LoadState.NotStarted;
this.putFullLogsInTextareaPlease = false;
this.fetchLogs();
},
events: {
'click .textarea': 'putFullLogsInTextarea',
'scroll .textarea': 'putFullLogsInTextarea',
'wheel .textarea': 'putFullLogsInTextarea',
'click .submit': 'submit',
'click .close': 'close',
},
render_attributes: {
title: window.i18n('submitDebugLog'),
cancel: window.i18n('cancel'),
submit: window.i18n('submit'),
close: window.i18n('gotIt'),
debugLogExplanation: window.i18n('debugLogExplanation'),
},
async fetchLogs() {
if (this.loadState !== LoadState.NotStarted) {
return;
}
this.loadState = LoadState.Started;
this.textarea.value = window.i18n('loading');
this.$('.submit').attr('disabled', 'disabled');
this.logText = await debugLog.fetch();
this.loadState = LoadState.LogsFetchedButNotInTextarea;
// This number is somewhat arbitrary; we want to show enough that it's clear that
// we need to scroll, but not so many that things get slow.
const linesToShow = Math.ceil(Math.min(window.innerHeight, 2000) / 5);
this.textarea.value = this.logText
.split(/\n/g, linesToShow)
.concat(['', window.i18n('loading')])
.join('\n');
this.$('.submit').removeAttr('disabled');
if (this.putFullLogsInTextareaPlease) {
this.putFullLogsInTextarea();
}
},
putFullLogsInTextarea() {
switch (this.loadState) {
case LoadState.NotStarted:
case LoadState.Started:
this.putFullLogsInTextareaPlease = true;
break;
case LoadState.LogsInTextarea:
case LoadState.PuttingLogsInTextarea:
break;
case LoadState.LogsFetchedButNotInTextarea:
if (!this.logText) {
throw new Error('Expected log text to be present');
}
this.loadState = LoadState.PuttingLogsInTextarea;
showToast(ToastLoadingFullLogs);
setTimeout(() => {
this.textarea.value = this.logText;
this.textarea.removeAttribute('readonly');
this.loadState = LoadState.LogsInTextarea;
}, 0);
break;
default:
// When we can, we should make this throw a `missingCaseError`.
break;
}
},
close() {
window.closeDebugLog();
},
async submit(e: SubmitEvent) {
e.preventDefault();
let text;
switch (this.loadState) {
case LoadState.NotStarted:
case LoadState.Started:
return;
case LoadState.LogsFetchedButNotInTextarea:
text = this.logText;
break;
case LoadState.LogsInTextarea:
text = this.textarea.value;
break;
default:
// When we can, we should make this throw a `missingCaseError`.
return;
}
if (text.length === 0) {
return;
}
this.$('.buttons, .textarea').remove();
this.$('.result').addClass('loading');
try {
const publishedLogURL = await debugLog.upload(text, window.getVersion());
const view = new DebugLogLinkView({
url: publishedLogURL,
el: this.$('.result'),
});
this.$('.loading').removeClass('loading');
view.render();
this.$('.link').focus().select();
} catch (error) {
log.error(
'DebugLogView error:',
error && error.stack ? error.stack : error
);
this.$('.loading').removeClass('loading');
this.$('.result').text(window.i18n('debugLogError'));
}
},
});
window.Whisper.DebugLogView = DebugLogView;

5
ts/window.d.ts vendored
View File

@ -111,7 +111,6 @@ import { QualifiedAddress } from './types/QualifiedAddress';
import { CI } from './CI';
import { IPCEventsType, IPCEventsValuesType } from './util/createIPCEvents';
import { ConversationView } from './views/conversation_view';
import { DebugLogView } from './views/debug_log_view';
import { LoggerType } from './types/Logging';
import { SettingType } from './util/preload';
@ -162,7 +161,6 @@ declare global {
startApp: () => void;
QRCode: any;
closeDebugLog: () => unknown;
removeSetupMenuItems: () => unknown;
showPermissionsPopup: () => unknown;
@ -209,7 +207,6 @@ declare global {
getLocale: () => ElectronLocaleType;
getMediaCameraPermissions: () => Promise<boolean>;
getMediaPermissions: () => Promise<boolean>;
getNodeVersion: () => string;
getServerPublicParams: () => string;
getSfuUrl: () => string;
getSocketStatus: () => SocketStatus;
@ -499,6 +496,7 @@ declare global {
context: SignalContext;
getAppInstance: () => string | undefined;
getEnvironment: () => string;
getNodeVersion: () => string;
getVersion: () => string;
i18n: LocalizerType;
log: LoggerType;
@ -567,7 +565,6 @@ export class BasicReactWrapperViewClass extends AnyViewClass {
export type WhisperType = {
Conversation: typeof ConversationModel;
ConversationCollection: typeof ConversationModelCollectionType;
DebugLogView: typeof DebugLogView;
Message: typeof MessageModel;
MessageCollection: typeof MessageModelCollectionType;

View File

@ -37,6 +37,7 @@ export const SignalWindow = {
getAppInstance: (): string | undefined =>
config.appInstance ? String(config.appInstance) : undefined,
getEnvironment,
getNodeVersion: (): string => String(config.node_version),
getVersion: (): string => String(config.version),
i18n: setupI18n(locale, localeMessages),
log: window.SignalWindow.log,

View File

@ -0,0 +1,44 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import ReactDOM from 'react-dom';
import { contextBridge, ipcRenderer } from 'electron';
// It is important to call this as early as possible
import '../context';
import { SignalWindow } from '../configure';
import { DebugLogWindow } from '../../components/DebugLogWindow';
import * as debugLog from '../../logging/debuglogs';
contextBridge.exposeInMainWorld('SignalWindow', {
...SignalWindow,
renderWindow: () => {
const environmentText: Array<string> = [SignalWindow.getEnvironment()];
const appInstance = SignalWindow.getAppInstance();
if (appInstance) {
environmentText.push(appInstance);
}
ReactDOM.render(
React.createElement(DebugLogWindow, {
closeWindow: () => ipcRenderer.send('close-debug-log'),
downloadLog: (logText: string) =>
ipcRenderer.send('show-debug-log-save-dialog', logText),
i18n: SignalWindow.i18n,
fetchLogs() {
return debugLog.fetch(
SignalWindow.getNodeVersion(),
SignalWindow.getVersion()
);
},
uploadLogs(logs: string) {
return debugLog.upload(logs, SignalWindow.getVersion());
},
}),
document.getElementById('app')
);
},
});