Page MenuHomePhabricator

D14295.id46933.diff
No OneTemporary

D14295.id46933.diff

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
@@ -186,6 +186,7 @@
export const SecondaryDeviceConnectedRouteName = 'SecondaryDeviceConnected';
export const SecondaryDeviceNotRespondingRouteName =
'SecondaryDeviceNotResponding';
+export const DebugLogsScreenRouteName = 'DebugLogsScreen';
export type RootParamList = {
+LoggedOutModal: void,
@@ -301,6 +302,7 @@
+KeyserverSelectionList: void,
+AddKeyserver: void,
+FarcasterAccountSettings: void,
+ +DebugLogsScreenRouteName: 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,81 @@
+// @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}>
+ <PrimaryButton onPress={clearLogs} variant="danger" label="Clear Logs" />
+ <PrimaryButton onPress={copyLogs} variant="enabled" label="Copy Logs" />
+ <FlatList data={logs} renderItem={renderItem} />
+ </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>
);
}

File Metadata

Mime Type
text/plain
Expires
Tue, Mar 18, 3:37 PM (14 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3350961
Default Alt Text
D14295.id46933.diff (19 KB)

Event Timeline