Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F4933019
D14295.id46933.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
19 KB
Referenced Files
None
Subscribers
None
D14295.id46933.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
@@ -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
Details
Attached
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)
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