Context isolation for the debug log window
This commit is contained in:
parent
0f9608d9a3
commit
fa66ddde0f
28 changed files with 477 additions and 686 deletions
|
@ -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": {
|
||||
|
|
|
@ -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:;
|
||||
|
|
21
app/main.ts
21
app/main.ts
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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');
|
33
index.html
33
index.html
|
@ -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>
|
|
@ -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);
|
12
loading.html
12
loading.html
|
@ -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"
|
||||
|
|
|
@ -419,8 +419,6 @@
|
|||
"app/*",
|
||||
"preload.bundle.js",
|
||||
"preload_utils.js",
|
||||
"permissions_popup_preload.js",
|
||||
"debug_log_preload.js",
|
||||
"main.js",
|
||||
"images/**",
|
||||
"fonts/**",
|
||||
|
|
|
@ -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:;
|
||||
|
|
|
@ -76,7 +76,6 @@ try {
|
|||
}
|
||||
return localBuildExpiration;
|
||||
};
|
||||
window.getNodeVersion = () => config.node_version;
|
||||
window.getHostName = () => config.hostname;
|
||||
window.getServerTrustRoot = () => config.serverTrustRoot;
|
||||
window.getServerPublicParams = () => config.serverPublicParams;
|
||||
|
|
|
@ -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:;
|
||||
|
|
|
@ -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:;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
74
stylesheets/components/DebugLogWindow.scss
Normal file
74
stylesheets/components/DebugLogWindow.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@
|
|||
outline: none;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
&__buttons {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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', [
|
||||
|
|
32
ts/components/DebugLogWindow.stories.tsx
Normal file
32
ts/components/DebugLogWindow.stories.tsx
Normal 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()} />);
|
207
ts/components/DebugLogWindow.tsx
Normal file
207
ts/components/DebugLogWindow.tsx
Normal 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>
|
||||
);
|
||||
};
|
21
ts/components/ToastDebugLogError.stories.tsx
Normal file
21
ts/components/ToastDebugLogError.stories.tsx
Normal 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} />);
|
18
ts/components/ToastDebugLogError.tsx
Normal file
18
ts/components/ToastDebugLogError.tsx
Normal 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>;
|
||||
};
|
|
@ -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');
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
5
ts/window.d.ts
vendored
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
44
ts/windows/debuglog/preload.ts
Normal file
44
ts/windows/debuglog/preload.ts
Normal 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')
|
||||
);
|
||||
},
|
||||
});
|
Loading…
Add table
Reference in a new issue