((_: boolean) => {})\n\nexport function Provider({children}: React.PropsWithChildren<{}>) {\n const [state, setState] = React.useState(false)\n\n return (\n \n {children}\n \n )\n}\n\nexport function useIsDrawerOpen() {\n return React.useContext(stateContext)\n}\n\nexport function useSetDrawerOpen() {\n return React.useContext(setContext)\n}\n","import {useCallback} from 'react'\n\nimport {useDialogStateControlContext} from '#/state/dialogs'\nimport {useLightboxControls} from './lightbox'\nimport {useModalControls} from './modals'\nimport {useComposerControls} from './shell/composer'\nimport {useSetDrawerOpen} from './shell/drawer-open'\n\n/**\n * returns true if something was closed\n * (used by the android hardware back btn)\n */\nexport function useCloseAnyActiveElement() {\n const {closeLightbox} = useLightboxControls()\n const {closeModal} = useModalControls()\n const {closeComposer} = useComposerControls()\n const {closeAllDialogs} = useDialogStateControlContext()\n const setDrawerOpen = useSetDrawerOpen()\n return useCallback(() => {\n if (closeLightbox()) {\n return true\n }\n if (closeModal()) {\n return true\n }\n if (closeAllDialogs()) {\n return true\n }\n if (closeComposer()) {\n return true\n }\n setDrawerOpen(false)\n return false\n }, [closeLightbox, closeModal, closeComposer, setDrawerOpen, closeAllDialogs])\n}\n\n/**\n * used to clear out any modals, eg for a navigation\n */\nexport function useCloseAllActiveElements() {\n const {closeLightbox} = useLightboxControls()\n const {closeAllModals} = useModalControls()\n const {closeComposer} = useComposerControls()\n const {closeAllDialogs: closeAlfDialogs} = useDialogStateControlContext()\n const setDrawerOpen = useSetDrawerOpen()\n return useCallback(() => {\n closeLightbox()\n closeAllModals()\n closeComposer()\n closeAlfDialogs()\n setDrawerOpen(false)\n }, [\n closeLightbox,\n closeAllModals,\n closeComposer,\n closeAlfDialogs,\n setDrawerOpen,\n ])\n}\n","import EventEmitter from 'eventemitter3'\n\ntype UnlistenFn = () => void\n\nconst emitter = new EventEmitter()\n\n// a \"soft reset\" typically means scrolling to top and loading latest\n// but it can depend on the screen\nexport function emitSoftReset() {\n emitter.emit('soft-reset')\n}\nexport function listenSoftReset(fn: () => void): UnlistenFn {\n emitter.on('soft-reset', fn)\n return () => emitter.off('soft-reset', fn)\n}\n\nexport function emitSessionDropped() {\n emitter.emit('session-dropped')\n}\nexport function listenSessionDropped(fn: () => void): UnlistenFn {\n emitter.on('session-dropped', fn)\n return () => emitter.off('session-dropped', fn)\n}\n\nexport function emitPostCreated() {\n emitter.emit('post-created')\n}\nexport function listenPostCreated(fn: () => void): UnlistenFn {\n emitter.on('post-created', fn)\n return () => emitter.off('post-created', fn)\n}\n","export function cleanError(str: any): string {\n if (!str) {\n return ''\n }\n if (typeof str !== 'string') {\n str = str.toString()\n }\n if (isNetworkError(str)) {\n return 'Unable to connect. Please check your internet connection and try again.'\n }\n if (str.includes('Upstream Failure')) {\n return 'The server appears to be experiencing issues. Please try again in a few moments.'\n }\n if (str.includes('Bad token scope')) {\n return 'This feature is not available while using an App Password. Please sign in with your main password.'\n }\n if (str.startsWith('Error: ')) {\n return str.slice('Error: '.length)\n }\n return str\n}\n\nexport function isNetworkError(e: unknown) {\n const str = String(e)\n return (\n str.includes('Abort') ||\n str.includes('Network request failed') ||\n str.includes('Failed to fetch')\n )\n}\n","import {isNetworkError} from 'lib/strings/errors'\n\nexport async function retry(\n retries: number,\n cond: (err: any) => boolean,\n fn: () => Promise
,\n): Promise
{\n let lastErr\n while (retries > 0) {\n try {\n return await fn()\n } catch (e: any) {\n lastErr = e\n if (cond(e)) {\n retries--\n continue\n }\n throw e\n }\n }\n throw lastErr\n}\n\nexport async function networkRetry
(\n retries: number,\n fn: () => Promise
,\n): Promise
{\n return retry(retries, isNetworkError, fn)\n}\n","export function niceDate(date: number | string | Date) {\n const d = new Date(date)\n return `${d.toLocaleDateString('en-us', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })} at ${d.toLocaleTimeString(undefined, {\n hour: 'numeric',\n minute: '2-digit',\n })}`\n}\n\nexport function getAge(birthDate: Date): number {\n var today = new Date()\n var age = today.getFullYear() - birthDate.getFullYear()\n var m = today.getMonth() - birthDate.getMonth()\n if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {\n age--\n }\n return age\n}\n\n/**\n * Compares two dates by year, month, and day only\n */\nexport function simpleAreDatesEqual(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n )\n}\n","import React from 'react'\nimport * as persisted from '#/state/persisted'\nimport {track} from '#/lib/analytics/analytics'\n\nexport const OnboardingScreenSteps = {\n Welcome: 'Welcome',\n RecommendedFeeds: 'RecommendedFeeds',\n RecommendedFollows: 'RecommendedFollows',\n Home: 'Home',\n} as const\n\ntype OnboardingStep =\n (typeof OnboardingScreenSteps)[keyof typeof OnboardingScreenSteps]\nconst OnboardingStepsArray = Object.values(OnboardingScreenSteps)\n\ntype Action =\n | {type: 'set'; step: OnboardingStep}\n | {type: 'next'; currentStep?: OnboardingStep}\n | {type: 'start'}\n | {type: 'finish'}\n | {type: 'skip'}\n\nexport type StateContext = persisted.Schema['onboarding'] & {\n isComplete: boolean\n isActive: boolean\n}\nexport type DispatchContext = (action: Action) => void\n\nconst stateContext = React.createContext(\n compute(persisted.defaults.onboarding),\n)\nconst dispatchContext = React.createContext((_: Action) => {})\n\nfunction reducer(state: StateContext, action: Action): StateContext {\n switch (action.type) {\n case 'set': {\n if (OnboardingStepsArray.includes(action.step)) {\n persisted.write('onboarding', {step: action.step})\n return compute({...state, step: action.step})\n }\n return state\n }\n case 'next': {\n const currentStep = action.currentStep || state.step\n let nextStep = 'Home'\n if (currentStep === 'Welcome') {\n nextStep = 'RecommendedFeeds'\n } else if (currentStep === 'RecommendedFeeds') {\n nextStep = 'RecommendedFollows'\n } else if (currentStep === 'RecommendedFollows') {\n nextStep = 'Home'\n }\n persisted.write('onboarding', {step: nextStep})\n return compute({...state, step: nextStep})\n }\n case 'start': {\n track('Onboarding:Begin')\n persisted.write('onboarding', {step: 'Welcome'})\n return compute({...state, step: 'Welcome'})\n }\n case 'finish': {\n track('Onboarding:Complete')\n persisted.write('onboarding', {step: 'Home'})\n return compute({...state, step: 'Home'})\n }\n case 'skip': {\n track('Onboarding:Skipped')\n persisted.write('onboarding', {step: 'Home'})\n return compute({...state, step: 'Home'})\n }\n default: {\n throw new Error('Invalid action')\n }\n }\n}\n\nexport function Provider({children}: React.PropsWithChildren<{}>) {\n const [state, dispatch] = React.useReducer(\n reducer,\n compute(persisted.get('onboarding')),\n )\n\n React.useEffect(() => {\n return persisted.onUpdate(() => {\n const next = persisted.get('onboarding').step\n // TODO we've introduced a footgun\n if (state.step !== next) {\n dispatch({\n type: 'set',\n step: persisted.get('onboarding').step as OnboardingStep,\n })\n }\n })\n }, [state, dispatch])\n\n return (\n \n \n {children}\n \n \n )\n}\n\nexport function useOnboardingState() {\n return React.useContext(stateContext)\n}\n\nexport function useOnboardingDispatch() {\n return React.useContext(dispatchContext)\n}\n\nexport function isOnboardingActive() {\n return compute(persisted.get('onboarding')).isActive\n}\n\nfunction compute(state: persisted.Schema['onboarding']): StateContext {\n return {\n ...state,\n isActive: state.step !== 'Home',\n isComplete: state.step === 'Home',\n }\n}\n","import {simpleAreDatesEqual} from '#/lib/strings/time'\nimport {logger} from '#/logger'\nimport * as persisted from '#/state/persisted'\nimport {SessionAccount} from '../session'\nimport {isOnboardingActive} from './onboarding'\n\nexport function shouldRequestEmailConfirmation(account: SessionAccount) {\n // ignore logged out\n if (!account) return false\n // ignore confirmed accounts, this is the success state of this reminder\n if (account.emailConfirmed) return false\n // wait for onboarding to complete\n if (isOnboardingActive()) return false\n\n const snoozedAt = persisted.get('reminders').lastEmailConfirm\n const today = new Date()\n\n logger.debug('Checking email confirmation reminder', {\n today,\n snoozedAt,\n })\n\n // never been snoozed, new account\n if (!snoozedAt) {\n return true\n }\n\n // already snoozed today\n if (simpleAreDatesEqual(new Date(Date.parse(snoozedAt)), new Date())) {\n return false\n }\n\n return true\n}\n\nexport function snoozeEmailConfirmationPrompt() {\n const lastEmailConfirm = new Date().toISOString()\n logger.debug('Snoozing email confirmation reminder', {\n snoozedAt: lastEmailConfirm,\n })\n persisted.write('reminders', {\n ...persisted.get('reminders'),\n lastEmailConfirm,\n })\n}\n","import {AtpSessionData, AtpSessionEvent} from '@atproto/api'\nimport {sha256} from 'js-sha256'\nimport {Statsig} from 'statsig-react-native-expo'\n\nimport {Schema} from '../persisted'\nimport {Action, State} from './reducer'\nimport {SessionAccount} from './types'\n\ntype Reducer = (state: State, action: Action) => State\n\ntype Log =\n | {\n type: 'reducer:init'\n state: State\n }\n | {\n type: 'reducer:call'\n action: Action\n prevState: State\n nextState: State\n }\n | {\n type: 'method:start'\n method:\n | 'createAccount'\n | 'login'\n | 'logout'\n | 'resumeSession'\n | 'removeAccount'\n account?: SessionAccount\n }\n | {\n type: 'method:end'\n method:\n | 'createAccount'\n | 'login'\n | 'logout'\n | 'resumeSession'\n | 'removeAccount'\n account?: SessionAccount\n }\n | {\n type: 'persisted:broadcast'\n data: Schema['session']\n }\n | {\n type: 'persisted:receive'\n data: Schema['session']\n }\n | {\n type: 'agent:switch'\n prevAgent: object\n nextAgent: object\n }\n | {\n type: 'agent:patch'\n agent: object\n prevSession: AtpSessionData | undefined\n nextSession: AtpSessionData\n }\n\nexport function wrapSessionReducerForLogging(reducer: Reducer): Reducer {\n return function loggingWrapper(prevState: State, action: Action): State {\n const nextState = reducer(prevState, action)\n addSessionDebugLog({type: 'reducer:call', prevState, action, nextState})\n return nextState\n }\n}\n\nlet nextMessageIndex = 0\nconst MAX_SLICE_LENGTH = 1000\n\n// Not gated.\nexport function addSessionErrorLog(did: string, event: AtpSessionEvent) {\n try {\n if (!Statsig.initializeCalled() || !Statsig.getStableID()) {\n return\n }\n const stack = (new Error().stack ?? '').slice(0, MAX_SLICE_LENGTH)\n Statsig.logEvent('session:error', null, {\n did,\n event,\n stack,\n })\n } catch (e) {\n console.error(e)\n }\n}\n\nexport function addSessionDebugLog(log: Log) {\n try {\n if (!Statsig.initializeCalled() || !Statsig.getStableID()) {\n // Drop these logs for now.\n return\n }\n if (!Statsig.checkGate('debug_session')) {\n return\n }\n const messageIndex = nextMessageIndex++\n const {type, ...content} = log\n let payload = JSON.stringify(content, replacer)\n\n let nextSliceIndex = 0\n while (payload.length > 0) {\n const sliceIndex = nextSliceIndex++\n const slice = payload.slice(0, MAX_SLICE_LENGTH)\n payload = payload.slice(MAX_SLICE_LENGTH)\n Statsig.logEvent('session:debug', null, {\n realmId,\n messageIndex: String(messageIndex),\n messageType: type,\n sliceIndex: String(sliceIndex),\n slice,\n })\n }\n } catch (e) {\n console.error(e)\n }\n}\n\nlet agentIds = new WeakMap