Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F33314463
D14295.1768821913.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
26 KB
Referenced Files
None
Subscribers
None
D14295.1768821913.diff
View Options
diff --git a/lib/components/debug-logs-context-provider.react.js b/lib/components/debug-logs-context-provider.react.js
new file mode 100644
--- /dev/null
+++ b/lib/components/debug-logs-context-provider.react.js
@@ -0,0 +1,45 @@
+// @flow
+
+import * as React from 'react';
+
+import { type DebugLog, DebugLogsContext } from './debug-logs-context.js';
+
+type Props = {
+ +children: React.Node,
+};
+
+function DebugLogsContextProvider(props: Props): React.Node {
+ const [logs, setLogs] = React.useState<$ReadOnlyArray<DebugLog>>([]);
+
+ const addLog = React.useCallback(
+ (title: string, message: string) =>
+ setLogs(prevLogs => [
+ ...prevLogs,
+ {
+ title,
+ message,
+ timestamp: Date.now(),
+ },
+ ]),
+ [],
+ );
+
+ const clearLogs = React.useCallback(() => setLogs([]), []);
+
+ const contextValue = React.useMemo(
+ () => ({
+ logs,
+ addLog,
+ clearLogs,
+ }),
+ [addLog, clearLogs, logs],
+ );
+
+ return (
+ <DebugLogsContext.Provider value={contextValue}>
+ {props.children}
+ </DebugLogsContext.Provider>
+ );
+}
+
+export { DebugLogsContextProvider };
diff --git a/lib/components/debug-logs-context.js b/lib/components/debug-logs-context.js
new file mode 100644
--- /dev/null
+++ b/lib/components/debug-logs-context.js
@@ -0,0 +1,31 @@
+// @flow
+
+import invariant from 'invariant';
+import * as React from 'react';
+
+export type DebugLog = {
+ +title: string,
+ +message: string,
+ +timestamp: number,
+};
+
+export type DebugLogsContextType = {
+ +logs: $ReadOnlyArray<DebugLog>,
+ +addLog: (title: string, message: string) => mixed,
+ +clearLogs: () => mixed,
+};
+
+const DebugLogsContext: React.Context<DebugLogsContextType> =
+ React.createContext<DebugLogsContextType>({
+ logs: [],
+ addLog: () => {},
+ clearLogs: () => {},
+ });
+
+function useDebugLogs(): DebugLogsContextType {
+ const debugLogsContext = React.useContext(DebugLogsContext);
+ invariant(debugLogsContext, 'Debug logs context should be present');
+ return debugLogsContext;
+}
+
+export { DebugLogsContext, useDebugLogs };
diff --git a/native/navigation/route-names.js b/native/navigation/route-names.js
--- a/native/navigation/route-names.js
+++ b/native/navigation/route-names.js
@@ -188,6 +188,7 @@
export const SecondaryDeviceConnectedRouteName = 'SecondaryDeviceConnected';
export const SecondaryDeviceNotRespondingRouteName =
'SecondaryDeviceNotResponding';
+export const DebugLogsScreenRouteName = 'DebugLogsScreen';
export type RootParamList = {
+LoggedOutModal: void,
@@ -304,6 +305,7 @@
+KeyserverSelectionList: void,
+AddKeyserver: void,
+FarcasterAccountSettings: void,
+ +DebugLogsScreen: void,
};
export type CalendarParamList = {
diff --git a/native/profile/debug-logs-screen.react.js b/native/profile/debug-logs-screen.react.js
new file mode 100644
--- /dev/null
+++ b/native/profile/debug-logs-screen.react.js
@@ -0,0 +1,82 @@
+// @flow
+
+import Clipboard from '@react-native-clipboard/clipboard';
+import * as React from 'react';
+import { FlatList, View, Text } from 'react-native';
+
+import {
+ useDebugLogs,
+ type DebugLog,
+} from 'lib/components/debug-logs-context.js';
+
+import type { ProfileNavigationProp } from './profile.react.js';
+import PrimaryButton from '../components/primary-button.react.js';
+import type { NavigationRoute } from '../navigation/route-names.js';
+import { useStyles } from '../themes/colors.js';
+
+type Props = {
+ +navigation: ProfileNavigationProp<'DebugLogsScreen'>,
+ +route: NavigationRoute<'DebugLogsScreen'>,
+};
+
+// eslint-disable-next-line no-unused-vars
+function DebugLogsScreen(props: Props): React.Node {
+ const { logs, clearLogs } = useDebugLogs();
+
+ const copyLogs = React.useCallback(() => {
+ Clipboard.setString(JSON.stringify(logs));
+ }, [logs]);
+
+ const styles = useStyles(unboundStyles);
+
+ const renderItem = React.useCallback(
+ ({ item }: { +item: DebugLog, ... }) => {
+ const date = new Date(item.timestamp);
+ const timestampString = date.toISOString();
+ return (
+ <View style={styles.item}>
+ <Text style={styles.timestamp}>{timestampString}</Text>
+ <Text style={styles.title}>{item.title}</Text>
+ <Text style={styles.message}>{item.message}</Text>
+ </View>
+ );
+ },
+ [styles.item, styles.message, styles.timestamp, styles.title],
+ );
+
+ return (
+ <View style={styles.view}>
+ <FlatList data={logs} renderItem={renderItem} />
+ <PrimaryButton onPress={clearLogs} variant="danger" label="Clear Logs" />
+ <PrimaryButton onPress={copyLogs} variant="enabled" label="Copy Logs" />
+ </View>
+ );
+}
+
+const unboundStyles = {
+ view: {
+ flex: 1,
+ backgroundColor: 'panelBackground',
+ padding: 24,
+ },
+ item: {
+ backgroundColor: 'panelForeground',
+ borderWidth: 1,
+ borderTopWidth: 1,
+ borderColor: 'panelForegroundBorder',
+ marginBottom: 8,
+ padding: 8,
+ },
+ timestamp: {
+ color: 'panelForegroundLabel',
+ },
+ title: {
+ color: 'panelForegroundLabel',
+ fontSize: 16,
+ },
+ message: {
+ color: 'panelForegroundSecondaryLabel',
+ },
+};
+
+export default DebugLogsScreen;
diff --git a/native/profile/profile-screen.react.js b/native/profile/profile-screen.react.js
--- a/native/profile/profile-screen.react.js
+++ b/native/profile/profile-screen.react.js
@@ -57,6 +57,7 @@
KeyserverSelectionListRouteName,
TunnelbrokerMenuRouteName,
FarcasterAccountSettingsRouteName,
+ DebugLogsScreenRouteName,
} from '../navigation/route-names.js';
import { useSelector } from '../redux/redux-utils.js';
import { type Colors, useColors, useStyles } from '../themes/colors.js';
@@ -199,7 +200,8 @@
let developerTools,
defaultNotifications,
keyserverSelection,
- tunnelbrokerMenu;
+ tunnelbrokerMenu,
+ debugLogs;
const { staffCanSee } = this.props;
if (staffCanSee) {
developerTools = (
@@ -226,6 +228,10 @@
onPress={this.onPressTunnelbrokerMenu}
/>
);
+
+ debugLogs = (
+ <ProfileRow content="Debug logs" onPress={this.onPressDebugLogs} />
+ );
}
let backupMenu;
@@ -342,6 +348,7 @@
<ProfileRow content="Build info" onPress={this.onPressBuildInfo} />
{developerTools}
{dmActions}
+ {debugLogs}
</View>
<View style={this.props.styles.unpaddedSection}>
<ProfileRow
@@ -546,6 +553,10 @@
onPressCreateThread = () => {
void this.props.onCreateDMThread();
};
+
+ onPressDebugLogs = () => {
+ this.props.navigation.navigate({ name: DebugLogsScreenRouteName });
+ };
}
const logOutLoadingStatusSelector =
diff --git a/native/profile/profile.react.js b/native/profile/profile.react.js
--- a/native/profile/profile.react.js
+++ b/native/profile/profile.react.js
@@ -14,6 +14,7 @@
import AppearancePreferences from './appearance-preferences.react.js';
import BackupMenu from './backup-menu.react.js';
import BuildInfo from './build-info.react.js';
+import DebugLogsScreen from './debug-logs-screen.react.js';
import DefaultNotificationsPreferences from './default-notifications-preferences.react.js';
import DeleteAccount from './delete-account.react.js';
import DevTools from './dev-tools.react.js';
@@ -52,6 +53,7 @@
type ScreenParamList,
type ProfileParamList,
TunnelbrokerMenuRouteName,
+ DebugLogsScreenRouteName,
} from '../navigation/route-names.js';
import type { TabNavigationProp } from '../navigation/tab-navigator.react.js';
import { useStyles, useColors } from '../themes/colors.js';
@@ -83,6 +85,7 @@
const blockListOptions = { headerTitle: 'Block list' };
const defaultNotificationsOptions = { headerTitle: 'Default Notifications' };
const farcasterSettingsOptions = { headerTitle: 'Farcaster account' };
+const debugLogsScreenOptions = { headerTitle: 'Logs' };
export type ProfileNavigationProp<
RouteName: $Keys<ProfileParamList> = $Keys<ProfileParamList>,
@@ -222,6 +225,11 @@
component={FarcasterAccountSettings}
options={farcasterSettingsOptions}
/>
+ <Profile.Screen
+ name={DebugLogsScreenRouteName}
+ component={DebugLogsScreen}
+ options={debugLogsScreenOptions}
+ />
</Profile.Navigator>
</KeyboardAvoidingView>
</View>
diff --git a/native/root.react.js b/native/root.react.js
--- a/native/root.react.js
+++ b/native/root.react.js
@@ -24,6 +24,7 @@
import { PersistGate as ReduxPersistGate } from 'redux-persist/es/integration/react.js';
import { ChatMentionContextProvider } from 'lib/components/chat-mention-provider.react.js';
+import { DebugLogsContextProvider } from 'lib/components/debug-logs-context-provider.react.js';
import { EditUserAvatarProvider } from 'lib/components/edit-user-avatar-provider.react.js';
import { ENSCacheProvider } from 'lib/components/ens-cache-provider.react.js';
import { FarcasterChannelPrefetchHandler } from 'lib/components/farcaster-channel-prefetch-handler.react.js';
@@ -334,95 +335,97 @@
);
}
return (
- <GestureHandlerRootView style={styles.app}>
- <StaffContextProvider>
- <IdentityServiceContextProvider>
- <UserIdentityCacheProvider>
- <ENSCacheProvider
- ethersProvider={ethersProvider}
- alchemyKey={alchemyKey}
- >
- <NeynarClientProvider apiKey={neynarKey}>
- <TunnelbrokerProvider>
- <IdentitySearchProvider>
- <SecondaryDeviceQRAuthContextProvider
- parseTunnelbrokerQRAuthMessage={
- parseTunnelbrokerQRAuthMessage
- }
- composeTunnelbrokerQRAuthMessage={
- composeTunnelbrokerQRAuthMessage
- }
- generateAESKey={generateQRAuthAESKey}
- performBackupRestore={performBackupRestore}
- onLogInError={handleSecondaryDeviceLogInError}
- >
- <FeatureFlagsProvider>
- <NavContext.Provider value={navContext}>
- <RootContext.Provider value={rootContext}>
- <InputStateContainer>
- <MessageEditingContextProvider>
- <SafeAreaProvider
- initialMetrics={initialWindowMetrics}
- >
- <ActionSheetProvider>
- <MediaCacheProvider
- persistence={filesystemMediaCache}
- >
- <EditUserAvatarProvider>
- <NativeEditThreadAvatarProvider>
- <MarkdownContextProvider>
- <MessageSearchProvider>
- <BottomSheetProvider>
- <RegistrationContextProvider>
- <SQLiteDataHandler />
- <ConnectedStatusBar />
- <ReduxPersistGate
- persistor={getPersistor()}
- >
- {gated}
- </ReduxPersistGate>
- <PersistedStateGate>
- <KeyserverConnectionsHandler
- socketComponent={Socket}
- detectUnsupervisedBackgroundRef={
- detectUnsupervisedBackgroundRef
- }
- />
- <DMActivityHandler />
- <VersionSupportedChecker />
- <PlatformDetailsSynchronizer />
- <BackgroundIdentityLoginHandler />
- <PrekeysHandler />
- <ReportHandler />
- <FarcasterChannelPrefetchHandler />
- <AutoJoinCommunityHandler />
- <SyncCommunityStoreHandler />
- <InitialStateSharingHandler />
- </PersistedStateGate>
- {navigation}
- </RegistrationContextProvider>
- </BottomSheetProvider>
- </MessageSearchProvider>
- </MarkdownContextProvider>
- </NativeEditThreadAvatarProvider>
- </EditUserAvatarProvider>
- </MediaCacheProvider>
- </ActionSheetProvider>
- </SafeAreaProvider>
- </MessageEditingContextProvider>
- </InputStateContainer>
- </RootContext.Provider>
- </NavContext.Provider>
- </FeatureFlagsProvider>
- </SecondaryDeviceQRAuthContextProvider>
- </IdentitySearchProvider>
- </TunnelbrokerProvider>
- </NeynarClientProvider>
- </ENSCacheProvider>
- </UserIdentityCacheProvider>
- </IdentityServiceContextProvider>
- </StaffContextProvider>
- </GestureHandlerRootView>
+ <DebugLogsContextProvider>
+ <GestureHandlerRootView style={styles.app}>
+ <StaffContextProvider>
+ <IdentityServiceContextProvider>
+ <UserIdentityCacheProvider>
+ <ENSCacheProvider
+ ethersProvider={ethersProvider}
+ alchemyKey={alchemyKey}
+ >
+ <NeynarClientProvider apiKey={neynarKey}>
+ <TunnelbrokerProvider>
+ <IdentitySearchProvider>
+ <SecondaryDeviceQRAuthContextProvider
+ parseTunnelbrokerQRAuthMessage={
+ parseTunnelbrokerQRAuthMessage
+ }
+ composeTunnelbrokerQRAuthMessage={
+ composeTunnelbrokerQRAuthMessage
+ }
+ generateAESKey={generateQRAuthAESKey}
+ performBackupRestore={performBackupRestore}
+ onLogInError={handleSecondaryDeviceLogInError}
+ >
+ <FeatureFlagsProvider>
+ <NavContext.Provider value={navContext}>
+ <RootContext.Provider value={rootContext}>
+ <InputStateContainer>
+ <MessageEditingContextProvider>
+ <SafeAreaProvider
+ initialMetrics={initialWindowMetrics}
+ >
+ <ActionSheetProvider>
+ <MediaCacheProvider
+ persistence={filesystemMediaCache}
+ >
+ <EditUserAvatarProvider>
+ <NativeEditThreadAvatarProvider>
+ <MarkdownContextProvider>
+ <MessageSearchProvider>
+ <BottomSheetProvider>
+ <RegistrationContextProvider>
+ <SQLiteDataHandler />
+ <ConnectedStatusBar />
+ <ReduxPersistGate
+ persistor={getPersistor()}
+ >
+ {gated}
+ </ReduxPersistGate>
+ <PersistedStateGate>
+ <KeyserverConnectionsHandler
+ socketComponent={Socket}
+ detectUnsupervisedBackgroundRef={
+ detectUnsupervisedBackgroundRef
+ }
+ />
+ <DMActivityHandler />
+ <VersionSupportedChecker />
+ <PlatformDetailsSynchronizer />
+ <BackgroundIdentityLoginHandler />
+ <PrekeysHandler />
+ <ReportHandler />
+ <FarcasterChannelPrefetchHandler />
+ <AutoJoinCommunityHandler />
+ <SyncCommunityStoreHandler />
+ <InitialStateSharingHandler />
+ </PersistedStateGate>
+ {navigation}
+ </RegistrationContextProvider>
+ </BottomSheetProvider>
+ </MessageSearchProvider>
+ </MarkdownContextProvider>
+ </NativeEditThreadAvatarProvider>
+ </EditUserAvatarProvider>
+ </MediaCacheProvider>
+ </ActionSheetProvider>
+ </SafeAreaProvider>
+ </MessageEditingContextProvider>
+ </InputStateContainer>
+ </RootContext.Provider>
+ </NavContext.Provider>
+ </FeatureFlagsProvider>
+ </SecondaryDeviceQRAuthContextProvider>
+ </IdentitySearchProvider>
+ </TunnelbrokerProvider>
+ </NeynarClientProvider>
+ </ENSCacheProvider>
+ </UserIdentityCacheProvider>
+ </IdentityServiceContextProvider>
+ </StaffContextProvider>
+ </GestureHandlerRootView>
+ </DebugLogsContextProvider>
);
}
diff --git a/web/root.js b/web/root.js
--- a/web/root.js
+++ b/web/root.js
@@ -11,6 +11,7 @@
import thunk from 'redux-thunk';
import { WagmiProvider } from 'wagmi';
+import { DebugLogsContextProvider } from 'lib/components/debug-logs-context-provider.react.js';
import IntegrityHandler from 'lib/components/integrity-handler.react.js';
import PrekeysHandler from 'lib/components/prekeys-handler.react.js';
import ReportHandler from 'lib/components/report-handler.react.js';
@@ -62,25 +63,27 @@
const RootProvider = (): React.Node => (
<Provider store={store}>
<ErrorBoundary>
- <WagmiProvider config={wagmiConfig}>
- <QueryClientProvider client={queryClient}>
- <CallKeyserverEndpointProvider>
- <InitialReduxStateGate persistor={persistor}>
- <IdentityServiceContextProvider>
- <UserIdentityCacheProvider>
- <Router history={history.getHistoryObject()}>
- <Route path="*" component={App} />
- </Router>
- <PrekeysHandler />
- <SQLiteDataHandler />
- <IntegrityHandler />
- <ReportHandler canSendReports={true} />
- </UserIdentityCacheProvider>
- </IdentityServiceContextProvider>
- </InitialReduxStateGate>
- </CallKeyserverEndpointProvider>
- </QueryClientProvider>
- </WagmiProvider>
+ <DebugLogsContextProvider>
+ <WagmiProvider config={wagmiConfig}>
+ <QueryClientProvider client={queryClient}>
+ <CallKeyserverEndpointProvider>
+ <InitialReduxStateGate persistor={persistor}>
+ <IdentityServiceContextProvider>
+ <UserIdentityCacheProvider>
+ <Router history={history.getHistoryObject()}>
+ <Route path="*" component={App} />
+ </Router>
+ <PrekeysHandler />
+ <SQLiteDataHandler />
+ <IntegrityHandler />
+ <ReportHandler canSendReports={true} />
+ </UserIdentityCacheProvider>
+ </IdentityServiceContextProvider>
+ </InitialReduxStateGate>
+ </CallKeyserverEndpointProvider>
+ </QueryClientProvider>
+ </WagmiProvider>
+ </DebugLogsContextProvider>
</ErrorBoundary>
</Provider>
);
diff --git a/web/settings/account-settings.react.js b/web/settings/account-settings.react.js
--- a/web/settings/account-settings.react.js
+++ b/web/settings/account-settings.react.js
@@ -32,6 +32,7 @@
import css from './account-settings.css';
import AppearanceChangeModal from './appearance-change-modal.react.js';
import BackupTestRestoreModal from './backup-test-restore-modal.react.js';
+import DebugLogsModal from './debug-logs-modal.react.js';
import BlockListModal from './relationship/block-list-modal.react.js';
import FriendListModal from './relationship/friend-list-modal.react.js';
import TunnelbrokerMessagesScreen from './tunnelbroker-message-list.react.js';
@@ -178,6 +179,11 @@
[pushModal],
);
+ const openDebugLogs = React.useCallback(
+ () => pushModal(<DebugLogsModal />),
+ [pushModal],
+ );
+
if (!currentUserInfo || currentUserInfo.anonymous) {
return null;
}
@@ -294,6 +300,25 @@
);
}
+ let debugLogs;
+ if (staffCanSee) {
+ debugLogs = (
+ <div className={css.preferencesContainer}>
+ <h4 className={css.preferencesHeader}>Debug menu</h4>
+ <div className={css.content}>
+ <ul>
+ <li>
+ <span>See debug logs</span>
+ <Button variant="text" onClick={openDebugLogs}>
+ <p className={css.buttonText}>See List</p>
+ </Button>
+ </li>
+ </ul>
+ </div>
+ </div>
+ );
+ }
+
return (
<div className={css.container}>
<div className={css.contentContainer}>
@@ -329,6 +354,7 @@
{backup}
{deviceData}
{dms}
+ {debugLogs}
</div>
</div>
);
diff --git a/web/settings/debug-logs-modal.css b/web/settings/debug-logs-modal.css
new file mode 100644
--- /dev/null
+++ b/web/settings/debug-logs-modal.css
@@ -0,0 +1,47 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ height: 60vh;
+ gap: 8px;
+}
+
+.logsList {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ overflow-y: scroll;
+}
+
+.item {
+ margin: 4px 0;
+ padding: 4px 0;
+ color: var(--text-background-secondary-default);
+ transition: 0.1s;
+}
+
+.item:hover {
+ background-color: var(--modal-listItem-primary-hover);
+}
+
+.timestamp {
+ font-size: var(--m-font-16);
+}
+
+.title {
+ font-size: var(--l-font-18);
+ color: var(--text-background-primary-default);
+}
+
+.message {
+ font-size: var(--m-font-16);
+}
+
+.buttons {
+ display: flex;
+ gap: 8px;
+ justify-content: space-between;
+}
+
+.button {
+ flex: 1;
+}
diff --git a/web/settings/debug-logs-modal.react.js b/web/settings/debug-logs-modal.react.js
new file mode 100644
--- /dev/null
+++ b/web/settings/debug-logs-modal.react.js
@@ -0,0 +1,58 @@
+// @flow
+
+import * as React from 'react';
+
+import { useDebugLogs } from 'lib/components/debug-logs-context.js';
+import { useModalContext } from 'lib/components/modal-provider.react.js';
+
+import css from './debug-logs-modal.css';
+import Button, { buttonThemes } from '../components/button.react.js';
+import Modal from '../modals/modal.react.js';
+
+function DebugLogsModal(): React.Node {
+ const { logs, clearLogs } = useDebugLogs();
+ const { popModal } = useModalContext();
+
+ const messageList = React.useMemo(
+ () =>
+ logs.map((item, index) => {
+ const date = new Date(item.timestamp);
+ const timestampString = date.toISOString();
+ return (
+ <div key={index} className={css.item}>
+ <div className={css.timestamp}>{timestampString}</div>
+ <div className={css.title}>{item.title}</div>
+ <div className={css.message}>{item.message}</div>
+ </div>
+ );
+ }),
+ [logs],
+ );
+
+ const copyLogs = React.useCallback(async () => {
+ await navigator.clipboard.writeText(JSON.stringify(logs));
+ }, [logs]);
+
+ return (
+ <Modal name="Debug Logs" onClose={popModal} size="large">
+ <div className={css.container}>
+ <div className={css.logsList}>{messageList}</div>
+ <div className={css.buttons}>
+ <Button variant="filled" className={css.button} onClick={copyLogs}>
+ Copy Logs
+ </Button>
+ <Button
+ variant="filled"
+ buttonColor={buttonThemes.danger}
+ className={css.button}
+ onClick={clearLogs}
+ >
+ Clear Logs
+ </Button>
+ </div>
+ </div>
+ </Modal>
+ );
+}
+
+export default DebugLogsModal;
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Jan 19, 11:25 AM (18 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5956084
Default Alt Text
D14295.1768821913.diff (26 KB)
Attached To
Mode
D14295: [native] Create a place where logs are stored and can be displayed
Attached
Detach File
Event Timeline
Log In to Comment