// Copyright 2015 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { MouseEvent } from 'react'; import React, { useEffect, useState } from 'react'; import copyText from 'copy-text-to-clipboard'; import * as log from '../logging/log'; import { Button, ButtonVariant } from './Button'; import type { LocalizerType } from '../types/Util'; import { Spinner } from './Spinner'; import { ToastDebugLogError } from './ToastDebugLogError'; import { ToastLinkCopied } from './ToastLinkCopied'; import { TitleBarContainer } from './TitleBarContainer'; import type { ExecuteMenuRoleType } from './TitleBarContainer'; import { ToastLoadingFullLogs } from './ToastLoadingFullLogs'; import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser'; import { createSupportUrl } from '../util/createSupportUrl'; import * as Errors from '../types/errors'; import { useEscapeHandling } from '../hooks/useEscapeHandling'; import { useTheme } from '../hooks/useTheme'; enum LoadState { NotStarted, Started, Loaded, Submitting, } export type PropsType = { closeWindow: () => unknown; downloadLog: (text: string) => unknown; i18n: LocalizerType; fetchLogs: () => Promise; uploadLogs: (logs: string) => Promise; hasCustomTitleBar: boolean; executeMenuRole: ExecuteMenuRoleType; }; enum ToastType { Copied, Error, Loading, } export function DebugLogWindow({ closeWindow, downloadLog, i18n, fetchLogs, uploadLogs, hasCustomTitleBar, executeMenuRole, }: PropsType): JSX.Element { const [loadState, setLoadState] = useState(LoadState.NotStarted); const [logText, setLogText] = useState(); const [publicLogURL, setPublicLogURL] = useState(); const [textAreaValue, setTextAreaValue] = useState(i18n('loading')); const [toastType, setToastType] = useState(); const theme = useTheme(); 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}\n\n\n${i18n('debugLogLogIsIncomplete')}`); setToastType(undefined); } void doFetchLogs(); return () => { shouldCancel = true; }; }, [fetchLogs, i18n]); 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:', Errors.toLogFormat(error)); setLoadState(LoadState.Loaded); setToastType(ToastType.Error); } }; function closeToast() { setToastType(undefined); } let toastElement: JSX.Element | undefined; if (toastType === ToastType.Loading) { toastElement = ; } else if (toastType === ToastType.Copied) { toastElement = ; } else if (toastType === ToastType.Error) { toastElement = ; } if (publicLogURL) { const copyLog = (ev: MouseEvent) => { ev.preventDefault(); copyText(publicLogURL); setToastType(ToastType.Copied); }; const supportURL = createSupportUrl({ locale: i18n.getLocale(), query: { debugLog: publicLogURL, }, }); return (
{i18n('debugLogSuccess')}

{i18n('debugLogSuccessNextSteps')}

{toastElement}
); } const canSubmit = Boolean(logText) && loadState !== LoadState.Submitting; const canSave = Boolean(logText); const isLoading = loadState === LoadState.Started || loadState === LoadState.Submitting; return (
{i18n('submitDebugLog')}

{i18n('debugLogExplanation')}

{isLoading ? (
) : (
              {textAreaValue}
            
)}
{toastElement}
); }