mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-02-15 07:28:59 +00:00
Add diagnostic information window to Call Quality Survey
This commit is contained in:
@@ -10122,6 +10122,10 @@
|
||||
"messageformat": "This helps us learn more about calls and what is working or not working. You can view your debug log before submitting.",
|
||||
"description": "Call Quality Survey Dialog > Help us improve > Page Description"
|
||||
},
|
||||
"icu:CallQualitySurvey__ConfirmSubmission__PageDescriptionWithDiagnosticLink": {
|
||||
"messageformat": "Submitting will share your feedback along with <diagnosticInfoLink>diagnostic information about your call</diagnosticInfoLink>. You can optionally share a debug log to help us improve call quality.",
|
||||
"description": "Call Quality Survey Dialog > Help us improve > Page Description with link to view diagnostic info"
|
||||
},
|
||||
"icu:CallQualitySurvey__ConfirmSubmission__ShareDebugLog__Label": {
|
||||
"messageformat": "Share debug log",
|
||||
"description": "Call Quality Survey Dialog > Help us improve > Share debug log > Label"
|
||||
@@ -10134,6 +10138,14 @@
|
||||
"messageformat": "Debug logs contain low level app information and do not reveal any of your message contents.",
|
||||
"description": "Call Quality Survey Dialog > Help us improve > Share debug log > Help Text"
|
||||
},
|
||||
"icu:CallQualitySurvey__ConfirmSubmission__PrivacyNote": {
|
||||
"messageformat": "Information shared with us contains low level app information and does not include the contents of your calls.",
|
||||
"description": "Call Quality Survey Dialog > Help us improve > Privacy note about what information is shared"
|
||||
},
|
||||
"icu:CallDiagnosticWindow__title": {
|
||||
"messageformat": "Diagnostic information",
|
||||
"description": "Title of the call diagnostic information window"
|
||||
},
|
||||
"icu:CallQualitySurvey__ConfirmSubmission__SubmitButton": {
|
||||
"messageformat": "Submit",
|
||||
"description": "Call Quality Survey Dialog > Help us improve > Submit Button"
|
||||
|
||||
107
app/main.main.ts
107
app/main.main.ts
@@ -1401,16 +1401,28 @@ async function openArtCreator() {
|
||||
}
|
||||
|
||||
let debugLogWindow: BrowserWindow | undefined;
|
||||
let debugLogCurrentMode: 'submit' | 'close' | undefined;
|
||||
type DebugLogWindowOptions = {
|
||||
mode?: 'submit' | 'close';
|
||||
};
|
||||
|
||||
async function showDebugLogWindow(options: DebugLogWindowOptions = {}) {
|
||||
const newMode = options.mode ?? 'submit';
|
||||
|
||||
if (debugLogWindow) {
|
||||
if (debugLogCurrentMode !== newMode) {
|
||||
debugLogCurrentMode = newMode;
|
||||
const url = pathToFileURL(join(__dirname, '../debug_log.html'));
|
||||
url.searchParams.set('mode', newMode);
|
||||
await safeLoadURL(debugLogWindow, url.href);
|
||||
}
|
||||
|
||||
doShowDebugLogWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
debugLogCurrentMode = newMode;
|
||||
|
||||
function doShowDebugLogWindow() {
|
||||
if (debugLogWindow) {
|
||||
// Electron has [a macOS bug][0] that causes parent windows to become unresponsive
|
||||
@@ -1452,6 +1464,7 @@ async function showDebugLogWindow(options: DebugLogWindowOptions = {}) {
|
||||
|
||||
debugLogWindow.on('closed', () => {
|
||||
debugLogWindow = undefined;
|
||||
debugLogCurrentMode = undefined;
|
||||
});
|
||||
|
||||
debugLogWindow.once('ready-to-show', () => {
|
||||
@@ -1471,6 +1484,71 @@ async function showDebugLogWindow(options: DebugLogWindowOptions = {}) {
|
||||
await safeLoadURL(debugLogWindow, url.href);
|
||||
}
|
||||
|
||||
let callDiagnosticWindow: BrowserWindow | undefined;
|
||||
let storedCallDiagnosticData: string | undefined;
|
||||
|
||||
async function showCallDiagnosticWindow() {
|
||||
if (callDiagnosticWindow) {
|
||||
doShowCallDiagnosticWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
function doShowCallDiagnosticWindow() {
|
||||
if (callDiagnosticWindow) {
|
||||
// Electron has [a macOS bug][0] that causes parent windows to become unresponsive
|
||||
// if it's fullscreen and opens a fullscreen child window. Until that's fixed, we
|
||||
// only set the parent on MacOS is if the mainWindow is not fullscreen
|
||||
// [0]: https://github.com/electron/electron/issues/32374
|
||||
if (OS.isMacOS() && mainWindow?.isFullScreen()) {
|
||||
callDiagnosticWindow.setParentWindow(null);
|
||||
} else {
|
||||
callDiagnosticWindow.setParentWindow(mainWindow ?? null);
|
||||
}
|
||||
callDiagnosticWindow.show();
|
||||
}
|
||||
}
|
||||
|
||||
const windowOptions: Electron.BrowserWindowConstructorOptions = {
|
||||
width: 700,
|
||||
height: 500,
|
||||
resizable: false,
|
||||
title: getResolvedMessagesLocale().i18n('icu:CallDiagnosticWindow__title'),
|
||||
titleBarStyle: nonMainTitleBarStyle,
|
||||
autoHideMenuBar: true,
|
||||
backgroundColor: await getBackgroundColor(),
|
||||
show: false,
|
||||
webPreferences: {
|
||||
...defaultWebPrefs,
|
||||
nodeIntegration: false,
|
||||
nodeIntegrationInWorker: false,
|
||||
sandbox: true,
|
||||
contextIsolation: true,
|
||||
preload: join(__dirname, '../bundles/calldiagnostic/preload.preload.js'),
|
||||
},
|
||||
parent: mainWindow,
|
||||
};
|
||||
|
||||
callDiagnosticWindow = new BrowserWindow(windowOptions);
|
||||
|
||||
await handleCommonWindowEvents(callDiagnosticWindow);
|
||||
|
||||
callDiagnosticWindow.on('closed', () => {
|
||||
callDiagnosticWindow = undefined;
|
||||
});
|
||||
|
||||
callDiagnosticWindow.once('ready-to-show', () => {
|
||||
if (callDiagnosticWindow) {
|
||||
doShowCallDiagnosticWindow();
|
||||
|
||||
// Electron sometimes puts the window in a strange spot until it's shown.
|
||||
callDiagnosticWindow.center();
|
||||
}
|
||||
});
|
||||
|
||||
const url = pathToFileURL(join(__dirname, '../call_diagnostic.html'));
|
||||
await safeLoadURL(callDiagnosticWindow, url.href);
|
||||
}
|
||||
|
||||
let permissionsPopupWindow: BrowserWindow | undefined;
|
||||
function showPermissionsPopupWindow(forCalling: boolean, forCamera: boolean) {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
@@ -2686,6 +2764,35 @@ ipc.on(
|
||||
}
|
||||
);
|
||||
|
||||
// Call Diagnostic Window-related IPC calls
|
||||
|
||||
ipc.on('show-call-diagnostic', () => {
|
||||
void showCallDiagnosticWindow();
|
||||
});
|
||||
|
||||
ipc.handle('get-call-diagnostic-data', () => {
|
||||
return storedCallDiagnosticData ?? '';
|
||||
});
|
||||
|
||||
ipc.on('close-call-diagnostic', () => {
|
||||
storedCallDiagnosticData = undefined;
|
||||
callDiagnosticWindow?.close();
|
||||
});
|
||||
|
||||
ipc.on('close-debug-log', () => {
|
||||
if (debugLogCurrentMode === 'close') {
|
||||
debugLogWindow?.close();
|
||||
}
|
||||
});
|
||||
|
||||
ipc.on('update-call-diagnostic-data', (_event, diagnosticData: string) => {
|
||||
storedCallDiagnosticData = diagnosticData;
|
||||
|
||||
if (callDiagnosticWindow && !callDiagnosticWindow.isDestroyed()) {
|
||||
callDiagnosticWindow.webContents.send('call-diagnostic-data-updated');
|
||||
}
|
||||
});
|
||||
|
||||
// Permissions Popup-related IPC calls
|
||||
|
||||
ipc.handle(
|
||||
|
||||
25
call_diagnostic.html
Normal file
25
call_diagnostic.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<!-- Copyright 2026 Signal Messenger, LLC -->
|
||||
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
||||
|
||||
<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="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
||||
<link href="stylesheets/tailwind.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="bundles/calldiagnostic/app.dom.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -178,6 +178,7 @@ async function sandboxedEnv() {
|
||||
mainFields: ['browser', 'main'],
|
||||
entryPoints: [
|
||||
path.join(ROOT_DIR, 'ts', 'windows', 'about', 'app.dom.tsx'),
|
||||
path.join(ROOT_DIR, 'ts', 'windows', 'calldiagnostic', 'app.dom.tsx'),
|
||||
path.join(ROOT_DIR, 'ts', 'windows', 'debuglog', 'app.dom.tsx'),
|
||||
path.join(ROOT_DIR, 'ts', 'windows', 'loading', 'start.dom.ts'),
|
||||
path.join(ROOT_DIR, 'ts', 'windows', 'permissions', 'app.dom.tsx'),
|
||||
@@ -189,6 +190,13 @@ async function sandboxedEnv() {
|
||||
mainFields: ['browser', 'main'],
|
||||
entryPoints: [
|
||||
path.join(ROOT_DIR, 'ts', 'windows', 'about', 'preload.preload.ts'),
|
||||
path.join(
|
||||
ROOT_DIR,
|
||||
'ts',
|
||||
'windows',
|
||||
'calldiagnostic',
|
||||
'preload.preload.ts'
|
||||
),
|
||||
path.join(ROOT_DIR, 'ts', 'windows', 'debuglog', 'preload.preload.ts'),
|
||||
path.join(ROOT_DIR, 'ts', 'windows', 'loading', 'preload.preload.ts'),
|
||||
path.join(
|
||||
|
||||
70
ts/components/CallDiagnosticWindow.dom.tsx
Normal file
70
ts/components/CallDiagnosticWindow.dom.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import type { LocalizerType } from '../types/Util.std.js';
|
||||
import { tw } from '../axo/tw.dom.js';
|
||||
import { AxoButton } from '../axo/AxoButton.dom.js';
|
||||
import { useEscapeHandling } from '../hooks/useEscapeHandling.dom.js';
|
||||
|
||||
export type PropsType = {
|
||||
closeWindow: () => unknown;
|
||||
i18n: LocalizerType;
|
||||
diagnosticData: string;
|
||||
};
|
||||
|
||||
export function CallDiagnosticWindow({
|
||||
closeWindow,
|
||||
i18n,
|
||||
diagnosticData,
|
||||
}: PropsType): React.JSX.Element {
|
||||
useEscapeHandling(closeWindow);
|
||||
|
||||
const formattedData = useMemo(() => {
|
||||
try {
|
||||
const parsed = JSON.parse(diagnosticData);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { rawStats, rawStatsText, ...rest } = parsed;
|
||||
const pretty = {
|
||||
...rest,
|
||||
rawStats: JSON.parse(rawStatsText),
|
||||
};
|
||||
return JSON.stringify(pretty, null, 2);
|
||||
} catch {
|
||||
return diagnosticData;
|
||||
}
|
||||
}, [diagnosticData]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={tw(
|
||||
'flex h-screen flex-col bg-background-primary p-4 text-label-primary'
|
||||
)}
|
||||
>
|
||||
<div className={tw('mb-4')}>
|
||||
<h1 className={tw('type-title-medium font-semibold')}>
|
||||
{i18n('icu:CallDiagnosticWindow__title')}
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
className={tw(
|
||||
'min-h-0 flex-1 overflow-auto border border-border-secondary bg-background-secondary p-4'
|
||||
)}
|
||||
>
|
||||
<pre
|
||||
className={tw(
|
||||
'font-mono type-body-small whitespace-pre-wrap text-label-primary'
|
||||
)}
|
||||
>
|
||||
{formattedData}
|
||||
</pre>
|
||||
</div>
|
||||
<div className={tw('mt-4 flex justify-end')}>
|
||||
<AxoButton.Root onClick={closeWindow} variant="primary" size="md">
|
||||
{i18n('icu:close')}
|
||||
</AxoButton.Root>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -19,6 +19,8 @@ export function Default(): React.JSX.Element {
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
onSubmit={action('onSubmit')}
|
||||
onViewDebugLog={action('onViewDebugLog')}
|
||||
onViewDiagnosticInfo={action('onViewDiagnosticInfo')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,9 +11,28 @@ import { missingCaseError } from '../util/missingCaseError.std.js';
|
||||
import { AxoCheckbox } from '../axo/AxoCheckbox.dom.js';
|
||||
import { strictAssert } from '../util/assert.std.js';
|
||||
import { Tooltip, TooltipPlacement } from './Tooltip.dom.js';
|
||||
import { I18n } from './I18n.dom.js';
|
||||
|
||||
import Issue = CallQualitySurvey.Issue;
|
||||
|
||||
function DiagnosticInfoLink({
|
||||
parts,
|
||||
onClick,
|
||||
}: {
|
||||
parts: Array<string | React.JSX.Element>;
|
||||
onClick: () => void;
|
||||
}): React.JSX.Element {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={tw('text-color-label-primary hover:underline')}
|
||||
onClick={onClick}
|
||||
>
|
||||
{parts}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
enum Page {
|
||||
HOW_WAS_YOUR_CALL,
|
||||
WHAT_ISSUES_DID_YOU_HAVE,
|
||||
@@ -25,14 +44,16 @@ export type CallQualitySurveyDialogProps = Readonly<{
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSubmit: (form: CallQualitySurvey.Form) => void;
|
||||
onViewDebugLog?: () => void;
|
||||
onViewDebugLog: () => void;
|
||||
onViewDiagnosticInfo: () => void;
|
||||
isSubmitting?: boolean;
|
||||
}>;
|
||||
|
||||
export function CallQualitySurveyDialog(
|
||||
props: CallQualitySurveyDialogProps
|
||||
): React.JSX.Element {
|
||||
const { i18n, onSubmit, onViewDebugLog, isSubmitting } = props;
|
||||
const { i18n, onSubmit, onViewDebugLog, onViewDiagnosticInfo, isSubmitting } =
|
||||
props;
|
||||
|
||||
const [page, setPage] = useState(Page.HOW_WAS_YOUR_CALL);
|
||||
const [userSatisfied, setUserSatisfied] = useState<boolean | null>(null);
|
||||
@@ -79,6 +100,20 @@ export function CallQualitySurveyDialog(
|
||||
shareDebugLog,
|
||||
]);
|
||||
|
||||
const renderDiagnosticInfoLink = useCallback(
|
||||
(parts: Array<string | React.JSX.Element>) => (
|
||||
<DiagnosticInfoLink parts={parts} onClick={onViewDiagnosticInfo} />
|
||||
),
|
||||
[onViewDiagnosticInfo]
|
||||
);
|
||||
|
||||
const diagnosticInfoLinkComponents = useMemo(
|
||||
() => ({
|
||||
diagnosticInfoLink: renderDiagnosticInfoLink,
|
||||
}),
|
||||
[renderDiagnosticInfoLink]
|
||||
);
|
||||
|
||||
return (
|
||||
<AxoDialog.Root open={props.open} onOpenChange={props.onOpenChange}>
|
||||
<AxoDialog.Content escape="cancel-is-destructive" size="md">
|
||||
@@ -291,9 +326,11 @@ export function CallQualitySurveyDialog(
|
||||
<AxoDialog.Body>
|
||||
<p className={tw('mb-3 type-body-medium text-label-primary')}>
|
||||
<AxoDialog.Description>
|
||||
{i18n(
|
||||
'icu:CallQualitySurvey__ConfirmSubmission__PageDescription'
|
||||
)}
|
||||
<I18n
|
||||
i18n={i18n}
|
||||
id="icu:CallQualitySurvey__ConfirmSubmission__PageDescriptionWithDiagnosticLink"
|
||||
components={diagnosticInfoLinkComponents}
|
||||
/>
|
||||
</AxoDialog.Description>
|
||||
</p>
|
||||
<div className={tw('my-1.5 flex items-center gap-3')}>
|
||||
@@ -314,9 +351,7 @@ export function CallQualitySurveyDialog(
|
||||
<AxoButton.Root
|
||||
variant="subtle-primary"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
onViewDebugLog?.();
|
||||
}}
|
||||
onClick={onViewDebugLog}
|
||||
>
|
||||
{i18n(
|
||||
'icu:CallQualitySurvey__ConfirmSubmission__ShareDebugLog__ViewButton'
|
||||
@@ -324,9 +359,7 @@ export function CallQualitySurveyDialog(
|
||||
</AxoButton.Root>
|
||||
</div>
|
||||
<p className={tw('mt-3 type-body-small text-label-secondary')}>
|
||||
{i18n(
|
||||
'icu:CallQualitySurvey__ConfirmSubmission__ShareDebugLog__HelpText'
|
||||
)}
|
||||
{i18n('icu:CallQualitySurvey__ConfirmSubmission__PrivacyNote')}
|
||||
</p>
|
||||
</AxoDialog.Body>
|
||||
<AxoDialog.Footer>
|
||||
|
||||
@@ -103,10 +103,10 @@ import type {
|
||||
ToggleConfirmLeaveCallModalActionType,
|
||||
} from './globalModals.preload.js';
|
||||
import {
|
||||
HIDE_CALL_QUALITY_SURVEY,
|
||||
SHOW_CALL_QUALITY_SURVEY,
|
||||
SHOW_ERROR_MODAL,
|
||||
toggleConfirmLeaveCallModal,
|
||||
hideCallQualitySurvey,
|
||||
} from './globalModals.preload.js';
|
||||
import { CallQualitySurvey } from '../../types/CallQualitySurvey.std.js';
|
||||
import { isCallFailure } from '../../util/callQualitySurvey.dom.js';
|
||||
@@ -2935,6 +2935,9 @@ function showCallQualitySurvey(
|
||||
return dispatch => {
|
||||
dispatch({ type: RESET_CQS_SUBMISSION_STATE });
|
||||
dispatch({ type: SHOW_CALL_QUALITY_SURVEY, payload });
|
||||
|
||||
const diagnosticData = JSON.stringify(payload.callSummary);
|
||||
window.IPC.updateCallDiagnosticData(diagnosticData);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3031,7 +3034,7 @@ function submitCallQualitySurvey(
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
dispatch({ type: HIDE_CALL_QUALITY_SURVEY });
|
||||
dispatch(hideCallQualitySurvey());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -657,9 +657,16 @@ function showCallQualitySurvey(
|
||||
};
|
||||
}
|
||||
|
||||
function hideCallQualitySurvey(): HideCallQualitySurveyActionType {
|
||||
return {
|
||||
type: HIDE_CALL_QUALITY_SURVEY,
|
||||
export function hideCallQualitySurvey(): ThunkAction<
|
||||
void,
|
||||
RootStateType,
|
||||
unknown,
|
||||
HideCallQualitySurveyActionType
|
||||
> {
|
||||
return dispatch => {
|
||||
window.IPC.closeDebugLog();
|
||||
window.IPC.closeCallDiagnostic();
|
||||
dispatch({ type: HIDE_CALL_QUALITY_SURVEY });
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,10 @@ export const SmartCallQualitySurveyDialog = memo(
|
||||
[submitCallQualitySurvey, callSummary, callType]
|
||||
);
|
||||
|
||||
const handleViewDiagnosticInfo = useCallback(() => {
|
||||
window.IPC.showCallDiagnostic();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<CallQualitySurveyDialog
|
||||
i18n={i18n}
|
||||
@@ -59,6 +63,7 @@ export const SmartCallQualitySurveyDialog = memo(
|
||||
onOpenChange={handleOpenChange}
|
||||
onSubmit={handleSubmit}
|
||||
onViewDebugLog={() => window.IPC.showDebugLog({ mode: 'close' })}
|
||||
onViewDiagnosticInfo={handleViewDiagnosticInfo}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
);
|
||||
|
||||
10
ts/window.d.ts
vendored
10
ts/window.d.ts
vendored
@@ -55,6 +55,10 @@ export type IPCType = {
|
||||
setMediaCameraPermissions: (value: boolean) => Promise<void>;
|
||||
setMenuBarVisibility: (value: boolean) => void;
|
||||
showDebugLog: (options?: { mode?: 'submit' | 'close' }) => void;
|
||||
showCallDiagnostic: () => void;
|
||||
closeCallDiagnostic: () => void;
|
||||
closeDebugLog: () => void;
|
||||
updateCallDiagnosticData: (data: string) => void;
|
||||
showPermissionsPopup: (
|
||||
forCalling: boolean,
|
||||
forCamera: boolean
|
||||
@@ -91,6 +95,11 @@ type DebugLogWindowPropsType = {
|
||||
mode: 'submit' | 'close';
|
||||
};
|
||||
|
||||
type CallDiagnosticWindowPropsType = {
|
||||
subscribe: (listener: () => void) => () => void;
|
||||
getSnapshot: () => string | null;
|
||||
};
|
||||
|
||||
type PermissionsWindowPropsType = {
|
||||
forCamera: boolean;
|
||||
forCalling: boolean;
|
||||
@@ -113,6 +122,7 @@ type SettingsWindowPropsType = {
|
||||
|
||||
export type SignalCoreType = {
|
||||
AboutWindowProps?: AboutWindowPropsType;
|
||||
CallDiagnosticWindowProps?: CallDiagnosticWindowPropsType;
|
||||
DebugLogWindowProps?: DebugLogWindowPropsType;
|
||||
PermissionsWindowProps?: PermissionsWindowPropsType;
|
||||
ScreenShareWindowProps?: ScreenShareWindowPropsType;
|
||||
|
||||
46
ts/windows/calldiagnostic/app.dom.tsx
Normal file
46
ts/windows/calldiagnostic/app.dom.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { StrictMode, useSyncExternalStore } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import '../sandboxedInit.dom.js';
|
||||
import { CallDiagnosticWindow } from '../../components/CallDiagnosticWindow.dom.js';
|
||||
import { FunDefaultEnglishEmojiLocalizationProvider } from '../../components/fun/FunEmojiLocalizationProvider.dom.js';
|
||||
import { strictAssert } from '../../util/assert.std.js';
|
||||
import { AxoProvider } from '../../axo/AxoProvider.dom.js';
|
||||
|
||||
const { CallDiagnosticWindowProps } = window.Signal;
|
||||
strictAssert(CallDiagnosticWindowProps, 'window values not provided');
|
||||
const { subscribe, getSnapshot } = CallDiagnosticWindowProps;
|
||||
const { i18n } = window.SignalContext;
|
||||
|
||||
function App(): React.JSX.Element | null {
|
||||
const diagnosticData = useSyncExternalStore(subscribe, getSnapshot);
|
||||
|
||||
if (diagnosticData == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<CallDiagnosticWindow
|
||||
closeWindow={() => window.SignalContext.executeMenuRole('close')}
|
||||
i18n={i18n}
|
||||
diagnosticData={diagnosticData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const app = document.getElementById('app');
|
||||
strictAssert(app != null, 'No #app');
|
||||
|
||||
createRoot(app).render(
|
||||
<StrictMode>
|
||||
<AxoProvider
|
||||
dir={window.SignalContext.getResolvedMessagesLocaleDirection()}
|
||||
>
|
||||
<FunDefaultEnglishEmojiLocalizationProvider>
|
||||
<App />
|
||||
</FunDefaultEnglishEmojiLocalizationProvider>
|
||||
</AxoProvider>
|
||||
</StrictMode>
|
||||
);
|
||||
38
ts/windows/calldiagnostic/preload.preload.ts
Normal file
38
ts/windows/calldiagnostic/preload.preload.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { contextBridge, ipcRenderer } from 'electron';
|
||||
import { MinimalSignalContext } from '../minimalContext.preload.js';
|
||||
|
||||
// External store for useSyncExternalStore
|
||||
let currentData: string | null = null;
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
async function fetchData(): Promise<void> {
|
||||
currentData = await ipcRenderer.invoke('get-call-diagnostic-data');
|
||||
listeners.forEach(listener => listener());
|
||||
}
|
||||
|
||||
ipcRenderer.on('call-diagnostic-data-updated', () => {
|
||||
void fetchData();
|
||||
});
|
||||
|
||||
function subscribe(listener: () => void): () => void {
|
||||
listeners.add(listener);
|
||||
return () => listeners.delete(listener);
|
||||
}
|
||||
|
||||
function getSnapshot(): string | null {
|
||||
return currentData;
|
||||
}
|
||||
|
||||
void fetchData();
|
||||
|
||||
const Signal = {
|
||||
CallDiagnosticWindowProps: {
|
||||
subscribe,
|
||||
getSnapshot,
|
||||
},
|
||||
};
|
||||
contextBridge.exposeInMainWorld('Signal', Signal);
|
||||
contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext);
|
||||
@@ -133,6 +133,19 @@ const IPC: IPCType = {
|
||||
log.info('showDebugLog', options);
|
||||
ipc.send('show-debug-log', options);
|
||||
},
|
||||
showCallDiagnostic: () => {
|
||||
log.info('showCallDiagnostic');
|
||||
ipc.send('show-call-diagnostic');
|
||||
},
|
||||
closeCallDiagnostic: () => {
|
||||
ipc.send('close-call-diagnostic');
|
||||
},
|
||||
closeDebugLog: () => {
|
||||
ipc.send('close-debug-log');
|
||||
},
|
||||
updateCallDiagnosticData: (data: string) => {
|
||||
ipc.send('update-call-diagnostic-data', data);
|
||||
},
|
||||
showPermissionsPopup: (forCalling, forCamera) =>
|
||||
ipc.invoke('show-permissions-popup', forCalling, forCamera),
|
||||
setMediaPermissions: (value: boolean) =>
|
||||
|
||||
Reference in New Issue
Block a user