Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at cope-settings-sync 304 lines 13 kB view raw
1import '#/logger/sentry/setup' // must be near top 2import '#/view/icons' 3import './style.css' 4 5import {Fragment, useEffect, useState} from 'react' 6import {KeyboardProvider as KeyboardControllerProvider} from 'react-native-keyboard-controller' 7import {SafeAreaProvider} from 'react-native-safe-area-context' 8import {useLingui} from '@lingui/react/macro' 9import * as Sentry from '@sentry/react-native' 10 11import {Provider as HotkeysProvider} from '#/lib/hotkeys' 12import {SafeAreaOverride} from '#/lib/pwa-safe-area' 13import {QueryProvider} from '#/lib/react-query' 14import {ThemeProvider} from '#/lib/ThemeContext' 15import {Provider as TranslateOnDeviceProvider} from '#/lib/translation' 16import I18nProvider from '#/locale/i18nProvider' 17import {logger} from '#/logger' 18import {Provider as A11yProvider} from '#/state/a11y' 19import { 20 prefetchAppConfig, 21 Provider as AppConfigProvider, 22} from '#/state/appConfig' 23import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' 24import {Provider as DialogStateProvider} from '#/state/dialogs' 25import {Provider as EmailVerificationProvider} from '#/state/email-verification' 26import {listenSessionDropped} from '#/state/events' 27import {Provider as HomeBadgeProvider} from '#/state/home-badge' 28import {Provider as LightboxStateProvider} from '#/state/lightbox' 29import {MessagesProvider} from '#/state/messages' 30import {Provider as ModalStateProvider} from '#/state/modals' 31import {init as initPersistedState} from '#/state/persisted' 32import {Provider as PrefsStateProvider} from '#/state/preferences' 33import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs' 34import {Provider as ModerationOptsProvider} from '#/state/preferences/moderation-opts' 35import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread' 36import {Provider as ServiceConfigProvider} from '#/state/service-config' 37import { 38 Provider as SessionProvider, 39 type SessionAccount, 40 useSession, 41 useSessionApi, 42} from '#/state/session' 43import {getWebOAuthClient} from '#/state/session/oauth-web-client' 44import {readLastActiveAccount} from '#/state/session/util' 45import {Provider as ShellStateProvider} from '#/state/shell' 46import {Provider as ComposerProvider} from '#/state/shell/composer' 47import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' 48import {Provider as OnboardingProvider} from '#/state/shell/onboarding' 49import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide' 50import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' 51import {Provider as StarterPackProvider} from '#/state/shell/starter-pack' 52import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies' 53import {Shell} from '#/view/shell/index' 54import {ThemeProvider as Alf} from '#/alf' 55import {useColorModeTheme} from '#/alf/util/useColorModeTheme' 56import {Provider as ContextMenuProvider} from '#/components/ContextMenu' 57import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry' 58import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs' 59import {Provider as PolicyUpdateOverlayProvider} from '#/components/PolicyUpdateOverlay' 60import {Provider as PortalProvider} from '#/components/Portal' 61import {Provider as ActiveVideoProvider} from '#/components/Post/Embed/VideoEmbed/ActiveVideoWebContext' 62import {Provider as VideoVolumeProvider} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' 63import * as Toast from '#/components/Toast' 64import {ToastOutlet} from '#/components/Toast' 65import { 66 prefetchAgeAssuranceConfig, 67 Provider as AgeAssuranceV2Provider, 68} from '#/ageAssurance' 69import { 70 AnalyticsContext, 71 AnalyticsFeaturesContext, 72 features, 73 setupDeviceId, 74} from '#/analytics' 75import {SettingsSyncGate} from '#/features/settingsSync' 76import { 77 prefetchLiveEvents, 78 Provider as LiveEventsProvider, 79} from '#/features/liveEvents/context' 80import * as Geo from '#/geolocation' 81import {Splash} from '#/Splash' 82import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' 83import {Provider as HideBottomBarBorderProvider} from './lib/hooks/useHideBottomBarBorder' 84 85// For local development: the OAuth loopback spec requires IP-based origins 86// (127.0.0.1), not "localhost". The auth server redirects to 127.0.0.1, but 87// IndexedDB is per-origin, so PKCE state stored on "localhost" is unreachable 88// from "127.0.0.1". Redirect immediately so both signIn() and the callback 89// use the same origin. 90if (typeof window !== 'undefined' && window.location.hostname === 'localhost') { 91 const url = new URL(window.location.href) 92 url.hostname = '127.0.0.1' 93 window.location.replace(url.href) 94} 95 96function hasOAuthCallbackParams(): boolean { 97 // OAuth callback params come in the hash fragment (response_mode=fragment) 98 // or query string. Check both for "state" + ("code" or "error"). 99 const hash = new URLSearchParams(window.location.hash.slice(1)) 100 const query = new URLSearchParams(window.location.search) 101 const params = hash.has('state') ? hash : query 102 return params.has('state') && (params.has('code') || params.has('error')) 103} 104 105/** 106 * Begin geolocation ASAP 107 */ 108void Geo.resolve() 109void prefetchAgeAssuranceConfig() 110void prefetchLiveEvents() 111void prefetchAppConfig() 112 113function InnerApp() { 114 const [isReady, setIsReady] = useState(false) 115 const {currentAccount} = useSession() 116 const {resumeSession, login} = useSessionApi() 117 const theme = useColorModeTheme() 118 const {t: l} = useLingui() 119 const hasCheckedReferrer = useStarterPackEntry() 120 121 // init 122 useEffect(() => { 123 // Safety valve: if onLaunch hangs (e.g. stale IndexedDB blocking an 124 // upgrade, or a never-settling promise), the app will still load after 125 // this timeout fires. 126 const safetyTimeout = setTimeout(() => { 127 logger.warn('session: onLaunch safety timeout fired, forcing ready state') 128 setIsReady(true) 129 }, 15_000) 130 131 async function onLaunch(account?: SessionAccount) { 132 try { 133 // Check for OAuth callback params first (loopback redirects to /) 134 if (hasOAuthCallbackParams()) { 135 const client = getWebOAuthClient() 136 const result = await client.init() 137 if (result?.session) { 138 await login( 139 { 140 service: '', 141 identifier: '', 142 password: '', 143 oauthSession: result.session, 144 }, 145 'LoginForm', 146 ) 147 // Clear hash fragment after processing 148 window.history.replaceState(null, '', window.location.pathname) 149 return 150 } 151 } 152 153 if (account) { 154 await resumeSession(account) 155 } else { 156 await features.init 157 } 158 } catch (e) { 159 logger.error('session: resumeSession failed', {message: e}) 160 } finally { 161 clearTimeout(safetyTimeout) 162 setIsReady(true) 163 } 164 } 165 const account = readLastActiveAccount() 166 void onLaunch(account) 167 }, [resumeSession, login]) 168 169 useEffect(() => { 170 return listenSessionDropped(() => { 171 Toast.show(l`Sorry! Your session expired. Please sign in again.`, { 172 type: 'info', 173 }) 174 }) 175 }, [l]) 176 177 return ( 178 <Alf theme={theme}> 179 <ThemeProvider theme={theme}> 180 <ContextMenuProvider> 181 <Splash isReady={isReady && hasCheckedReferrer}> 182 <VideoVolumeProvider> 183 <ActiveVideoProvider> 184 <Fragment 185 // Resets the entire tree below when it changes: 186 key={currentAccount?.did}> 187 <AnalyticsFeaturesContext> 188 <QueryProvider currentDid={currentAccount?.did}> 189 <SettingsSyncGate> 190 <PolicyUpdateOverlayProvider> 191 <LiveEventsProvider> 192 <AgeAssuranceV2Provider> 193 <ComposerProvider> 194 <MessagesProvider> 195 {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 196 <LabelDefsProvider> 197 <ModerationOptsProvider> 198 <LoggedOutViewProvider> 199 <SelectedFeedProvider> 200 <HiddenRepliesProvider> 201 <HomeBadgeProvider> 202 <UnreadNotifsProvider> 203 <BackgroundNotificationPreferencesProvider> 204 <MutedThreadsProvider> 205 <SafeAreaProvider> 206 <SafeAreaOverride> 207 <ProgressGuideProvider> 208 <ServiceConfigProvider> 209 <EmailVerificationProvider> 210 <HideBottomBarBorderProvider> 211 <IntentDialogProvider> 212 <TranslateOnDeviceProvider> 213 <HotkeysProvider> 214 <Shell /> 215 <ToastOutlet /> 216 </HotkeysProvider> 217 </TranslateOnDeviceProvider> 218 </IntentDialogProvider> 219 </HideBottomBarBorderProvider> 220 </EmailVerificationProvider> 221 </ServiceConfigProvider> 222 </ProgressGuideProvider> 223 </SafeAreaOverride> 224 </SafeAreaProvider> 225 </MutedThreadsProvider> 226 </BackgroundNotificationPreferencesProvider> 227 </UnreadNotifsProvider> 228 </HomeBadgeProvider> 229 </HiddenRepliesProvider> 230 </SelectedFeedProvider> 231 </LoggedOutViewProvider> 232 </ModerationOptsProvider> 233 </LabelDefsProvider> 234 </MessagesProvider> 235 </ComposerProvider> 236 </AgeAssuranceV2Provider> 237 </LiveEventsProvider> 238 </PolicyUpdateOverlayProvider> 239 </SettingsSyncGate> 240 </QueryProvider> 241 </AnalyticsFeaturesContext> 242 </Fragment> 243 </ActiveVideoProvider> 244 </VideoVolumeProvider> 245 </Splash> 246 </ContextMenuProvider> 247 </ThemeProvider> 248 </Alf> 249 ) 250} 251 252function App() { 253 const [isReady, setIsReady] = useState(false) 254 255 useEffect(() => { 256 void Promise.all([initPersistedState(), Geo.resolve(), setupDeviceId]).then( 257 () => setIsReady(true), 258 ) 259 }, []) 260 261 if (!isReady) { 262 return null 263 } 264 265 /* 266 * NOTE: only nothing here can depend on other data or session state, since 267 * that is set up in the InnerApp component above. 268 */ 269 return ( 270 <Geo.Provider> 271 <AppConfigProvider> 272 <A11yProvider> 273 <KeyboardControllerProvider> 274 <OnboardingProvider> 275 <AnalyticsContext> 276 <SessionProvider> 277 <PrefsStateProvider> 278 <I18nProvider> 279 <ShellStateProvider> 280 <ModalStateProvider> 281 <DialogStateProvider> 282 <LightboxStateProvider> 283 <PortalProvider> 284 <StarterPackProvider> 285 <InnerApp /> 286 </StarterPackProvider> 287 </PortalProvider> 288 </LightboxStateProvider> 289 </DialogStateProvider> 290 </ModalStateProvider> 291 </ShellStateProvider> 292 </I18nProvider> 293 </PrefsStateProvider> 294 </SessionProvider> 295 </AnalyticsContext> 296 </OnboardingProvider> 297 </KeyboardControllerProvider> 298 </A11yProvider> 299 </AppConfigProvider> 300 </Geo.Provider> 301 ) 302} 303 304export default Sentry.wrap(App)