Goon — self-hosted aggregator for adult-content scene metadata. Indexes scenes from TPDB, StashDB, and 30+ public adult tube sites. Cross-source deduplication via perceptual hash + Levenshtein distance. FastAPI backend + APScheduler worker + React Native (Expo) mobile client. FOSS, ad-free, donation-funded. See README for details.
80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
import * as Sentry from '@sentry/react-native';
|
|
import React from 'react';
|
|
import { ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
import { theme } from './theme';
|
|
|
|
interface Props {
|
|
children: React.ReactNode;
|
|
/** Opcjonalny tag wysyłany do Sentry — pomaga rozróżnić top-level boundary od lokalnych. */
|
|
scope?: string;
|
|
}
|
|
|
|
interface State {
|
|
error: Error | null;
|
|
info: string | null;
|
|
}
|
|
|
|
export class ErrorBoundary extends React.Component<Props, State> {
|
|
state: State = { error: null, info: null };
|
|
|
|
static getDerivedStateFromError(error: Error): State {
|
|
return { error, info: null };
|
|
}
|
|
|
|
componentDidCatch(error: Error, info: React.ErrorInfo): void {
|
|
this.setState({ error, info: info.componentStack || null });
|
|
// Sentry.wrap()-owy boundary łapie tylko jeśli root nie jest manuanle wrapped.
|
|
// Dodajemy explicit capture żeby ten boundary też wysyłał — z tagiem `scope`
|
|
// żebyśmy w UI Sentry odróżnili global crash od per-tab crash.
|
|
Sentry.captureException(error, {
|
|
tags: { boundary: this.props.scope || 'global' },
|
|
contexts: { react: { componentStack: info.componentStack } },
|
|
});
|
|
// eslint-disable-next-line no-console
|
|
console.error('App crashed:', error, info);
|
|
}
|
|
|
|
render() {
|
|
if (this.state.error) {
|
|
return (
|
|
<ScrollView style={styles.container} contentContainerStyle={{ padding: 20 }}>
|
|
<Text style={styles.title}>App crashed</Text>
|
|
<Text style={styles.label}>Error</Text>
|
|
<Text style={styles.body}>{this.state.error.name}: {this.state.error.message}</Text>
|
|
{this.state.error.stack ? (
|
|
<>
|
|
<Text style={styles.label}>Stack</Text>
|
|
<Text style={styles.code}>{this.state.error.stack}</Text>
|
|
</>
|
|
) : null}
|
|
{this.state.info ? (
|
|
<>
|
|
<Text style={styles.label}>Component stack</Text>
|
|
<Text style={styles.code}>{this.state.info}</Text>
|
|
</>
|
|
) : null}
|
|
</ScrollView>
|
|
);
|
|
}
|
|
return <>{this.props.children}</>;
|
|
}
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { flex: 1, backgroundColor: theme.bg },
|
|
title: { color: theme.bad, fontSize: 24, fontWeight: '700', marginBottom: 16 },
|
|
label: {
|
|
color: theme.muted,
|
|
fontSize: 12,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.6,
|
|
marginTop: 16,
|
|
marginBottom: 4,
|
|
},
|
|
body: { color: theme.fg, fontSize: 14 },
|
|
code: {
|
|
color: theme.fg,
|
|
fontSize: 11,
|
|
fontFamily: 'monospace',
|
|
},
|
|
});
|