diff --git a/go/libkb/version.go b/go/libkb/version.go index d8b23ce1ad0e..e9c38269738b 100644 --- a/go/libkb/version.go +++ b/go/libkb/version.go @@ -4,4 +4,4 @@ package libkb // Version is the current version (should be MAJOR.MINOR.PATCH) -const Version = "6.6.0" +const Version = "6.7.0" diff --git a/shared/android/app/build.gradle b/shared/android/app/build.gradle index 43cfc1c7c8ad..29e9bfaf1434 100644 --- a/shared/android/app/build.gradle +++ b/shared/android/app/build.gradle @@ -4,7 +4,7 @@ apply plugin: "com.facebook.react" apply plugin: 'com.github.triplet.play' // KB: app version -def VERSION_NAME = "6.6.0" +def VERSION_NAME = "6.7.0" // KB: Number of commits, like ios Integer getVersionCode() { diff --git a/shared/app/global-errors.tsx b/shared/app/global-errors.tsx new file mode 100644 index 000000000000..23e6106654b6 --- /dev/null +++ b/shared/app/global-errors.tsx @@ -0,0 +1,332 @@ +import * as C from '@/constants' +import * as Kb from '@/common-adapters' +import * as React from 'react' +import logger from '@/logger' +import {ignoreDisconnectOverlay} from '@/local-debug' +import {useConfigState} from '@/stores/config' +import type {RPCError} from '@/util/errors' +import {settingsFeedbackTab} from '@/constants/settings' +import {useDaemonState} from '@/stores/daemon' + +type Size = 'Closed' | 'Small' | 'Big' + +const summaryForError = (err?: Error | RPCError) => err?.message ?? '' +const detailsForError = (err?: Error | RPCError) => err?.stack ?? '' + +const maxHeightForSize = (size: Size) => { + return { + Big: 900, + Closed: 0, + Small: 35, + }[size] +} + +const useData = () => { + const loggedIn = useConfigState(s => s.loggedIn) + const daemonError = useDaemonState(s => s.error) + const error = useConfigState(s => s.globalError) + const setGlobalError = useConfigState(s => s.dispatch.setGlobalError) + const clearModals = C.useRouterState(s => s.dispatch.clearModals) + const navigateAppend = C.useRouterState(s => s.dispatch.navigateAppend) + const onFeedback = React.useCallback(() => { + setGlobalError() + if (loggedIn) { + clearModals() + navigateAppend(settingsFeedbackTab) + } else { + navigateAppend('feedback') + } + }, [navigateAppend, clearModals, loggedIn, setGlobalError]) + const onDismiss = React.useCallback(() => { + setGlobalError() + }, [setGlobalError]) + + const [cachedSummary, setSummary] = React.useState(summaryForError(error)) + const [cachedDetails, setDetails] = React.useState(detailsForError(error)) + const [size, setSize] = React.useState('Closed') + const countdownTimerRef = React.useRef>(undefined) + + const clearCountdown = React.useCallback(() => { + countdownTimerRef.current && clearTimeout(countdownTimerRef.current) + countdownTimerRef.current = undefined + }, [countdownTimerRef]) + + const onExpandClick = React.useCallback(() => { + setSize('Big') + if (!C.isMobile) { + clearCountdown() + } + }, [clearCountdown]) + + const resetError = React.useCallback( + (newError: boolean) => { + setSize(newError ? 'Small' : 'Closed') + if (!C.isMobile) { + clearCountdown() + if (newError) { + countdownTimerRef.current = setTimeout(() => { + onDismiss() + }, 10000) + } + } + }, + [clearCountdown, onDismiss] + ) + + C.useOnUnMountOnce(() => { + clearCountdown() + }) + + C.useOnMountOnce(() => { + resetError(!!error) + }) + + React.useEffect(() => { + const id = setTimeout( + () => { + setDetails(detailsForError(error)) + if (!C.isMobile) { + setSummary(summaryForError(error)) + } + }, + error ? 0 : 7000 + ) // if it's set, do it immediately, if it's cleared set it in a bit + resetError(!!error) + return () => { + clearTimeout(id) + } + }, [error, resetError]) + + return { + cachedDetails, + cachedSummary, + daemonError, + error, + onDismiss, + onExpandClick, + onFeedback, + size, + } +} + +const GlobalError = () => { + const d = useData() + const {daemonError, error, onDismiss, onFeedback} = d + const {cachedDetails, cachedSummary, size, onExpandClick} = d + + if (size === 'Closed') { + return null + } + + if (!daemonError && !error) { + return null + } + + if (daemonError) { + if (C.isMobile) { + return null + } + if (ignoreDisconnectOverlay) { + logger.warn('Ignoring disconnect overlay') + return null + } + + const message = daemonError.message || 'Keybase is currently unreachable. Trying to reconnect you…' + return ( + + + + {message} + + + + + + + ) + } + + if (C.isMobile) { + return ( + + + + + + {size !== 'Big' && ( + + )} + {' '} + An error occurred. + + + + + + + + {size === 'Big' && ( + + + {error?.message} + {'\n\n'} + {cachedDetails} + + + )} + + ) + } + + const summary = cachedSummary + const details = cachedDetails + + let stylesContainer: Kb.Styles.StylesCrossPlatform + switch (size) { + case 'Big': + stylesContainer = styles.containerBig + break + case 'Small': + stylesContainer = styles.containerSmall + break + } + + return ( + + + + {summary} + + + {summary && ( + + )} + + + + {details} + + + + ) +} + +const styles = Kb.Styles.styleSheetCreate(() => { + const containerBase = { + left: 0, + overflow: 'hidden' as const, + position: 'absolute' as const, + right: 0, + top: 40, + zIndex: 1000, + ...Kb.Styles.transition('max-height'), + } + + return { + containerBig: Kb.Styles.platformStyles({ + isElectron: {...containerBase, maxHeight: maxHeightForSize('Big')}, + }), + containerOverlay: { + ...Kb.Styles.globalStyles.fillAbsolute, + zIndex: 1000, + }, + containerSmall: Kb.Styles.platformStyles({ + isElectron: {...containerBase, maxHeight: maxHeightForSize('Small')}, + }), + details: { + backgroundColor: Kb.Styles.globalColors.black, + color: Kb.Styles.globalColors.white_75, + ...Kb.Styles.padding(8, Kb.Styles.globalMargins.xlarge), + }, + innerContainer: { + ...Kb.Styles.globalStyles.flexBoxCenter, + backgroundColor: Kb.Styles.globalColors.black, + flex: 1, + gap: Kb.Styles.globalMargins.small, + minHeight: maxHeightForSize('Small'), + ...Kb.Styles.padding(Kb.Styles.globalMargins.xtiny, Kb.Styles.globalMargins.small), + }, + message: { + color: Kb.Styles.globalColors.white, + }, + mobileContainer: { + backgroundColor: Kb.Styles.globalColors.black, + position: 'absolute', + top: 0, + }, + mobileDetails: { + color: Kb.Styles.globalColors.white_75, + fontSize: 14, + lineHeight: 19, + ...Kb.Styles.padding(Kb.Styles.globalMargins.tiny, Kb.Styles.globalMargins.xtiny, Kb.Styles.globalMargins.xtiny), + }, + mobileErrorText: { + color: Kb.Styles.globalColors.white, + flex: 1, + }, + mobileErrorTextContainer: { + paddingBottom: Kb.Styles.globalMargins.xtiny, + position: 'relative', + }, + mobileSafeAreaView: { + backgroundColor: Kb.Styles.globalColors.transparent, + flexGrow: 0, + }, + mobileSummaryRow: { + alignItems: 'center', + flexShrink: 0, + justifyContent: 'center', + ...Kb.Styles.padding(Kb.Styles.globalMargins.tiny, Kb.Styles.globalMargins.xsmall), + }, + overlayFill: { + ...Kb.Styles.globalStyles.flexBoxCenter, + backgroundColor: Kb.Styles.globalColors.white, + flex: 1, + }, + overlayRow: { + ...Kb.Styles.globalStyles.flexBoxCenter, + backgroundColor: Kb.Styles.globalColors.blue, + padding: 8, + }, + summary: { + color: Kb.Styles.globalColors.white, + flex: 1, + }, + } as const +}) + +export default GlobalError diff --git a/shared/app/global-errors/hook.tsx b/shared/app/global-errors/hook.tsx deleted file mode 100644 index 646d872c8f90..000000000000 --- a/shared/app/global-errors/hook.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import * as C from '@/constants' -import * as React from 'react' -import {useConfigState} from '@/constants/config' -import type {RPCError} from '@/util/errors' -import {settingsFeedbackTab} from '@/constants/settings/util' -import {useDaemonState} from '@/constants/daemon' - -export type Size = 'Closed' | 'Small' | 'Big' - -const summaryForError = (err?: Error | RPCError) => err?.message ?? '' -const detailsForError = (err?: Error | RPCError) => err?.stack ?? '' - -const useData = () => { - const loggedIn = useConfigState(s => s.loggedIn) - const daemonError = useDaemonState(s => s.error) - const error = useConfigState(s => s.globalError) - const setGlobalError = useConfigState(s => s.dispatch.setGlobalError) - const clearModals = C.useRouterState(s => s.dispatch.clearModals) - const navigateAppend = C.useRouterState(s => s.dispatch.navigateAppend) - const onFeedback = React.useCallback(() => { - setGlobalError() - if (loggedIn) { - clearModals() - navigateAppend(settingsFeedbackTab) - } else { - navigateAppend('feedback') - } - }, [navigateAppend, clearModals, loggedIn, setGlobalError]) - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) - const onDismiss = React.useCallback(() => { - setGlobalError() - }, [setGlobalError]) - - const [cachedSummary, setSummary] = React.useState(summaryForError(error)) - const [cachedDetails, setDetails] = React.useState(detailsForError(error)) - const [size, setSize] = React.useState('Closed') - const countdownTimerRef = React.useRef>(undefined) - - const clearCountdown = React.useCallback(() => { - countdownTimerRef.current && clearTimeout(countdownTimerRef.current) - countdownTimerRef.current = undefined - }, [countdownTimerRef]) - - const onExpandClick = React.useCallback(() => { - setSize('Big') - if (!C.isMobile) { - clearCountdown() - } - }, [clearCountdown]) - - const resetError = React.useCallback( - (newError: boolean) => { - setSize(newError ? 'Small' : 'Closed') - if (!C.isMobile) { - clearCountdown() - if (newError) { - countdownTimerRef.current = setTimeout(() => { - onDismiss() - }, 10000) - } - } - }, - [clearCountdown, onDismiss] - ) - - C.useOnUnMountOnce(() => { - clearCountdown() - }) - - C.useOnMountOnce(() => { - resetError(!!error) - }) - - React.useEffect(() => { - const id = setTimeout( - () => { - setDetails(detailsForError(error)) - if (!C.isMobile) { - setSummary(summaryForError(error)) - } - }, - error ? 0 : 7000 - ) // if it's set, do it immediately, if it's cleared set it in a bit - resetError(!!error) - return () => { - clearTimeout(id) - } - }, [error, resetError]) - - return { - cachedDetails, - cachedSummary, - copyToClipboard, - daemonError, - error, - onDismiss, - onExpandClick, - onFeedback, - size, - } -} - -export default useData diff --git a/shared/app/global-errors/index.d.ts b/shared/app/global-errors/index.d.ts deleted file mode 100644 index 695dd42f1e55..000000000000 --- a/shared/app/global-errors/index.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type * as React from 'react' -declare const GlobalError: () => React.ReactNode -export default GlobalError diff --git a/shared/app/global-errors/index.desktop.tsx b/shared/app/global-errors/index.desktop.tsx deleted file mode 100644 index 815396255845..000000000000 --- a/shared/app/global-errors/index.desktop.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import logger from '@/logger' -import * as Kb from '@/common-adapters' -import {ignoreDisconnectOverlay} from '@/local-debug.desktop' -import useData, {type Size} from './hook' - -const maxHeightForSize = (size: Size) => { - return { - Big: 900, - Closed: 0, - Small: 35, - }[size] -} - -const GlobalError = () => { - const d = useData() - const {daemonError, error, onDismiss, onFeedback} = d - const {cachedDetails, cachedSummary, size, onExpandClick} = d - - if (size === 'Closed') { - return null - } - - if (!daemonError && !error) { - return null - } - - if (daemonError) { - if (ignoreDisconnectOverlay) { - logger.warn('Ignoring disconnect overlay') - return null - } - - const message = daemonError.message || 'Keybase is currently unreachable. Trying to reconnect you…' - return ( - - - - {message} - - - - - - - ) - } else { - const summary = cachedSummary - const details = cachedDetails - - let stylesContainer: Kb.Styles.StylesCrossPlatform - switch (size) { - case 'Big': - stylesContainer = styles.containerBig - break - case 'Small': - stylesContainer = styles.containerSmall - break - } - - return ( - - - - {summary} - - - {summary && ( - - )} - - - - {details} - - - - ) - } -} - -const styles = Kb.Styles.styleSheetCreate(() => { - const containerBase = { - ...Kb.Styles.globalStyles.flexBoxColumn, - left: 0, - overflow: 'hidden', - position: 'absolute', - right: 0, - top: 40, - zIndex: 1000, - ...Kb.Styles.transition('max-height'), - } as const - - return { - closeIcon: { - position: 'absolute', - right: Kb.Styles.globalMargins.xsmall, - top: 10, - }, - containerBig: Kb.Styles.platformStyles({ - isElectron: {...containerBase, maxHeight: maxHeightForSize('Big')}, - }), - containerClosed: Kb.Styles.platformStyles({ - isElectron: {...containerBase, maxHeight: maxHeightForSize('Closed')}, - }), - containerOverlay: { - ...Kb.Styles.globalStyles.flexBoxColumn, - bottom: 0, - left: 0, - position: 'absolute', - right: 0, - top: 0, - zIndex: 1000, - }, - containerSmall: Kb.Styles.platformStyles({ - isElectron: {...containerBase, maxHeight: maxHeightForSize('Small')}, - }), - details: { - backgroundColor: Kb.Styles.globalColors.black, - color: Kb.Styles.globalColors.white_75, - padding: 8, - paddingLeft: Kb.Styles.globalMargins.xlarge, - paddingRight: Kb.Styles.globalMargins.xlarge, - }, - feedbackButton: { - marginRight: Kb.Styles.globalMargins.large, - }, - innerContainer: { - ...Kb.Styles.globalStyles.flexBoxRow, - alignItems: 'center', - backgroundColor: Kb.Styles.globalColors.black, - flex: 1, - justifyContent: 'center', - minHeight: maxHeightForSize('Small'), - padding: Kb.Styles.globalMargins.xtiny, - position: 'relative', - }, - message: { - color: Kb.Styles.globalColors.white, - }, - overlayFill: { - ...Kb.Styles.globalStyles.flexBoxColumn, - alignItems: 'center', - backgroundColor: Kb.Styles.globalColors.white, - flex: 1, - justifyContent: 'center', - }, - overlayRow: { - ...Kb.Styles.globalStyles.flexBoxRow, - alignItems: 'center', - backgroundColor: Kb.Styles.globalColors.blue, - justifyContent: 'center', - padding: 8, - }, - summary: { - color: Kb.Styles.globalColors.white, - flex: 1, - }, - summaryRow: { - ...Kb.Styles.globalStyles.flexBoxRow, - alignItems: 'center', - flex: 1, - justifyContent: 'center', - padding: Kb.Styles.globalMargins.xtiny, - position: 'relative', - }, - summaryRowError: { - backgroundColor: Kb.Styles.globalColors.black, - minHeight: maxHeightForSize('Small'), - }, - } as const -}) - -export default GlobalError diff --git a/shared/app/global-errors/index.native.tsx b/shared/app/global-errors/index.native.tsx deleted file mode 100644 index 7555c1ef588c..000000000000 --- a/shared/app/global-errors/index.native.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import * as Kb from '@/common-adapters' -import NativeScrollView from '@/common-adapters/scroll-view.native' -import useData from './hook' - -const GlobalError = () => { - const d = useData() - const {daemonError, error, onDismiss, onFeedback} = d - const {cachedDetails, size, onExpandClick} = d - - if (size === 'Closed') { - return null - } - - if (!daemonError && !error) { - return null - } - - return ( - - - - - - {size !== 'Big' && ( - - )} - {' '} - An error occurred. - - - - - - - - {size === 'Big' && ( - - - {error?.message} - {'\n\n'} - {cachedDetails} - - - )} - - ) -} - -const styles = Kb.Styles.styleSheetCreate( - () => - ({ - container: { - backgroundColor: Kb.Styles.globalColors.black, - position: 'absolute', - top: 0, - }, - details: { - color: Kb.Styles.globalColors.white_75, - fontSize: 14, - lineHeight: 19, - padding: Kb.Styles.globalMargins.xtiny, - paddingTop: Kb.Styles.globalMargins.tiny, - }, - errorText: { - color: Kb.Styles.globalColors.white, - flex: 1, - }, - errorTextContainer: { - paddingBottom: Kb.Styles.globalMargins.xtiny, - position: 'relative', - }, - itemText: { - color: Kb.Styles.globalColors.white, - fontSize: 8, - lineHeight: 8, - }, - safeAreaView: { - backgroundColor: Kb.Styles.globalColors.transparent, - flexGrow: 0, - }, - summaryRow: { - ...Kb.Styles.globalStyles.flexBoxRow, - alignItems: 'center', - flexShrink: 0, - justifyContent: 'center', - paddingBottom: Kb.Styles.globalMargins.tiny, - paddingLeft: Kb.Styles.globalMargins.xsmall, - paddingRight: Kb.Styles.globalMargins.xsmall, - paddingTop: Kb.Styles.globalMargins.tiny, - }, - }) as const -) - -export default GlobalError diff --git a/shared/app/index.native.tsx b/shared/app/index.native.tsx index 1152498d3a5f..d9f009250dad 100644 --- a/shared/app/index.native.tsx +++ b/shared/app/index.native.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as Kb from '@/common-adapters' import * as React from 'react' -import {useDeepLinksState} from '@/constants/deeplinks' +import {handleAppLink} from '@/constants/deeplinks' import Main from './main.native' import {KeyboardProvider} from 'react-native-keyboard-controller' import Animated, {ReducedMotionConfig, ReduceMotion} from 'react-native-reanimated' @@ -18,9 +18,8 @@ import ServiceDecoration from '@/common-adapters/markdown/service-decoration' import {useUnmountAll} from '@/util/debug-react' import {darkModeSupported, guiConfig} from 'react-native-kb' import {install} from 'react-native-kb' -import {useEngineState} from '@/constants/engine' -import * as DarkMode from '@/constants/darkmode' -import {initPlatformListener} from '@/constants/platform-specific' +import * as DarkMode from '@/stores/darkmode' +import {initPlatformListener, onEngineConnected, onEngineDisconnected, onEngineIncoming} from '@/constants/init/index.native' import logger from '@/logger' logger.info('INIT App index module load') @@ -110,7 +109,6 @@ const StoreHelper = (p: {children: React.ReactNode}): React.ReactNode => { const {children} = p useDarkHookup() useKeyboardHookup() - const handleAppLink = useDeepLinksState(s => s.dispatch.handleAppLink) React.useEffect(() => { const linkingSub = Linking.addEventListener('url', ({url}: {url: string}) => { @@ -119,7 +117,7 @@ const StoreHelper = (p: {children: React.ReactNode}): React.ReactNode => { return () => { linkingSub.remove() } - }, [handleAppLink]) + }, []) return children } @@ -139,11 +137,11 @@ const useInit = () => { const {batch} = C.useWaitingState.getState().dispatch const eng = makeEngine(batch, c => { if (c) { - useEngineState.getState().dispatch.onEngineConnected() + onEngineConnected() } else { - useEngineState.getState().dispatch.onEngineDisconnected() + onEngineDisconnected() } - }) + }, onEngineIncoming) initPlatformListener() eng.listenersAreReady() diff --git a/shared/app/main.desktop.tsx b/shared/app/main.desktop.tsx index 16ef0670516d..12bd8e36da36 100644 --- a/shared/app/main.desktop.tsx +++ b/shared/app/main.desktop.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import Router from '@/router-v2/router' -import {useDarkModeState} from '@/constants/darkmode' +import {useDarkModeState} from '@/stores/darkmode' import ResetModal from '../login/reset/modal' import GlobalError from './global-errors' import OutOfDate from './out-of-date' diff --git a/shared/app/out-of-date.tsx b/shared/app/out-of-date.tsx index 3287a86e87d1..70b33bb01693 100644 --- a/shared/app/out-of-date.tsx +++ b/shared/app/out-of-date.tsx @@ -3,7 +3,7 @@ import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' import logger from '@/logger' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const styles = Kb.Styles.styleSheetCreate(() => ({ container: { @@ -62,7 +62,7 @@ const OutOfDate = () => { C.ignorePromise(f()) }) - const onOpenAppStore = useConfigState(s => s.dispatch.dynamic.openAppStore) + const onOpenAppStore = useConfigState(s => s.dispatch.defer.openAppStore) return status !== 'critical' ? null : ( diff --git a/shared/app/runtime-stats.tsx b/shared/app/runtime-stats.tsx index 55e94eee4a96..2ea975b665cc 100644 --- a/shared/app/runtime-stats.tsx +++ b/shared/app/runtime-stats.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const isIPhoneX = false as boolean // import lagRadar from 'lag-radar' diff --git a/shared/chat/audio/audio-recorder.native.tsx b/shared/chat/audio/audio-recorder.native.tsx index b68e7f68ade6..8c3a2d197c98 100644 --- a/shared/chat/audio/audio-recorder.native.tsx +++ b/shared/chat/audio/audio-recorder.native.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {Portal} from '@/common-adapters/portal.native' diff --git a/shared/chat/blocking/block-modal.tsx b/shared/chat/blocking/block-modal.tsx index e0c695937385..0a9dfdd64d03 100644 --- a/shared/chat/blocking/block-modal.tsx +++ b/shared/chat/blocking/block-modal.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' -import * as Chat from '@/constants/chat2' -import {useTeamsState} from '@/constants/teams' -import {useUsersState} from '@/constants/users' +import * as Chat from '@/stores/chat2' +import {useTeamsState} from '@/stores/teams' +import {useUsersState} from '@/stores/users' // Type for extra RouteProp passed to block modal sometimes when launching the // modal from specific places from the app. diff --git a/shared/chat/blocking/invitation-to-block.tsx b/shared/chat/blocking/invitation-to-block.tsx index e7ba0b2f0950..9e124e37d688 100644 --- a/shared/chat/blocking/invitation-to-block.tsx +++ b/shared/chat/blocking/invitation-to-block.tsx @@ -1,8 +1,8 @@ -import * as Chat from '@/constants/chat2' -import {useProfileState} from '@/constants/profile' +import * as Chat from '@/stores/chat2' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' const BlockButtons = () => { const nav = useSafeNavigation() diff --git a/shared/chat/chat-button.tsx b/shared/chat/chat-button.tsx index 1524bf4e37a2..d565cffd15b4 100644 --- a/shared/chat/chat-button.tsx +++ b/shared/chat/chat-button.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Styles from '@/styles' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import WaitingButton from '@/common-adapters/waiting-button' import Icon from '@/common-adapters/icon' diff --git a/shared/chat/conversation/attachment-fullscreen/hooks.tsx b/shared/chat/conversation/attachment-fullscreen/hooks.tsx index 584d3cdeacc9..3c6c21497450 100644 --- a/shared/chat/conversation/attachment-fullscreen/hooks.tsx +++ b/shared/chat/conversation/attachment-fullscreen/hooks.tsx @@ -1,9 +1,9 @@ import * as React from 'react' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import type * as T from '@/constants/types' import {maxWidth, maxHeight} from '../messages/attachment/shared' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const blankMessage = Chat.makeMessageAttachment({}) export const useData = (initialOrdinal: T.Chat.Ordinal) => { @@ -37,7 +37,7 @@ export const useData = (initialOrdinal: T.Chat.Ordinal) => { }, [onSwitchAttachment]) const openLocalPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop + s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const navigateUp = C.useRouterState(s => s.dispatch.navigateUp) const showInfoPanel = Chat.useChatContext(s => s.dispatch.showInfoPanel) diff --git a/shared/chat/conversation/attachment-get-titles.tsx b/shared/chat/conversation/attachment-get-titles.tsx index f5658e3078a8..609cbee8610b 100644 --- a/shared/chat/conversation/attachment-get-titles.tsx +++ b/shared/chat/conversation/attachment-get-titles.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/bot/confirm.tsx b/shared/chat/conversation/bot/confirm.tsx index 4f0c8230ebbc..6d7c001fb176 100644 --- a/shared/chat/conversation/bot/confirm.tsx +++ b/shared/chat/conversation/bot/confirm.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import type * as T from '@/constants/types' import {useBotConversationIDKey} from './install' diff --git a/shared/chat/conversation/bot/install.tsx b/shared/chat/conversation/bot/install.tsx index c1e24c343beb..88c1f3a90369 100644 --- a/shared/chat/conversation/bot/install.tsx +++ b/shared/chat/conversation/bot/install.tsx @@ -1,12 +1,12 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import ChannelPicker from './channel-picker' import openURL from '@/util/open-url' import * as T from '@/constants/types' -import {useBotsState} from '@/constants/bots' +import {useBotsState} from '@/stores/bots' import {useAllChannelMetas} from '@/teams/common/channel-hooks' const RestrictedItem = '---RESTRICTED---' diff --git a/shared/chat/conversation/bot/search.tsx b/shared/chat/conversation/bot/search.tsx index 3a3dd2ab36f3..ec1309ad25f6 100644 --- a/shared/chat/conversation/bot/search.tsx +++ b/shared/chat/conversation/bot/search.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import debounce from 'lodash/debounce' import type * as T from '@/constants/types' import {Bot} from '../info-panel/bot' -import {getFeaturedSorted, useBotsState} from '@/constants/bots' +import {getFeaturedSorted, useBotsState} from '@/stores/bots' type Props = {teamID?: T.Teams.TeamID} diff --git a/shared/chat/conversation/bot/team-picker.tsx b/shared/chat/conversation/bot/team-picker.tsx index 23c869eeff54..b62b38be91c9 100644 --- a/shared/chat/conversation/bot/team-picker.tsx +++ b/shared/chat/conversation/bot/team-picker.tsx @@ -5,7 +5,7 @@ import * as T from '@/constants/types' import {Avatars, TeamAvatar} from '@/chat/avatars' import debounce from 'lodash/debounce' import logger from '@/logger' -import {useBotsState} from '@/constants/bots' +import {useBotsState} from '@/stores/bots' type Props = {botUsername: string} diff --git a/shared/chat/conversation/bottom-banner.tsx b/shared/chat/conversation/bottom-banner.tsx index 3b845cd09e92..878d44341538 100644 --- a/shared/chat/conversation/bottom-banner.tsx +++ b/shared/chat/conversation/bottom-banner.tsx @@ -1,12 +1,13 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import _openSMS from '@/util/sms' import {assertionToDisplay} from '@/common-adapters/usernames' import type {Props as TextProps} from '@/common-adapters/text' -import {useUsersState} from '@/constants/users' -import {useFollowerState} from '@/constants/followers' +import {useUsersState} from '@/stores/users' +import {useFollowerState} from '@/stores/followers' +import {showShareActionSheet} from '@/util/platform-specific' const installMessage = `I sent you encrypted messages on Keybase. You can install it here: https://keybase.io/phone-app` @@ -16,7 +17,7 @@ const Invite = () => { const users = participantInfoAll.filter(p => p.includes('@')) const openShareSheet = () => { - C.PlatformSpecific.showShareActionSheet({ + showShareActionSheet({ message: installMessage, mimeType: 'text/plain', }) diff --git a/shared/chat/conversation/command-markdown.tsx b/shared/chat/conversation/command-markdown.tsx index 1f434bc4d280..10c20f1c5509 100644 --- a/shared/chat/conversation/command-markdown.tsx +++ b/shared/chat/conversation/command-markdown.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' const CommandMarkdown = () => { diff --git a/shared/chat/conversation/command-status.tsx b/shared/chat/conversation/command-status.tsx index 9a005c52a13c..dc67888d461c 100644 --- a/shared/chat/conversation/command-status.tsx +++ b/shared/chat/conversation/command-status.tsx @@ -1,7 +1,7 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const empty = { actions: [], @@ -13,7 +13,7 @@ const Container = () => { const info = Chat.useChatContext(s => s.commandStatus) const _info = info || empty - const onOpenAppSettings = useConfigState(s => s.dispatch.dynamic.openAppSettings) + const onOpenAppSettings = useConfigState(s => s.dispatch.defer.openAppSettings) const setCommandStatusInfo = Chat.useChatContext(s => s.dispatch.setCommandStatusInfo) const onCancel = () => { setCommandStatusInfo() diff --git a/shared/chat/conversation/container.tsx b/shared/chat/conversation/container.tsx index 0cb2f2d21908..d892d3f39949 100644 --- a/shared/chat/conversation/container.tsx +++ b/shared/chat/conversation/container.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import Normal from './normal/container' import NoConversation from './no-conversation' import Error from './error' diff --git a/shared/chat/conversation/error.tsx b/shared/chat/conversation/error.tsx index c4762dc4f87a..0c9266bb235c 100644 --- a/shared/chat/conversation/error.tsx +++ b/shared/chat/conversation/error.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' const ConversationError = () => { diff --git a/shared/chat/conversation/fwd-msg.tsx b/shared/chat/conversation/fwd-msg.tsx index 53ec16deb001..265d9e69c756 100644 --- a/shared/chat/conversation/fwd-msg.tsx +++ b/shared/chat/conversation/fwd-msg.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' diff --git a/shared/chat/conversation/giphy/hooks.tsx b/shared/chat/conversation/giphy/hooks.tsx index bad968dd56aa..b2aea6c79b09 100644 --- a/shared/chat/conversation/giphy/hooks.tsx +++ b/shared/chat/conversation/giphy/hooks.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' export const useHooks = () => { const giphy = Chat.useChatContext(s => s.giphyResult) diff --git a/shared/chat/conversation/header-area/index.native.tsx b/shared/chat/conversation/header-area/index.native.tsx index c2b82732a4aa..ceaaff316fdd 100644 --- a/shared/chat/conversation/header-area/index.native.tsx +++ b/shared/chat/conversation/header-area/index.native.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useProfileState} from '@/constants/profile' +import * as Chat from '@/stores/chat2' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' import * as React from 'react' import type {HeaderBackButtonProps} from '@react-navigation/elements' @@ -9,8 +9,8 @@ import {Keyboard} from 'react-native' // import {DebugChatDumpContext} from '@/constants/chat2/debug' import {assertionToDisplay} from '@/common-adapters/usernames' import {useSafeAreaFrame} from 'react-native-safe-area-context' -import {useUsersState} from '@/constants/users' -import {useCurrentUserState} from '@/constants/current-user' +import {useUsersState} from '@/stores/users' +import {useCurrentUserState} from '@/stores/current-user' export const HeaderAreaRight = () => { const conversationIDKey = Chat.useChatContext(s => s.id) diff --git a/shared/chat/conversation/info-panel/add-people.tsx b/shared/chat/conversation/info-panel/add-people.tsx index 9e7492a91f92..724cd29ec1c1 100644 --- a/shared/chat/conversation/info-panel/add-people.tsx +++ b/shared/chat/conversation/info-panel/add-people.tsx @@ -1,5 +1,5 @@ -import * as Chat from '@/constants/chat2' -import {useTeamsState} from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import {useTeamsState} from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/info-panel/add-to-channel.tsx b/shared/chat/conversation/info-panel/add-to-channel.tsx index 59bfd1d8e9ca..2109aaae2717 100644 --- a/shared/chat/conversation/info-panel/add-to-channel.tsx +++ b/shared/chat/conversation/info-panel/add-to-channel.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' import * as T from '@/constants/types' diff --git a/shared/chat/conversation/info-panel/attachments.tsx b/shared/chat/conversation/info-panel/attachments.tsx index 451fa0a9bf98..61b7fb7608a9 100644 --- a/shared/chat/conversation/info-panel/attachments.tsx +++ b/shared/chat/conversation/info-panel/attachments.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import type {StylesTextCrossPlatform} from '@/common-adapters/text' import * as T from '@/constants/types' @@ -8,7 +8,7 @@ import chunk from 'lodash/chunk' import {formatAudioRecordDuration, formatTimeForMessages} from '@/util/timestamp' import {infoPanelWidth} from './common' import {useMessagePopup} from '../messages/message-popup' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' type Props = { commonSections: ReadonlyArray
@@ -499,7 +499,7 @@ export const useAttachmentSections = ( } const openLocalPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop + s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const onShowInFinder = (message: T.Chat.MessageAttachment) => message.downloadPath && openLocalPathInSystemFileManagerDesktop?.(message.downloadPath) diff --git a/shared/chat/conversation/info-panel/bot.tsx b/shared/chat/conversation/info-panel/bot.tsx index 9acfef03a51a..1152170c6d5d 100644 --- a/shared/chat/conversation/info-panel/bot.tsx +++ b/shared/chat/conversation/info-panel/bot.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Teams from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' import type * as T from '@/constants/types' -import {getFeaturedSorted, useBotsState} from '@/constants/bots' -import {useUsersState} from '@/constants/users' +import {getFeaturedSorted, useBotsState} from '@/stores/bots' +import {useUsersState} from '@/stores/users' type AddToChannelProps = { conversationIDKey: T.Chat.ConversationIDKey diff --git a/shared/chat/conversation/info-panel/common.tsx b/shared/chat/conversation/info-panel/common.tsx index 1de1bb893716..d01c196b8579 100644 --- a/shared/chat/conversation/info-panel/common.tsx +++ b/shared/chat/conversation/info-panel/common.tsx @@ -1,5 +1,5 @@ -import type * as Chat from '@/constants/chat2' -import {useTeamsState} from '@/constants/teams' +import type * as Chat from '@/stores/chat2' +import {useTeamsState} from '@/stores/teams' import * as React from 'react' import * as Styles from '@/styles' import type * as T from '@/constants/types' diff --git a/shared/chat/conversation/info-panel/header.tsx b/shared/chat/conversation/info-panel/header.tsx index 121f86d02404..6d45cc0ca533 100644 --- a/shared/chat/conversation/info-panel/header.tsx +++ b/shared/chat/conversation/info-panel/header.tsx @@ -1,6 +1,6 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import InfoPanelMenu from './menu' import * as InfoPanelCommon from './common' diff --git a/shared/chat/conversation/info-panel/index.tsx b/shared/chat/conversation/info-panel/index.tsx index f2bcd79a3346..0bf1adf3fad2 100644 --- a/shared/chat/conversation/info-panel/index.tsx +++ b/shared/chat/conversation/info-panel/index.tsx @@ -1,6 +1,6 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import {AdhocHeader, TeamHeader} from './header' import SettingsList from './settings' diff --git a/shared/chat/conversation/info-panel/members.tsx b/shared/chat/conversation/info-panel/members.tsx index 5c1242ee02d7..72b3a4405985 100644 --- a/shared/chat/conversation/info-panel/members.tsx +++ b/shared/chat/conversation/info-panel/members.tsx @@ -1,12 +1,12 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useProfileState} from '@/constants/profile' -import * as Teams from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import {useProfileState} from '@/stores/profile' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import Participant from './participant' -import {useUsersState} from '@/constants/users' +import {useUsersState} from '@/stores/users' type Props = { commonSections: ReadonlyArray
diff --git a/shared/chat/conversation/info-panel/menu.tsx b/shared/chat/conversation/info-panel/menu.tsx index eac5285c62f0..0d1bd8e20c50 100644 --- a/shared/chat/conversation/info-panel/menu.tsx +++ b/shared/chat/conversation/info-panel/menu.tsx @@ -1,14 +1,14 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as T from '@/constants/types' import * as InfoPanelCommon from './common' import {Avatars, TeamAvatar} from '@/chat/avatars' import {TeamsSubscriberMountOnly} from '@/teams/subscriber' -import {useUsersState} from '@/constants/users' -import {useCurrentUserState} from '@/constants/current-user' +import {useUsersState} from '@/stores/users' +import {useCurrentUserState} from '@/stores/current-user' export type OwnProps = { attachTo?: React.RefObject diff --git a/shared/chat/conversation/info-panel/settings/index.tsx b/shared/chat/conversation/info-panel/settings/index.tsx index 6535768ef672..7656d2a941d9 100644 --- a/shared/chat/conversation/info-panel/settings/index.tsx +++ b/shared/chat/conversation/info-panel/settings/index.tsx @@ -1,13 +1,13 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as T from '@/constants/types' import * as React from 'react' import MinWriterRole from './min-writer-role' import Notifications from './notifications' import RetentionPicker from '@/teams/team/settings-tab/retention' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type EntityType = 'adhoc' | 'small team' | 'channel' type SettingsPanelProps = {isPreview: boolean} diff --git a/shared/chat/conversation/info-panel/settings/min-writer-role.tsx b/shared/chat/conversation/info-panel/settings/min-writer-role.tsx index a25934607682..56c8a09e4566 100644 --- a/shared/chat/conversation/info-panel/settings/min-writer-role.tsx +++ b/shared/chat/conversation/info-panel/settings/min-writer-role.tsx @@ -1,6 +1,6 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as Style from '@/styles' import type * as T from '@/constants/types' diff --git a/shared/chat/conversation/info-panel/settings/notifications.tsx b/shared/chat/conversation/info-panel/settings/notifications.tsx index 53bbf518206f..fdfcb3a0bc94 100644 --- a/shared/chat/conversation/info-panel/settings/notifications.tsx +++ b/shared/chat/conversation/info-panel/settings/notifications.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' diff --git a/shared/chat/conversation/input-area/container.tsx b/shared/chat/conversation/input-area/container.tsx index 7ce09e75c96b..e318566ef103 100644 --- a/shared/chat/conversation/input-area/container.tsx +++ b/shared/chat/conversation/input-area/container.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import Normal from './normal2' import Preview from './preview' import ThreadSearch from '../search' diff --git a/shared/chat/conversation/input-area/location-popup.d.ts b/shared/chat/conversation/input-area/location-popup.d.ts new file mode 100644 index 000000000000..251868e41272 --- /dev/null +++ b/shared/chat/conversation/input-area/location-popup.d.ts @@ -0,0 +1,3 @@ +import type * as React from 'react' +declare const LocationPopup: () => React.ReactNode +export default LocationPopup diff --git a/shared/chat/conversation/input-area/location-popup.desktop.tsx b/shared/chat/conversation/input-area/location-popup.desktop.tsx new file mode 100644 index 000000000000..c07558b9f77f --- /dev/null +++ b/shared/chat/conversation/input-area/location-popup.desktop.tsx @@ -0,0 +1,2 @@ +const LocationPopup = () => null +export default LocationPopup diff --git a/shared/chat/conversation/input-area/location-popup.tsx b/shared/chat/conversation/input-area/location-popup.native.tsx similarity index 70% rename from shared/chat/conversation/input-area/location-popup.tsx rename to shared/chat/conversation/input-area/location-popup.native.tsx index fff570f758c7..ec5fef04455f 100644 --- a/shared/chat/conversation/input-area/location-popup.tsx +++ b/shared/chat/conversation/input-area/location-popup.native.tsx @@ -1,11 +1,53 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' +import logger from '@/logger' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import LocationMap from '@/chat/location-map' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' +import {requestLocationPermission} from '@/util/platform-specific' +import * as ExpoLocation from 'expo-location' + +const useWatchPosition = (conversationIDKey: T.Chat.ConversationIDKey) => { + const updateLastCoord = Chat.useChatState(s => s.dispatch.updateLastCoord) + const setCommandStatusInfo = Chat.useChatContext(s => s.dispatch.setCommandStatusInfo) + React.useEffect(() => { + let unsub = () => {} + logger.info('[location] perms check due to map') + const f = async () => { + try { + await requestLocationPermission(T.RPCChat.UIWatchPositionPerm.base) + const sub = await ExpoLocation.watchPositionAsync( + {accuracy: ExpoLocation.LocationAccuracy.Highest}, + (location: ExpoLocation.LocationObject) => { + const coord = { + accuracy: Math.floor(location.coords.accuracy ?? 0), + lat: location.coords.latitude, + lon: location.coords.longitude, + } + updateLastCoord(coord) + } + ) + unsub = () => sub.remove() + } catch (_error) { + const error = _error as {message?: string} + logger.info('failed to get location: ' + error.message) + setCommandStatusInfo({ + actions: [T.RPCChat.UICommandStatusActionTyp.appsettings], + displayText: `Failed to access location. ${error.message}`, + displayType: T.RPCChat.UICommandStatusDisplayTyp.error, + }) + } + } + + C.ignorePromise(f()) + return () => { + unsub() + } + }, [conversationIDKey, updateLastCoord, setCommandStatusInfo]) +} const LocationPopup = () => { const conversationIDKey = Chat.useChatContext(s => s.id) @@ -20,24 +62,14 @@ const LocationPopup = () => { const onClose = () => { clearModals() } - const onSettings = useConfigState(s => s.dispatch.dynamic.openAppSettings) + const onSettings = useConfigState(s => s.dispatch.defer.openAppSettings) const sendMessage = Chat.useChatContext(s => s.dispatch.sendMessage) const onLocationShare = (duration: string) => { onClose() sendMessage(duration ? `/location live ${duration}` : '/location') } - React.useEffect(() => { - let unwatch: undefined | (() => void) - C.PlatformSpecific.watchPositionForMap(conversationIDKey) - .then(unsub => { - unwatch = unsub - }) - .catch(() => {}) - return () => { - unwatch?.() - } - }, [conversationIDKey]) + useWatchPosition(conversationIDKey) const width = Math.ceil(Kb.Styles.dimensionWidth) const height = Math.ceil(Kb.Styles.dimensionHeight - 320) diff --git a/shared/chat/conversation/input-area/normal2/index.tsx b/shared/chat/conversation/input-area/normal2/index.tsx index 8ceea809bb21..248c3e9db0bd 100644 --- a/shared/chat/conversation/input-area/normal2/index.tsx +++ b/shared/chat/conversation/input-area/normal2/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import CommandMarkdown from '../../command-markdown' @@ -13,7 +13,7 @@ import {infoPanelWidthTablet} from '../../info-panel/common' import {assertionToDisplay} from '@/common-adapters/usernames' import {FocusContext, ScrollContext} from '@/chat/conversation/normal/context' import type {RefType as Input2Ref} from '@/common-adapters/input2' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' const useHintText = (p: { isExploding: boolean diff --git a/shared/chat/conversation/input-area/normal2/moremenu-popup.tsx b/shared/chat/conversation/input-area/normal2/moremenu-popup.native.tsx similarity index 97% rename from shared/chat/conversation/input-area/normal2/moremenu-popup.tsx rename to shared/chat/conversation/input-area/normal2/moremenu-popup.native.tsx index fc790cc73f5a..e75a2301b57f 100644 --- a/shared/chat/conversation/input-area/normal2/moremenu-popup.tsx +++ b/shared/chat/conversation/input-area/normal2/moremenu-popup.native.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' type Props = { diff --git a/shared/chat/conversation/input-area/normal2/platform-input.desktop.tsx b/shared/chat/conversation/input-area/normal2/platform-input.desktop.tsx index d56a9ef8bba8..5533d8515bec 100644 --- a/shared/chat/conversation/input-area/normal2/platform-input.desktop.tsx +++ b/shared/chat/conversation/input-area/normal2/platform-input.desktop.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import * as React from 'react' diff --git a/shared/chat/conversation/input-area/normal2/platform-input.native.tsx b/shared/chat/conversation/input-area/normal2/platform-input.native.tsx index 4194566d7d99..496f7ebb8c5a 100644 --- a/shared/chat/conversation/input-area/normal2/platform-input.native.tsx +++ b/shared/chat/conversation/input-area/normal2/platform-input.native.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import AudioRecorder from '@/chat/audio/audio-recorder.native' import FilePickerPopup from '../filepicker-popup' import {onHWKeyPressed, removeOnHWKeyPressed} from 'react-native-kb' -import MoreMenuPopup from './moremenu-popup' +import MoreMenuPopup from './moremenu-popup.native' import SetExplodingMessagePicker from './set-explode-popup' import Typing from './typing' import type * as ImagePicker from 'expo-image-picker' @@ -28,7 +28,7 @@ import logger from '@/logger' import {AudioSendWrapper} from '@/chat/audio/audio-send.native' import {usePickerState} from '@/chat/emoji-picker/use-picker' import type {RefType as Input2Ref, Props as Input2Props} from '@/common-adapters/input2' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const singleLineHeight = 36 const threeLineHeight = 78 diff --git a/shared/chat/conversation/input-area/normal2/set-explode-popup/hooks.tsx b/shared/chat/conversation/input-area/normal2/set-explode-popup/hooks.tsx index 1552f0f23d43..781e2a289d60 100644 --- a/shared/chat/conversation/input-area/normal2/set-explode-popup/hooks.tsx +++ b/shared/chat/conversation/input-area/normal2/set-explode-popup/hooks.tsx @@ -1,9 +1,14 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import type * as T from '@/constants/types' import type {Props} from '.' -const messageExplodeDescriptions: T.Chat.MessageExplodeDescription[] = [ +export type MessageExplodeDescription = { + text: string + seconds: number +} + +const messageExplodeDescriptions: MessageExplodeDescription[] = [ {seconds: 30, text: '30 seconds'}, {seconds: 300, text: '5 minutes'}, {seconds: 3600, text: '60 minutes'}, diff --git a/shared/chat/conversation/input-area/normal2/set-explode-popup/index.desktop.tsx b/shared/chat/conversation/input-area/normal2/set-explode-popup/index.desktop.tsx index ed46e8c6e1f2..255ad7b0c55e 100644 --- a/shared/chat/conversation/input-area/normal2/set-explode-popup/index.desktop.tsx +++ b/shared/chat/conversation/input-area/normal2/set-explode-popup/index.desktop.tsx @@ -1,7 +1,7 @@ -import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import type {Props} from '.' import useHooks from './hooks' +import type {MessageExplodeDescription} from './hooks' const quantityTextStyle = Kb.Styles.platformStyles({ common: { @@ -13,7 +13,7 @@ const quantityTextStyle = Kb.Styles.platformStyles({ }) type ItemProps = { - desc: T.Chat.MessageExplodeDescription + desc: MessageExplodeDescription selected: boolean } diff --git a/shared/chat/conversation/input-area/normal2/typing.tsx b/shared/chat/conversation/input-area/normal2/typing.tsx index b9a42ecff2ee..c274f4396aae 100644 --- a/shared/chat/conversation/input-area/normal2/typing.tsx +++ b/shared/chat/conversation/input-area/normal2/typing.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/input-area/preview.tsx b/shared/chat/conversation/input-area/preview.tsx index 3f118f4469ac..64e25419c5f8 100644 --- a/shared/chat/conversation/input-area/preview.tsx +++ b/shared/chat/conversation/input-area/preview.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/input-area/suggestors/channels.tsx b/shared/chat/conversation/input-area/suggestors/channels.tsx index 0fcf0b2bb3ba..4e0bc22ec650 100644 --- a/shared/chat/conversation/input-area/suggestors/channels.tsx +++ b/shared/chat/conversation/input-area/suggestors/channels.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Common from './common' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/input-area/suggestors/commands.tsx b/shared/chat/conversation/input-area/suggestors/commands.tsx index 80f9f7753c81..4cfa2041c692 100644 --- a/shared/chat/conversation/input-area/suggestors/commands.tsx +++ b/shared/chat/conversation/input-area/suggestors/commands.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as Common from './common' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/input-area/suggestors/emoji.tsx b/shared/chat/conversation/input-area/suggestors/emoji.tsx index 2af8b5366e0e..df285c3eb4d2 100644 --- a/shared/chat/conversation/input-area/suggestors/emoji.tsx +++ b/shared/chat/conversation/input-area/suggestors/emoji.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Common from './common' import * as Kb from '@/common-adapters' import * as React from 'react' diff --git a/shared/chat/conversation/input-area/suggestors/index.tsx b/shared/chat/conversation/input-area/suggestors/index.tsx index d82cd92e2019..a075d4262c9c 100644 --- a/shared/chat/conversation/input-area/suggestors/index.tsx +++ b/shared/chat/conversation/input-area/suggestors/index.tsx @@ -1,5 +1,5 @@ import * as Channels from './channels' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Commands from './commands' import * as Emoji from './emoji' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/input-area/suggestors/users.tsx b/shared/chat/conversation/input-area/suggestors/users.tsx index 841ea76922fd..f951b0e3536a 100644 --- a/shared/chat/conversation/input-area/suggestors/users.tsx +++ b/shared/chat/conversation/input-area/suggestors/users.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useTeamsState} from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import {useTeamsState} from '@/stores/teams' import * as T from '@/constants/types' import * as Common from './common' import * as Kb from '@/common-adapters' import * as React from 'react' -import {useUsersState} from '@/constants/users' +import {useUsersState} from '@/stores/users' export const transformer = ( input: { diff --git a/shared/chat/conversation/list-area/hooks.tsx b/shared/chat/conversation/list-area/hooks.tsx index 6208ee8417e4..0570ca7ed8fd 100644 --- a/shared/chat/conversation/list-area/hooks.tsx +++ b/shared/chat/conversation/list-area/hooks.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import JumpToRecent from './jump-to-recent' import type * as T from '@/constants/types' diff --git a/shared/chat/conversation/list-area/index.desktop.tsx b/shared/chat/conversation/list-area/index.desktop.tsx index 10742edc1a83..a7558654025f 100644 --- a/shared/chat/conversation/list-area/index.desktop.tsx +++ b/shared/chat/conversation/list-area/index.desktop.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as Hooks from './hooks' import * as React from 'react' @@ -17,7 +17,7 @@ import logger from '@/logger' import shallowEqual from 'shallowequal' import useResizeObserver from '@/util/use-resize-observer.desktop' import useIntersectionObserver from '@/util/use-intersection-observer' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' // Infinite scrolling list. // We group messages into a series of Waypoints. When the waypoint exits the screen we replace it with a single div instead @@ -506,7 +506,7 @@ const ThreadWrapper = React.memo(function ThreadWrapper() { ) const {conversationIDKey, editingOrdinal, centeredOrdinal} = data const {containsLatestMessage, messageOrdinals, loaded, messageTypeMap} = data - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) const listRef = React.useRef(null) const _setListRef = React.useCallback((r: HTMLDivElement | null) => { listRef.current = r diff --git a/shared/chat/conversation/list-area/index.native.tsx b/shared/chat/conversation/list-area/index.native.tsx index 129406200774..4a4ea8308087 100644 --- a/shared/chat/conversation/list-area/index.native.tsx +++ b/shared/chat/conversation/list-area/index.native.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as Hooks from './hooks' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/load-status.tsx b/shared/chat/conversation/load-status.tsx index e992dd920145..786061c50ab9 100644 --- a/shared/chat/conversation/load-status.tsx +++ b/shared/chat/conversation/load-status.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' diff --git a/shared/chat/conversation/messages/account-payment/container.tsx b/shared/chat/conversation/messages/account-payment/container.tsx index 7aec89fb2655..e728ab2606eb 100644 --- a/shared/chat/conversation/messages/account-payment/container.tsx +++ b/shared/chat/conversation/messages/account-payment/container.tsx @@ -1,8 +1,8 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import MarkdownMemo from '@/wallets/markdown-memo' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' // Props for rendering the loading indicator const loadingProps = { diff --git a/shared/chat/conversation/messages/account-payment/wrapper.tsx b/shared/chat/conversation/messages/account-payment/wrapper.tsx index 7e2bd8bf7b79..619ed8222849 100644 --- a/shared/chat/conversation/messages/account-payment/wrapper.tsx +++ b/shared/chat/conversation/messages/account-payment/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type PaymentMessageType from './container' diff --git a/shared/chat/conversation/messages/attachment/audio.tsx b/shared/chat/conversation/messages/attachment/audio.tsx index eb299725bc45..cd32586199c4 100644 --- a/shared/chat/conversation/messages/attachment/audio.tsx +++ b/shared/chat/conversation/messages/attachment/audio.tsx @@ -1,9 +1,9 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {useOrdinal} from '../ids-context' import AudioPlayer from '@/chat/audio/audio-player' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const missingMessage = Chat.makeMessageAttachment() @@ -25,7 +25,7 @@ const AudioAttachment = () => { const progressLabel = Chat.messageAttachmentTransferStateToProgressLabel(message.transferState) const hasProgress = messageAttachmentHasProgress(message) const openLocalPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop + s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const onShowInFinder = () => { message.downloadPath && openLocalPathInSystemFileManagerDesktop?.(message.downloadPath) diff --git a/shared/chat/conversation/messages/attachment/file.tsx b/shared/chat/conversation/messages/attachment/file.tsx index 4d1dc8419ee6..bfcbe3bbc11f 100644 --- a/shared/chat/conversation/messages/attachment/file.tsx +++ b/shared/chat/conversation/messages/attachment/file.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Crypto from '@/constants/crypto' +import * as Chat from '@/stores/chat2' +import * as Crypto from '@/stores/crypto' import * as React from 'react' import {isPathSaltpack, isPathSaltpackEncrypted, isPathSaltpackSigned} from '@/util/path' import type * as T from '@/constants/types' @@ -9,7 +9,7 @@ import captialize from 'lodash/capitalize' import * as Kb from '@/common-adapters' import type {StyleOverride} from '@/common-adapters/markdown' import {getEditStyle, ShowToastAfterSaving} from './shared' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' type OwnProps = {showPopup: () => void} @@ -57,7 +57,7 @@ const FileContainer = React.memo(function FileContainer(p: OwnProps) { [switchTab, saltpackOpenFile] ) const openLocalPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop + s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const _onShowInFinder = React.useCallback(() => { downloadPath && openLocalPathInSystemFileManagerDesktop?.(downloadPath) diff --git a/shared/chat/conversation/messages/attachment/image2/use-state.tsx b/shared/chat/conversation/messages/attachment/image2/use-state.tsx index da4c4093b538..2399086be164 100644 --- a/shared/chat/conversation/messages/attachment/image2/use-state.tsx +++ b/shared/chat/conversation/messages/attachment/image2/use-state.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import {useOrdinal} from '@/chat/conversation/messages/ids-context' import {maxWidth, maxHeight} from '../shared' diff --git a/shared/chat/conversation/messages/attachment/shared.tsx b/shared/chat/conversation/messages/attachment/shared.tsx index 379a85ae8374..3f0efebc0588 100644 --- a/shared/chat/conversation/messages/attachment/shared.tsx +++ b/shared/chat/conversation/messages/attachment/shared.tsx @@ -1,12 +1,12 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' import {useOrdinal} from '../ids-context' import {sharedStyles} from '../shared-styles' import {Keyboard} from 'react-native' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' type Props = { transferState: T.Chat.MessageAttachmentTransferState @@ -103,7 +103,7 @@ export const TransferIcon = (p: {style: Kb.Styles.StylesCrossPlatform}) => { download(ordinal) }, [ordinal, download]) - const openFinder = useFSState(s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop) + const openFinder = useFSState(s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop) const onFinder = React.useCallback(() => { downloadPath && openFinder?.(downloadPath) }, [openFinder, downloadPath]) diff --git a/shared/chat/conversation/messages/attachment/video/use-state.tsx b/shared/chat/conversation/messages/attachment/video/use-state.tsx index b02adc6d8a21..5fc3c9eaa684 100644 --- a/shared/chat/conversation/messages/attachment/video/use-state.tsx +++ b/shared/chat/conversation/messages/attachment/video/use-state.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import {useOrdinal} from '@/chat/conversation/messages/ids-context' import {missingMessage, maxWidth, maxHeight} from '../shared' diff --git a/shared/chat/conversation/messages/cards/make-team.tsx b/shared/chat/conversation/messages/cards/make-team.tsx index ce3ca6ea16b3..d01d46e8e954 100644 --- a/shared/chat/conversation/messages/cards/make-team.tsx +++ b/shared/chat/conversation/messages/cards/make-team.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' const MakeTeam = () => { diff --git a/shared/chat/conversation/messages/cards/team-journey/container.tsx b/shared/chat/conversation/messages/cards/team-journey/container.tsx index 249383bf41a7..19d47a2be434 100644 --- a/shared/chat/conversation/messages/cards/team-journey/container.tsx +++ b/shared/chat/conversation/messages/cards/team-journey/container.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import {renderWelcomeMessage} from './util' import {useAllChannelMetas} from '@/teams/common/channel-hooks' diff --git a/shared/chat/conversation/messages/emoji-row.tsx b/shared/chat/conversation/messages/emoji-row.tsx index 43e13a7f324c..ffbb461d8082 100644 --- a/shared/chat/conversation/messages/emoji-row.tsx +++ b/shared/chat/conversation/messages/emoji-row.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {useOrdinal} from './ids-context' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/messages/message-popup/attachment.tsx b/shared/chat/conversation/messages/message-popup/attachment.tsx index 0cf6fdb9efb9..b6da989e6deb 100644 --- a/shared/chat/conversation/messages/message-popup/attachment.tsx +++ b/shared/chat/conversation/messages/message-popup/attachment.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import type * as T from '@/constants/types' import {type Position, fileUIName, type StylesCrossPlatform} from '@/styles' import {useItems, useHeader} from './hooks' import * as Kb from '@/common-adapters' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' type OwnProps = { attachTo?: React.RefObject @@ -84,7 +84,7 @@ const PopAttach = (ownProps: OwnProps) => { const onShareAttachment = C.isMobile ? _onShareAttachment : undefined const openLocalPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop + s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const _onShowInFinder = React.useCallback(() => { downloadPath && openLocalPathInSystemFileManagerDesktop?.(downloadPath) diff --git a/shared/chat/conversation/messages/message-popup/exploding-header.tsx b/shared/chat/conversation/messages/message-popup/exploding-header.tsx index e390b96f7879..7d691f4b30b8 100644 --- a/shared/chat/conversation/messages/message-popup/exploding-header.tsx +++ b/shared/chat/conversation/messages/message-popup/exploding-header.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' import {formatTimeForPopup, formatTimeForRevoked, msToDHMS} from '@/util/timestamp' import {addTicker, removeTicker} from '@/util/second-timer' diff --git a/shared/chat/conversation/messages/message-popup/header.tsx b/shared/chat/conversation/messages/message-popup/header.tsx index d0221144273a..d57e76ad84f5 100644 --- a/shared/chat/conversation/messages/message-popup/header.tsx +++ b/shared/chat/conversation/messages/message-popup/header.tsx @@ -1,6 +1,6 @@ import * as Kb from '@/common-adapters' import * as React from 'react' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import {formatTimeForPopup, formatTimeForRevoked} from '@/util/timestamp' import type * as T from '@/constants/types' diff --git a/shared/chat/conversation/messages/message-popup/hooks.tsx b/shared/chat/conversation/messages/message-popup/hooks.tsx index 6a8a35db164b..48b40ada8b7f 100644 --- a/shared/chat/conversation/messages/message-popup/hooks.tsx +++ b/shared/chat/conversation/messages/message-popup/hooks.tsx @@ -1,11 +1,11 @@ import * as React from 'react' import type * as T from '@/constants/types' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Teams from '@/constants/teams' -import {useConfigState} from '@/constants/config' -import {useProfileState} from '@/constants/profile' -import {useCurrentUserState} from '@/constants/current-user' +import * as Chat from '@/stores/chat2' +import * as Teams from '@/stores/teams' +import {useConfigState} from '@/stores/config' +import {useProfileState} from '@/stores/profile' +import {useCurrentUserState} from '@/stores/current-user' import {linkFromConvAndMessage} from '@/constants/deeplinks' import ReactionItem from './reactionitem' import MessagePopupHeader from './header' @@ -104,7 +104,7 @@ export const useItems = (ordinal: T.Chat.Ordinal, onHidden: () => void) => { : [] const convLabel = getConversationLabel(participantInfo, meta, true) - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) const onCopyLink = React.useCallback(() => { copyToClipboard(linkFromConvAndMessage(convLabel, id)) }, [copyToClipboard, id, convLabel]) diff --git a/shared/chat/conversation/messages/message-popup/index.tsx b/shared/chat/conversation/messages/message-popup/index.tsx index 111c936d3678..3736bfdc3587 100644 --- a/shared/chat/conversation/messages/message-popup/index.tsx +++ b/shared/chat/conversation/messages/message-popup/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import AttachmentMessage from './attachment' diff --git a/shared/chat/conversation/messages/message-popup/journeycard.tsx b/shared/chat/conversation/messages/message-popup/journeycard.tsx index 560590fd8ce3..f631f84246f6 100644 --- a/shared/chat/conversation/messages/message-popup/journeycard.tsx +++ b/shared/chat/conversation/messages/message-popup/journeycard.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as React from 'react' diff --git a/shared/chat/conversation/messages/message-popup/reactionitem.tsx b/shared/chat/conversation/messages/message-popup/reactionitem.tsx index 051dce87b10c..9c734e3f529c 100644 --- a/shared/chat/conversation/messages/message-popup/reactionitem.tsx +++ b/shared/chat/conversation/messages/message-popup/reactionitem.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' type Props = { diff --git a/shared/chat/conversation/messages/message-popup/text.tsx b/shared/chat/conversation/messages/message-popup/text.tsx index 3a8c7f28077d..66425418691d 100644 --- a/shared/chat/conversation/messages/message-popup/text.tsx +++ b/shared/chat/conversation/messages/message-popup/text.tsx @@ -1,13 +1,13 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useConfigState} from '@/constants/config' +import * as Chat from '@/stores/chat2' +import {useConfigState} from '@/stores/config' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import type {Position, StylesCrossPlatform} from '@/styles' import {useItems, useHeader} from './hooks' import openURL from '@/util/open-url' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = { attachTo?: React.RefObject @@ -75,7 +75,7 @@ const PopText = (ownProps: OwnProps) => { // you can reply privately *if* text message, someone else's message, and not in a 1-on-1 chat const canReplyPrivately = ['small', 'big'].includes(teamType) || numPart > 2 const navigateAppend = C.useRouterState(s => s.dispatch.navigateAppend) - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) const onCopy = React.useCallback(() => { text && copyToClipboard(text) }, [copyToClipboard, text]) diff --git a/shared/chat/conversation/messages/pin/index.tsx b/shared/chat/conversation/messages/pin/index.tsx index 937d4cb6fb2c..7618510b7557 100644 --- a/shared/chat/conversation/messages/pin/index.tsx +++ b/shared/chat/conversation/messages/pin/index.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' diff --git a/shared/chat/conversation/messages/pin/wrapper.tsx b/shared/chat/conversation/messages/pin/wrapper.tsx index 5cfd3cb7c7f2..67125e1e8575 100644 --- a/shared/chat/conversation/messages/pin/wrapper.tsx +++ b/shared/chat/conversation/messages/pin/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type PinType from '.' diff --git a/shared/chat/conversation/messages/placeholder/wrapper.tsx b/shared/chat/conversation/messages/placeholder/wrapper.tsx index 5cc050f9d681..45c196e3be7a 100644 --- a/shared/chat/conversation/messages/placeholder/wrapper.tsx +++ b/shared/chat/conversation/messages/placeholder/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' diff --git a/shared/chat/conversation/messages/react-button.tsx b/shared/chat/conversation/messages/react-button.tsx index 2486bd7a1129..4ce79b009d4b 100644 --- a/shared/chat/conversation/messages/react-button.tsx +++ b/shared/chat/conversation/messages/react-button.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import type {StylesCrossPlatform} from '@/styles' import {useOrdinal} from './ids-context' @@ -7,7 +7,7 @@ import * as Kb from '@/common-adapters' import type {StyleOverride} from '@/common-adapters/markdown' import {colors, darkColors} from '@/styles/colors' import {useColorScheme} from 'react-native' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' export type OwnProps = { className?: string diff --git a/shared/chat/conversation/messages/reaction-tooltip.tsx b/shared/chat/conversation/messages/reaction-tooltip.tsx index 985f3edacb4f..d196fa1180f1 100644 --- a/shared/chat/conversation/messages/reaction-tooltip.tsx +++ b/shared/chat/conversation/messages/reaction-tooltip.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import ReactButton from './react-button' import type * as T from '@/constants/types' import {MessageContext} from './ids-context' -import {useUsersState} from '@/constants/users' +import {useUsersState} from '@/stores/users' const positionFallbacks = ['bottom center', 'left center'] as const diff --git a/shared/chat/conversation/messages/reactions-rows.tsx b/shared/chat/conversation/messages/reactions-rows.tsx index cde5d850f470..21d1eef42325 100644 --- a/shared/chat/conversation/messages/reactions-rows.tsx +++ b/shared/chat/conversation/messages/reactions-rows.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import EmojiRow from './emoji-row' diff --git a/shared/chat/conversation/messages/reset-user.tsx b/shared/chat/conversation/messages/reset-user.tsx index 08ae473cf8ff..5ef6bde4ba85 100644 --- a/shared/chat/conversation/messages/reset-user.tsx +++ b/shared/chat/conversation/messages/reset-user.tsx @@ -1,5 +1,5 @@ -import * as Chat from '@/constants/chat2' -import {useProfileState} from '@/constants/profile' +import * as Chat from '@/stores/chat2' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' const ResetUser = () => { diff --git a/shared/chat/conversation/messages/retention-notice.tsx b/shared/chat/conversation/messages/retention-notice.tsx index add6faf305ea..77327c4f58ed 100644 --- a/shared/chat/conversation/messages/retention-notice.tsx +++ b/shared/chat/conversation/messages/retention-notice.tsx @@ -1,6 +1,6 @@ import type * as T from '@/constants/types' -import * as Chat from '@/constants/chat2' -import * as Teams from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' diff --git a/shared/chat/conversation/messages/separator.tsx b/shared/chat/conversation/messages/separator.tsx index d2a640007553..877beff3ad3a 100644 --- a/shared/chat/conversation/messages/separator.tsx +++ b/shared/chat/conversation/messages/separator.tsx @@ -1,15 +1,15 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useTeamsState} from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' import {formatTimeForConversationList, formatTimeForChat} from '@/util/timestamp' import {OrangeLineContext} from '../orange-line-context' import logger from '@/logger' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' +import {useCurrentUserState} from '@/stores/current-user' // import {useChatDebugDump} from '@/constants/chat2/debug' const enoughTimeBetweenMessages = (mtimestamp?: number, ptimestamp?: number): boolean => diff --git a/shared/chat/conversation/messages/set-channelname/wrapper.tsx b/shared/chat/conversation/messages/set-channelname/wrapper.tsx index e3a0c0164c7d..c507ae29e940 100644 --- a/shared/chat/conversation/messages/set-channelname/wrapper.tsx +++ b/shared/chat/conversation/messages/set-channelname/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SetChannelnameType from './container' diff --git a/shared/chat/conversation/messages/set-description/wrapper.tsx b/shared/chat/conversation/messages/set-description/wrapper.tsx index 9f8400a9e58d..0383a7edc43e 100644 --- a/shared/chat/conversation/messages/set-description/wrapper.tsx +++ b/shared/chat/conversation/messages/set-description/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SetDescriptionType from './container' diff --git a/shared/chat/conversation/messages/special-bottom-message.tsx b/shared/chat/conversation/messages/special-bottom-message.tsx index e6e45bf35bee..386a56528d86 100644 --- a/shared/chat/conversation/messages/special-bottom-message.tsx +++ b/shared/chat/conversation/messages/special-bottom-message.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import OldProfileReset from './system-old-profile-reset-notice/container' import ResetUser from './reset-user' diff --git a/shared/chat/conversation/messages/special-top-message.tsx b/shared/chat/conversation/messages/special-top-message.tsx index 40205b7dc910..e726d608a19c 100644 --- a/shared/chat/conversation/messages/special-top-message.tsx +++ b/shared/chat/conversation/messages/special-top-message.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import * as React from 'react' @@ -11,7 +11,7 @@ import ProfileResetNotice from './system-profile-reset-notice' import RetentionNotice from './retention-notice' import {usingFlashList} from '../list-area/flashlist-config' import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' const ErrorMessage = () => { const createConversationError = Chat.useChatState(s => s.createConversationError) @@ -178,7 +178,7 @@ const SpecialTopMessage = React.memo(function SpecialTopMessage() { }, []) const openPrivateFolder = React.useCallback(() => { - FS.makeActionForOpenPathInFilesTab(T.FS.stringToPath(`/keybase/private/${username}`)) + FS.navToPath(T.FS.stringToPath(`/keybase/private/${username}`)) }, [username]) return ( diff --git a/shared/chat/conversation/messages/system-added-to-team/container.tsx b/shared/chat/conversation/messages/system-added-to-team/container.tsx index 58c6e70e030f..10999c12dc9f 100644 --- a/shared/chat/conversation/messages/system-added-to-team/container.tsx +++ b/shared/chat/conversation/messages/system-added-to-team/container.tsx @@ -1,12 +1,12 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' import {getAddedUsernames} from '../system-users-added-to-conv/container' import {indefiniteArticle} from '@/util/string' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {message: T.Chat.MessageSystemAddedToTeam} diff --git a/shared/chat/conversation/messages/system-added-to-team/wrapper.tsx b/shared/chat/conversation/messages/system-added-to-team/wrapper.tsx index f90914a28f97..dcefb1626e38 100644 --- a/shared/chat/conversation/messages/system-added-to-team/wrapper.tsx +++ b/shared/chat/conversation/messages/system-added-to-team/wrapper.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemAddedToTeamType from './container' diff --git a/shared/chat/conversation/messages/system-change-avatar/index.tsx b/shared/chat/conversation/messages/system-change-avatar/index.tsx index bdf0fffdfe4b..8aa32c6b3098 100644 --- a/shared/chat/conversation/messages/system-change-avatar/index.tsx +++ b/shared/chat/conversation/messages/system-change-avatar/index.tsx @@ -1,7 +1,7 @@ import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import UserNotice from '../user-notice' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type Props = { message: T.Chat.MessageSystemChangeAvatar diff --git a/shared/chat/conversation/messages/system-change-avatar/wrapper.tsx b/shared/chat/conversation/messages/system-change-avatar/wrapper.tsx index 172ac0051c55..ed14bda12cb3 100644 --- a/shared/chat/conversation/messages/system-change-avatar/wrapper.tsx +++ b/shared/chat/conversation/messages/system-change-avatar/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemChangeAvatarType from '.' diff --git a/shared/chat/conversation/messages/system-change-retention/container.tsx b/shared/chat/conversation/messages/system-change-retention/container.tsx index c0ad42530fa3..d192ebd93f88 100644 --- a/shared/chat/conversation/messages/system-change-retention/container.tsx +++ b/shared/chat/conversation/messages/system-change-retention/container.tsx @@ -1,11 +1,11 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' import * as T from '@/constants/types' import * as dateFns from 'date-fns' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {message: T.Chat.MessageSystemChangeRetention} diff --git a/shared/chat/conversation/messages/system-change-retention/wrapper.tsx b/shared/chat/conversation/messages/system-change-retention/wrapper.tsx index 85490ad934a3..8d6d231f78ed 100644 --- a/shared/chat/conversation/messages/system-change-retention/wrapper.tsx +++ b/shared/chat/conversation/messages/system-change-retention/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemChangeRetentionType from './container' diff --git a/shared/chat/conversation/messages/system-create-team/container.tsx b/shared/chat/conversation/messages/system-create-team/container.tsx index f248de32f0f6..b3c654e66d2f 100644 --- a/shared/chat/conversation/messages/system-create-team/container.tsx +++ b/shared/chat/conversation/messages/system-create-team/container.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' import type * as T from '@/constants/types' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {message: T.Chat.MessageSystemCreateTeam} diff --git a/shared/chat/conversation/messages/system-create-team/wrapper.tsx b/shared/chat/conversation/messages/system-create-team/wrapper.tsx index 4d4cd246197a..2a9216ababec 100644 --- a/shared/chat/conversation/messages/system-create-team/wrapper.tsx +++ b/shared/chat/conversation/messages/system-create-team/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemCreateTeamType from './container' diff --git a/shared/chat/conversation/messages/system-git-push/container.tsx b/shared/chat/conversation/messages/system-git-push/container.tsx index 3d8d2c346a98..725af44edf1a 100644 --- a/shared/chat/conversation/messages/system-git-push/container.tsx +++ b/shared/chat/conversation/messages/system-git-push/container.tsx @@ -1,11 +1,11 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' -import {useGitState} from '@/constants/git' +import {useGitState} from '@/stores/git' import UserNotice from '../user-notice' import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {message: T.Chat.MessageSystemGitPush} @@ -21,7 +21,7 @@ const GitContainer = React.memo(function GitContainer(p: OwnProps) { '/.kbfs_autogit_commit_' + commitHash ) - FS.makeActionForOpenPathInFilesTab(path) + FS.navToPath(path) }, [message] ) diff --git a/shared/chat/conversation/messages/system-git-push/wrapper.tsx b/shared/chat/conversation/messages/system-git-push/wrapper.tsx index aaac3ac464e6..89e05a0090f8 100644 --- a/shared/chat/conversation/messages/system-git-push/wrapper.tsx +++ b/shared/chat/conversation/messages/system-git-push/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemGitPushType from './container' diff --git a/shared/chat/conversation/messages/system-invite-accepted/container.tsx b/shared/chat/conversation/messages/system-invite-accepted/container.tsx index b3b76a752bba..b2d1357a85fd 100644 --- a/shared/chat/conversation/messages/system-invite-accepted/container.tsx +++ b/shared/chat/conversation/messages/system-invite-accepted/container.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {message: T.Chat.MessageSystemInviteAccepted} diff --git a/shared/chat/conversation/messages/system-invite-accepted/wrapper.tsx b/shared/chat/conversation/messages/system-invite-accepted/wrapper.tsx index 118224348648..aeed739b80d4 100644 --- a/shared/chat/conversation/messages/system-invite-accepted/wrapper.tsx +++ b/shared/chat/conversation/messages/system-invite-accepted/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemInviteAcceptedType from './container' diff --git a/shared/chat/conversation/messages/system-joined/container.tsx b/shared/chat/conversation/messages/system-joined/container.tsx index 7ca9df41d333..a9dc7563c9d2 100644 --- a/shared/chat/conversation/messages/system-joined/container.tsx +++ b/shared/chat/conversation/messages/system-joined/container.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/messages/system-joined/wrapper.tsx b/shared/chat/conversation/messages/system-joined/wrapper.tsx index 996f2dc95968..7fe46e21979f 100644 --- a/shared/chat/conversation/messages/system-joined/wrapper.tsx +++ b/shared/chat/conversation/messages/system-joined/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemJoinedType from './container' diff --git a/shared/chat/conversation/messages/system-left/container.tsx b/shared/chat/conversation/messages/system-left/container.tsx index 70ed9a1b3d74..65d0cdf61516 100644 --- a/shared/chat/conversation/messages/system-left/container.tsx +++ b/shared/chat/conversation/messages/system-left/container.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' diff --git a/shared/chat/conversation/messages/system-left/wrapper.tsx b/shared/chat/conversation/messages/system-left/wrapper.tsx index cf6f97dea1dd..53f8df6c82d2 100644 --- a/shared/chat/conversation/messages/system-left/wrapper.tsx +++ b/shared/chat/conversation/messages/system-left/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemLeftType from './container' diff --git a/shared/chat/conversation/messages/system-new-channel/container.tsx b/shared/chat/conversation/messages/system-new-channel/container.tsx index fb791f2b70b4..f8e1e201837d 100644 --- a/shared/chat/conversation/messages/system-new-channel/container.tsx +++ b/shared/chat/conversation/messages/system-new-channel/container.tsx @@ -1,6 +1,6 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as React from 'react' import type * as T from '@/constants/types' import UserNotice from '../user-notice' diff --git a/shared/chat/conversation/messages/system-new-channel/wrapper.tsx b/shared/chat/conversation/messages/system-new-channel/wrapper.tsx index 27da6aaba425..be12f9f0554f 100644 --- a/shared/chat/conversation/messages/system-new-channel/wrapper.tsx +++ b/shared/chat/conversation/messages/system-new-channel/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemNewChannelType from './container' diff --git a/shared/chat/conversation/messages/system-old-profile-reset-notice/container.tsx b/shared/chat/conversation/messages/system-old-profile-reset-notice/container.tsx index 4044fd8a281b..e306630f87ad 100644 --- a/shared/chat/conversation/messages/system-old-profile-reset-notice/container.tsx +++ b/shared/chat/conversation/messages/system-old-profile-reset-notice/container.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import type * as T from '@/constants/types' import {Text} from '@/common-adapters' import UserNotice from '../user-notice' diff --git a/shared/chat/conversation/messages/system-profile-reset-notice.tsx b/shared/chat/conversation/messages/system-profile-reset-notice.tsx index 2d7deca69d72..0a2677a6140d 100644 --- a/shared/chat/conversation/messages/system-profile-reset-notice.tsx +++ b/shared/chat/conversation/messages/system-profile-reset-notice.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import UserNotice from './user-notice' diff --git a/shared/chat/conversation/messages/system-sbs-resolve/container.tsx b/shared/chat/conversation/messages/system-sbs-resolve/container.tsx index 51da16607892..8346cd4d5a80 100644 --- a/shared/chat/conversation/messages/system-sbs-resolve/container.tsx +++ b/shared/chat/conversation/messages/system-sbs-resolve/container.tsx @@ -2,7 +2,7 @@ import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {e164ToDisplay} from '@/util/phone-numbers' import UserNotice from '../user-notice' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {message: T.Chat.MessageSystemSBSResolved} diff --git a/shared/chat/conversation/messages/system-sbs-resolve/wrapper.tsx b/shared/chat/conversation/messages/system-sbs-resolve/wrapper.tsx index 32db70666c32..bd9d9626c95a 100644 --- a/shared/chat/conversation/messages/system-sbs-resolve/wrapper.tsx +++ b/shared/chat/conversation/messages/system-sbs-resolve/wrapper.tsx @@ -1,9 +1,9 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemSBSResolvedType from './container' import type SystemJoinedType from '../system-joined/container' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' const WrapperSystemInvite = React.memo(function WrapperSystemInvite(p: Props) { const {ordinal} = p diff --git a/shared/chat/conversation/messages/system-simple-to-complex/container.tsx b/shared/chat/conversation/messages/system-simple-to-complex/container.tsx index d0066afafefb..baa73a2f6af5 100644 --- a/shared/chat/conversation/messages/system-simple-to-complex/container.tsx +++ b/shared/chat/conversation/messages/system-simple-to-complex/container.tsx @@ -1,10 +1,10 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {message: T.Chat.MessageSystemSimpleToComplex} diff --git a/shared/chat/conversation/messages/system-simple-to-complex/wrapper.tsx b/shared/chat/conversation/messages/system-simple-to-complex/wrapper.tsx index 93d830470578..d894327e7599 100644 --- a/shared/chat/conversation/messages/system-simple-to-complex/wrapper.tsx +++ b/shared/chat/conversation/messages/system-simple-to-complex/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemSimpleToComplexType from './container' diff --git a/shared/chat/conversation/messages/system-text/wrapper.tsx b/shared/chat/conversation/messages/system-text/wrapper.tsx index 9b99bc02b180..4f2efa4555f6 100644 --- a/shared/chat/conversation/messages/system-text/wrapper.tsx +++ b/shared/chat/conversation/messages/system-text/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemTextType from './container' diff --git a/shared/chat/conversation/messages/system-users-added-to-conv/container.tsx b/shared/chat/conversation/messages/system-users-added-to-conv/container.tsx index 3d67beba56bb..1a9d48e4233d 100644 --- a/shared/chat/conversation/messages/system-users-added-to-conv/container.tsx +++ b/shared/chat/conversation/messages/system-users-added-to-conv/container.tsx @@ -1,9 +1,9 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {message: T.Chat.MessageSystemUsersAddedToConversation} diff --git a/shared/chat/conversation/messages/system-users-added-to-conv/wrapper.tsx b/shared/chat/conversation/messages/system-users-added-to-conv/wrapper.tsx index 5941d8eebbcd..1fe3e0bf1c8b 100644 --- a/shared/chat/conversation/messages/system-users-added-to-conv/wrapper.tsx +++ b/shared/chat/conversation/messages/system-users-added-to-conv/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {WrapperMessage, useCommon, type Props} from '../wrapper/wrapper' import type SystemUsersAddedToConvType from './container' diff --git a/shared/chat/conversation/messages/text/bottom.tsx b/shared/chat/conversation/messages/text/bottom.tsx index 128cc464ef16..e1a26112b6ab 100644 --- a/shared/chat/conversation/messages/text/bottom.tsx +++ b/shared/chat/conversation/messages/text/bottom.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import type * as T from '@/constants/types' import type CoinFlipType from './coinflip' diff --git a/shared/chat/conversation/messages/text/coinflip/index.tsx b/shared/chat/conversation/messages/text/coinflip/index.tsx index 223547f15130..45f53d2d088d 100644 --- a/shared/chat/conversation/messages/text/coinflip/index.tsx +++ b/shared/chat/conversation/messages/text/coinflip/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' diff --git a/shared/chat/conversation/messages/text/reply.tsx b/shared/chat/conversation/messages/text/reply.tsx index 7ad7cfacd4f9..bc0d9c745128 100644 --- a/shared/chat/conversation/messages/text/reply.tsx +++ b/shared/chat/conversation/messages/text/reply.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import {useOrdinal, useIsHighlighted} from '../ids-context' diff --git a/shared/chat/conversation/messages/text/unfurl/prompt-list/container.tsx b/shared/chat/conversation/messages/text/unfurl/prompt-list/container.tsx index 240d090eab40..ea0da57b5081 100644 --- a/shared/chat/conversation/messages/text/unfurl/prompt-list/container.tsx +++ b/shared/chat/conversation/messages/text/unfurl/prompt-list/container.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {useOrdinal} from '@/chat/conversation/messages/ids-context' import * as T from '@/constants/types' diff --git a/shared/chat/conversation/messages/text/unfurl/unfurl-list/generic.tsx b/shared/chat/conversation/messages/text/unfurl/unfurl-list/generic.tsx index 444f2452a61e..51105368b6e1 100644 --- a/shared/chat/conversation/messages/text/unfurl/unfurl-list/generic.tsx +++ b/shared/chat/conversation/messages/text/unfurl/unfurl-list/generic.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters/index' import * as T from '@/constants/types' import * as React from 'react' diff --git a/shared/chat/conversation/messages/text/unfurl/unfurl-list/giphy.tsx b/shared/chat/conversation/messages/text/unfurl/unfurl-list/giphy.tsx index 3e97b16ef8fd..238515cda7c3 100644 --- a/shared/chat/conversation/messages/text/unfurl/unfurl-list/giphy.tsx +++ b/shared/chat/conversation/messages/text/unfurl/unfurl-list/giphy.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters/index' import * as React from 'react' import UnfurlImage from './image' diff --git a/shared/chat/conversation/messages/text/unfurl/unfurl-list/image/index.tsx b/shared/chat/conversation/messages/text/unfurl/unfurl-list/image/index.tsx index a826e6dc5e04..e2a3ba2f2c5d 100644 --- a/shared/chat/conversation/messages/text/unfurl/unfurl-list/image/index.tsx +++ b/shared/chat/conversation/messages/text/unfurl/unfurl-list/image/index.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as Kb from '@/common-adapters/index' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import {maxWidth} from '@/chat/conversation/messages/attachment/shared' import {Video} from './video' import openURL from '@/util/open-url' diff --git a/shared/chat/conversation/messages/text/unfurl/unfurl-list/index.tsx b/shared/chat/conversation/messages/text/unfurl/unfurl-list/index.tsx index 1aa89030311a..65c37cb40387 100644 --- a/shared/chat/conversation/messages/text/unfurl/unfurl-list/index.tsx +++ b/shared/chat/conversation/messages/text/unfurl/unfurl-list/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as React from 'react' import UnfurlGeneric from './generic' diff --git a/shared/chat/conversation/messages/text/unfurl/unfurl-list/map-popup.tsx b/shared/chat/conversation/messages/text/unfurl/unfurl-list/map-popup.tsx index 243034849db6..59e03f3db1c5 100644 --- a/shared/chat/conversation/messages/text/unfurl/unfurl-list/map-popup.tsx +++ b/shared/chat/conversation/messages/text/unfurl/unfurl-list/map-popup.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters/index' import type * as T from '@/constants/types' import openURL from '@/util/open-url' import LocationMap from '@/chat/location-map' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' type Props = { coord: T.Chat.Coordinate diff --git a/shared/chat/conversation/messages/text/unfurl/unfurl-list/map.tsx b/shared/chat/conversation/messages/text/unfurl/unfurl-list/map.tsx index 2cf754f6891e..68ec76e60397 100644 --- a/shared/chat/conversation/messages/text/unfurl/unfurl-list/map.tsx +++ b/shared/chat/conversation/messages/text/unfurl/unfurl-list/map.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters/index' import * as T from '@/constants/types' import * as React from 'react' diff --git a/shared/chat/conversation/messages/text/unfurl/unfurl-list/use-state.tsx b/shared/chat/conversation/messages/text/unfurl/unfurl-list/use-state.tsx index 4a7fddb9d9dd..1092daf24b90 100644 --- a/shared/chat/conversation/messages/text/unfurl/unfurl-list/use-state.tsx +++ b/shared/chat/conversation/messages/text/unfurl/unfurl-list/use-state.tsx @@ -1,7 +1,7 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import type * as T from '@/constants/types' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' export const useActions = (youAreAuthor: boolean, messageID: T.Chat.MessageID, ordinal: T.Chat.Ordinal) => { const unfurlRemove = Chat.useChatContext(s => s.dispatch.unfurlRemove) diff --git a/shared/chat/conversation/messages/text/wrapper.tsx b/shared/chat/conversation/messages/text/wrapper.tsx index d3a6ed554bae..c1b077922b4b 100644 --- a/shared/chat/conversation/messages/text/wrapper.tsx +++ b/shared/chat/conversation/messages/text/wrapper.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import {useReply} from './reply' diff --git a/shared/chat/conversation/messages/wrapper/edited.tsx b/shared/chat/conversation/messages/wrapper/edited.tsx index bc39cbae643b..a3e2d0df2fbe 100644 --- a/shared/chat/conversation/messages/wrapper/edited.tsx +++ b/shared/chat/conversation/messages/wrapper/edited.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import {useIsHighlighted, useOrdinal} from '../ids-context' diff --git a/shared/chat/conversation/messages/wrapper/exploding-height-retainer/container.tsx b/shared/chat/conversation/messages/wrapper/exploding-height-retainer/container.tsx index fae3926d1218..0fc71930593c 100644 --- a/shared/chat/conversation/messages/wrapper/exploding-height-retainer/container.tsx +++ b/shared/chat/conversation/messages/wrapper/exploding-height-retainer/container.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import ExplodingHeightRetainer from '.' import {useOrdinal} from '../../ids-context' diff --git a/shared/chat/conversation/messages/wrapper/exploding-meta.tsx b/shared/chat/conversation/messages/wrapper/exploding-meta.tsx index c6a3a6c1bdde..fa796f6b0354 100644 --- a/shared/chat/conversation/messages/wrapper/exploding-meta.tsx +++ b/shared/chat/conversation/messages/wrapper/exploding-meta.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {useIsHighlighted, useOrdinal} from '../ids-context' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/messages/wrapper/long-pressable/index.native.tsx b/shared/chat/conversation/messages/wrapper/long-pressable/index.native.tsx index f5d163fce1c0..72f39ea87942 100644 --- a/shared/chat/conversation/messages/wrapper/long-pressable/index.native.tsx +++ b/shared/chat/conversation/messages/wrapper/long-pressable/index.native.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import type {Props} from '.' diff --git a/shared/chat/conversation/messages/wrapper/send-indicator.tsx b/shared/chat/conversation/messages/wrapper/send-indicator.tsx index 4f5df715d762..6f197da1ed5a 100644 --- a/shared/chat/conversation/messages/wrapper/send-indicator.tsx +++ b/shared/chat/conversation/messages/wrapper/send-indicator.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import {useOrdinal} from '../ids-context' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/chat/conversation/messages/wrapper/wrapper.tsx b/shared/chat/conversation/messages/wrapper/wrapper.tsx index d1c4186278d0..e4f0a9609e36 100644 --- a/shared/chat/conversation/messages/wrapper/wrapper.tsx +++ b/shared/chat/conversation/messages/wrapper/wrapper.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import {MessageContext, useOrdinal} from '../ids-context' @@ -13,7 +13,7 @@ import SendIndicator from './send-indicator' import * as T from '@/constants/types' import capitalize from 'lodash/capitalize' import {useEdited} from './edited' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' // import {useDebugLayout} from '@/util/debug-react' export type Props = { diff --git a/shared/chat/conversation/normal/container.tsx b/shared/chat/conversation/normal/container.tsx index fd38892d4b89..dc1ee30496aa 100644 --- a/shared/chat/conversation/normal/container.tsx +++ b/shared/chat/conversation/normal/container.tsx @@ -1,10 +1,9 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useConfigState} from '@/constants/config' +import * as Chat from '@/stores/chat2' +import {useConfigState} from '@/stores/config' import * as React from 'react' import Normal from '.' import * as T from '@/constants/types' -import {useActiveState} from '@/constants/active' import {FocusProvider, ScrollProvider} from './context' import {OrangeLineContext} from '../orange-line-context' @@ -51,7 +50,7 @@ const useOrangeLine = () => { // just use the rpc for orange line if we're not active // if we are active we want to keep whatever state we had so it is maintained - const active = useActiveState(s => s.active) + const active = useConfigState(s => s.active) React.useEffect(() => { if (!active) { loadOrangeLine() diff --git a/shared/chat/conversation/normal/index.desktop.tsx b/shared/chat/conversation/normal/index.desktop.tsx index 645314cf0f43..2e5dde5320c0 100644 --- a/shared/chat/conversation/normal/index.desktop.tsx +++ b/shared/chat/conversation/normal/index.desktop.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import Banner from '../bottom-banner' diff --git a/shared/chat/conversation/normal/index.native.tsx b/shared/chat/conversation/normal/index.native.tsx index b51457ece826..bfb4263d9019 100644 --- a/shared/chat/conversation/normal/index.native.tsx +++ b/shared/chat/conversation/normal/index.native.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import {PortalHost} from '@/common-adapters/portal.native' import * as Kb from '@/common-adapters' import * as React from 'react' diff --git a/shared/chat/conversation/pinned-message.tsx b/shared/chat/conversation/pinned-message.tsx index c1bee40e5890..9e18af274b21 100644 --- a/shared/chat/conversation/pinned-message.tsx +++ b/shared/chat/conversation/pinned-message.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' const PinnedMessage = React.memo(function PinnedMessage() { const {conversationIDKey, teamname, pinnedMsg, replyJump, onIgnore, pinMessage} = Chat.useChatContext( diff --git a/shared/chat/conversation/rekey/container.tsx b/shared/chat/conversation/rekey/container.tsx index d4397fb0346a..8cc2428b7f7b 100644 --- a/shared/chat/conversation/rekey/container.tsx +++ b/shared/chat/conversation/rekey/container.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useProfileState} from '@/constants/profile' -import {useCurrentUserState} from '@/constants/current-user' +import * as Chat from '@/stores/chat2' +import {useProfileState} from '@/stores/profile' +import {useCurrentUserState} from '@/stores/current-user' import * as T from '@/constants/types' import ParticipantRekey from './participant-rekey' import YouRekey from './you-rekey' diff --git a/shared/chat/conversation/reply-preview.tsx b/shared/chat/conversation/reply-preview.tsx index 4997bad338bd..b194fb2091cb 100644 --- a/shared/chat/conversation/reply-preview.tsx +++ b/shared/chat/conversation/reply-preview.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' diff --git a/shared/chat/conversation/search.tsx b/shared/chat/conversation/search.tsx index 20d9d78fa081..455b96ee6743 100644 --- a/shared/chat/conversation/search.tsx +++ b/shared/chat/conversation/search.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import type * as Styles from '@/styles' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/chat/create-channel/hooks.tsx b/shared/chat/create-channel/hooks.tsx index ec52505df5e5..ae69f3d75443 100644 --- a/shared/chat/create-channel/hooks.tsx +++ b/shared/chat/create-channel/hooks.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import upperFirst from 'lodash/upperFirst' import type {Props} from '.' diff --git a/shared/chat/delete-history-warning.tsx b/shared/chat/delete-history-warning.tsx index 1e748facdc92..47ffafd71431 100644 --- a/shared/chat/delete-history-warning.tsx +++ b/shared/chat/delete-history-warning.tsx @@ -1,6 +1,6 @@ import * as Kb from '@/common-adapters' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import MaybePopup from './maybe-popup' const DeleteHistoryWarning = () => { diff --git a/shared/chat/emoji-picker/container.tsx b/shared/chat/emoji-picker/container.tsx index abe1b741f551..34376fe70b98 100644 --- a/shared/chat/emoji-picker/container.tsx +++ b/shared/chat/emoji-picker/container.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Teams from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' diff --git a/shared/chat/inbox-and-conversation-2.tsx b/shared/chat/inbox-and-conversation-2.tsx index 730766b62d5b..f0a13e01420a 100644 --- a/shared/chat/inbox-and-conversation-2.tsx +++ b/shared/chat/inbox-and-conversation-2.tsx @@ -1,6 +1,6 @@ // Just for desktop and tablet, we show inbox and conversation side by side import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import type * as T from '@/constants/types' diff --git a/shared/chat/inbox-and-conversation-header.tsx b/shared/chat/inbox-and-conversation-header.tsx index 157b61f73d3b..46fba91a82b5 100644 --- a/shared/chat/inbox-and-conversation-header.tsx +++ b/shared/chat/inbox-and-conversation-header.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import type {StyleOverride} from '@/common-adapters/markdown' @@ -7,9 +7,9 @@ import SearchRow from './inbox/search-row' import NewChatButton from './inbox/new-chat-button' import {useRoute} from '@react-navigation/native' import type {RootRouteProps} from '@/router-v2/route-params' -import {useUsersState} from '@/constants/users' -import {useCurrentUserState} from '@/constants/current-user' -import * as Teams from '@/constants/teams' +import {useUsersState} from '@/stores/users' +import {useCurrentUserState} from '@/stores/current-user' +import * as Teams from '@/stores/teams' const Header = () => { const {params} = useRoute>() diff --git a/shared/chat/inbox-search/index.tsx b/shared/chat/inbox-search/index.tsx index f85ab2edb2d3..823e2b970a33 100644 --- a/shared/chat/inbox-search/index.tsx +++ b/shared/chat/inbox-search/index.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useTeamsState} from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' import Rover from './background' diff --git a/shared/chat/inbox/container.tsx b/shared/chat/inbox/container.tsx index a8f303cc416c..1bd795aef1d2 100644 --- a/shared/chat/inbox/container.tsx +++ b/shared/chat/inbox/container.tsx @@ -1,9 +1,17 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as T from '@/constants/types' import Inbox, {type Props} from '.' import {useIsFocused} from '@react-navigation/core' +import type { + ChatInboxRowItemBig, + ChatInboxRowItemBigHeader, + ChatInboxRowItemTeamBuilder, + ChatInboxRowItemSmall, + ChatInboxRowItemDivider, + ChatInboxRowItem, +} from './rowitem' type OwnProps = { navKey: string @@ -12,9 +20,7 @@ type OwnProps = { const makeBigRows = ( bigTeams: ReadonlyArray -): Array< - T.Chat.ChatInboxRowItemBig | T.Chat.ChatInboxRowItemBigHeader | T.Chat.ChatInboxRowItemTeamBuilder -> => { +): Array => { return bigTeams.map(t => { switch (t.state) { case T.RPCChat.UIInboxBigTeamRowTyp.channel: { @@ -43,7 +49,7 @@ const makeBigRows = ( const makeSmallRows = ( smallTeams: ReadonlyArray -): Array => { +): Array => { return smallTeams.map(t => { const conversationIDKey = T.Chat.stringToConversationIDKey(t.convID) return { @@ -180,18 +186,17 @@ const Connected = (ownProps: OwnProps) => { const hasAllSmallTeamConvs = (_inboxLayout?.smallTeams?.length ?? 0) === (_inboxLayout?.totalSmallTeams ?? 0) - const divider: Array = - React.useMemo(() => { - return bigRows.length !== 0 || !hasAllSmallTeamConvs - ? [{showButton: !hasAllSmallTeamConvs || smallTeamsBelowTheFold, type: 'divider'}] - : [] - }, [bigRows.length, hasAllSmallTeamConvs, smallTeamsBelowTheFold]) - - const rows: Array = React.useMemo(() => { - const builderAfterSmall = new Array() - const builderAfterDivider = new Array() - const builderAfterBig = new Array() - const teamBuilder: T.Chat.ChatInboxRowItemTeamBuilder = {type: 'teamBuilder'} + const divider: Array = React.useMemo(() => { + return bigRows.length !== 0 || !hasAllSmallTeamConvs + ? [{showButton: !hasAllSmallTeamConvs || smallTeamsBelowTheFold, type: 'divider'}] + : [] + }, [bigRows.length, hasAllSmallTeamConvs, smallTeamsBelowTheFold]) + + const rows: Array = React.useMemo(() => { + const builderAfterSmall = new Array() + const builderAfterDivider = new Array() + const builderAfterBig = new Array() + const teamBuilder: ChatInboxRowItemTeamBuilder = {type: 'teamBuilder'} if (smallRows.length !== 0) { if (bigRows.length === 0) { if (divider.length !== 0) { diff --git a/shared/chat/inbox/filter-row.tsx b/shared/chat/inbox/filter-row.tsx index 8a5651be6445..1d7214f8b4c1 100644 --- a/shared/chat/inbox/filter-row.tsx +++ b/shared/chat/inbox/filter-row.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/chat/inbox/index.d.ts b/shared/chat/inbox/index.d.ts index 71bf8d45eba2..24737a0fdf77 100644 --- a/shared/chat/inbox/index.d.ts +++ b/shared/chat/inbox/index.d.ts @@ -1,5 +1,6 @@ import type * as React from 'react' import type * as T from '@/constants/types' +import type {ChatInboxRowItem} from './rowitem' export type Props = { allowShowFloatingButton: boolean @@ -10,7 +11,7 @@ export type Props = { neverLoaded: boolean onNewChat: () => void onUntrustedInboxVisible: (conversationIDKeys: Array) => void - rows: Array + rows: Array setInboxNumSmallRows: (rows: number) => void smallTeamsExpanded: boolean toggleSmallTeamsExpanded: () => void diff --git a/shared/chat/inbox/index.desktop.tsx b/shared/chat/inbox/index.desktop.tsx index 98eac06219af..c40b4875c1ea 100644 --- a/shared/chat/inbox/index.desktop.tsx +++ b/shared/chat/inbox/index.desktop.tsx @@ -4,6 +4,7 @@ import * as C from '@/constants' import * as React from 'react' import type * as TInbox from './index.d' import type * as T from '@/constants/types' +import type {ChatInboxRowItem} from './rowitem' import BigTeamsDivider from './row/big-teams-divider' import BuildTeam from './row/build-team' import TeamsDivider from './row/teams-divider' @@ -47,7 +48,7 @@ const DragLine = (p: { toggleSmallTeamsExpanded: () => void setInboxNumSmallRows: (n: number) => void style: object - rows: T.Chat.ChatInboxRowItem[] + rows: ChatInboxRowItem[] }) => { const {inboxNumSmallRows, showButton, style, scrollDiv} = p const {smallTeamsExpanded, toggleSmallTeamsExpanded, rows, setInboxNumSmallRows} = p @@ -185,7 +186,7 @@ const DragLine = (p: { type InboxRowData = { inboxNumSmallRows: number navKey: string - rows: T.Chat.ChatInboxRowItem[] + rows: ChatInboxRowItem[] scrollDiv: React.RefObject selectedConversationIDKey: string setInboxNumSmallRows: (rows: number) => void diff --git a/shared/chat/inbox/index.native.tsx b/shared/chat/inbox/index.native.tsx index 3a33bbcbe653..e1aa6aaf9ceb 100644 --- a/shared/chat/inbox/index.native.tsx +++ b/shared/chat/inbox/index.native.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import * as RowSizes from './row/sizes' @@ -16,8 +16,9 @@ import {FlatList} from 'react-native-gesture-handler' // import {FlashList, type ListRenderItemInfo} from '@shopify/flash-list' import {makeRow} from './row' import {useOpenedRowState} from './row/opened-row-state' +import type {ChatInboxRowItem} from './rowitem' -type RowItem = T.Chat.ChatInboxRowItem +type RowItem = ChatInboxRowItem const usingFlashList = false as boolean const List = /*usingFlashList ? FlashList :*/ FlatList diff --git a/shared/chat/inbox/new-chat-button.tsx b/shared/chat/inbox/new-chat-button.tsx index e6987c1ddc92..bb086547ac38 100644 --- a/shared/chat/inbox/new-chat-button.tsx +++ b/shared/chat/inbox/new-chat-button.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/chat/inbox/row/big-team-channel.tsx b/shared/chat/inbox/row/big-team-channel.tsx index 79a4e46f06f3..e4089507b3b3 100644 --- a/shared/chat/inbox/row/big-team-channel.tsx +++ b/shared/chat/inbox/row/big-team-channel.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import * as RowSizes from './sizes' diff --git a/shared/chat/inbox/row/big-team-header.tsx b/shared/chat/inbox/row/big-team-header.tsx index b4cf9085a9b0..0fc4172f6497 100644 --- a/shared/chat/inbox/row/big-team-header.tsx +++ b/shared/chat/inbox/row/big-team-header.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as RowSizes from './sizes' import type * as T from '@/constants/types' diff --git a/shared/chat/inbox/row/big-teams-divider.tsx b/shared/chat/inbox/row/big-teams-divider.tsx index 35fc6ab594b1..547f2e267670 100644 --- a/shared/chat/inbox/row/big-teams-divider.tsx +++ b/shared/chat/inbox/row/big-teams-divider.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import * as RowSizes from './sizes' diff --git a/shared/chat/inbox/row/build-team.tsx b/shared/chat/inbox/row/build-team.tsx index 7f6415928fed..822260af2b68 100644 --- a/shared/chat/inbox/row/build-team.tsx +++ b/shared/chat/inbox/row/build-team.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' diff --git a/shared/chat/inbox/row/index.tsx b/shared/chat/inbox/row/index.tsx index 0cd35a8c9631..c47b53e3576c 100644 --- a/shared/chat/inbox/row/index.tsx +++ b/shared/chat/inbox/row/index.tsx @@ -3,9 +3,9 @@ import BigTeamHeader from './big-team-header' import BigTeamChannel from './big-team-channel' import {SmallTeam} from './small-team' import {BigTeamsLabel} from './big-teams-label' -import type * as T from '@/constants/types' +import type {ChatInboxRowItem} from '../rowitem' -const makeRow = (item: T.Chat.ChatInboxRowItem, navKey: string, selected: boolean) => { +const makeRow = (item: ChatInboxRowItem, navKey: string, selected: boolean) => { if (item.type === 'bigTeamsLabel') { return } diff --git a/shared/chat/inbox/row/opened-row-state.tsx b/shared/chat/inbox/row/opened-row-state.tsx index f32a8b91ce9a..29c1d4c415ff 100644 --- a/shared/chat/inbox/row/opened-row-state.tsx +++ b/shared/chat/inbox/row/opened-row-state.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Z from '@/util/zustand' import type * as T from '@/constants/types' diff --git a/shared/chat/inbox/row/sizes.tsx b/shared/chat/inbox/row/sizes.tsx index 584b245ccfed..027ee2a5c384 100644 --- a/shared/chat/inbox/row/sizes.tsx +++ b/shared/chat/inbox/row/sizes.tsx @@ -1,7 +1,7 @@ // In order for the inbox rows to be calculated quickly we use fixed sizes for each type so // in order for the list and the rows to ensure they're the same size we keep the sizes here import * as Kb from '@/common-adapters' -import type * as T from '@/constants/types' +import type {ChatInboxRowType} from '../rowitem' export const smallRowHeight = Kb.Styles.isMobile ? 64 : 56 export const bigRowHeight = Kb.Styles.isMobile ? 40 : 24 @@ -17,8 +17,8 @@ export const dividerHeight = (showingButton: boolean) => { } } -export const getRowHeight = (type: T.Chat.ChatInboxRowType, showingDividerButton: boolean) => { - const exhaustive = (type: T.Chat.ChatInboxRowType, showingDividerButton: boolean) => { +export const getRowHeight = (type: ChatInboxRowType, showingDividerButton: boolean) => { + const exhaustive = (type: ChatInboxRowType, showingDividerButton: boolean) => { switch (type) { case 'bigTeamsLabel': return bigHeaderHeight diff --git a/shared/chat/inbox/row/small-team/bottom-line.tsx b/shared/chat/inbox/row/small-team/bottom-line.tsx index eccb3d94d2bb..1dfbc213d35e 100644 --- a/shared/chat/inbox/row/small-team/bottom-line.tsx +++ b/shared/chat/inbox/row/small-team/bottom-line.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import {SnippetContext, SnippetDecorationContext} from './contexts' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type Props = { layoutSnippet?: string diff --git a/shared/chat/inbox/row/small-team/index.tsx b/shared/chat/inbox/row/small-team/index.tsx index 869aaa3a9957..f0289b126b34 100644 --- a/shared/chat/inbox/row/small-team/index.tsx +++ b/shared/chat/inbox/row/small-team/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import {SimpleTopLine} from './top-line' @@ -9,7 +9,7 @@ import * as RowSizes from '../sizes' import * as T from '@/constants/types' import SwipeConvActions from './swipe-conv-actions' import './small-team.css' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' import { IsTeamContext, ParticipantsContext, diff --git a/shared/chat/inbox/row/small-team/swipe-conv-actions/index.native.tsx b/shared/chat/inbox/row/small-team/swipe-conv-actions/index.native.tsx index 5ec1de0c2ee2..94ff76ff16f9 100644 --- a/shared/chat/inbox/row/small-team/swipe-conv-actions/index.native.tsx +++ b/shared/chat/inbox/row/small-team/swipe-conv-actions/index.native.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import * as Reanimated from 'react-native-reanimated' diff --git a/shared/chat/inbox/row/small-team/top-line.tsx b/shared/chat/inbox/row/small-team/top-line.tsx index 86825d823584..3e45c439e679 100644 --- a/shared/chat/inbox/row/small-team/top-line.tsx +++ b/shared/chat/inbox/row/small-team/top-line.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import TeamMenu from '@/chat/conversation/info-panel/menu' diff --git a/shared/chat/inbox/row/teams-divider.tsx b/shared/chat/inbox/row/teams-divider.tsx index 282156d7da76..5c90d6eab859 100644 --- a/shared/chat/inbox/row/teams-divider.tsx +++ b/shared/chat/inbox/row/teams-divider.tsx @@ -1,14 +1,15 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as React from 'react' import * as RowSizes from './sizes' +import type {ChatInboxRowItem} from '../rowitem' type Props = { hiddenCountDelta?: number smallTeamsExpanded: boolean - rows: Array + rows: Array showButton: boolean toggle: () => void style?: Kb.Styles.StylesCrossPlatform diff --git a/shared/constants/types/chat2/rowitem.tsx b/shared/chat/inbox/rowitem.tsx similarity index 100% rename from shared/constants/types/chat2/rowitem.tsx rename to shared/chat/inbox/rowitem.tsx diff --git a/shared/chat/inbox/search-row.tsx b/shared/chat/inbox/search-row.tsx index c97a2cc786bd..ceedb67615a9 100644 --- a/shared/chat/inbox/search-row.tsx +++ b/shared/chat/inbox/search-row.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import ChatFilterRow from './filter-row' import StartNewChat from './row/start-new-chat' diff --git a/shared/chat/new-team-dialog-container.tsx b/shared/chat/new-team-dialog-container.tsx index 0421c30d6fef..4c4d1864c28e 100644 --- a/shared/chat/new-team-dialog-container.tsx +++ b/shared/chat/new-team-dialog-container.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import {CreateNewTeam} from '../teams/new-team' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import upperFirst from 'lodash/upperFirst' const NewTeamDialog = () => { diff --git a/shared/chat/payments/status/index.tsx b/shared/chat/payments/status/index.tsx index 3fe3ec65668b..05236a073498 100644 --- a/shared/chat/payments/status/index.tsx +++ b/shared/chat/payments/status/index.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as Styles from '@/styles' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import PaymentStatusError from './error' import Text from '@/common-adapters/text' import {Box2} from '@/common-adapters/box' @@ -9,7 +9,7 @@ import type * as T from '@/constants/types' import type {MeasureRef} from '@/common-adapters/measure-ref' import type * as WalletTypes from '@/constants/types/wallets' import {useOrdinal} from '@/chat/conversation/messages/ids-context' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' // This is actually a dependency of common-adapters/markdown so we have to treat it like a common-adapter, no * import allowed const Kb = { diff --git a/shared/chat/pdf/index.desktop.tsx b/shared/chat/pdf/index.desktop.tsx index d0c53c8853c4..8866e8ec8316 100644 --- a/shared/chat/pdf/index.desktop.tsx +++ b/shared/chat/pdf/index.desktop.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import type {Props} from '.' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const ChatPDF = (props: Props) => { const {ordinal} = props @@ -11,7 +11,7 @@ const ChatPDF = (props: Props) => { const title = message?.title || message?.fileName || 'PDF' const url = message?.fileURL const openLocalPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop + s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const attachmentDownload = Chat.useChatContext(s => s.dispatch.attachmentDownload) diff --git a/shared/chat/pdf/index.native.tsx b/shared/chat/pdf/index.native.tsx index 044e8691e00b..d0900794f567 100644 --- a/shared/chat/pdf/index.native.tsx +++ b/shared/chat/pdf/index.native.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import type {Props} from '.' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const ChatPDF = (props: Props) => { const {ordinal, url} = props @@ -12,7 +12,7 @@ const ChatPDF = (props: Props) => { const [error, setError] = React.useState('') const navigateUp = C.useRouterState(s => s.dispatch.navigateUp) const onBack = () => navigateUp() - const showShareActionSheet = useConfigState(s => s.dispatch.dynamic.showShareActionSheet) + const showShareActionSheet = useConfigState(s => s.dispatch.defer.showShareActionSheet) const onShare = () => { showShareActionSheet?.(url ?? '', '', 'application/pdf') } diff --git a/shared/chat/routes.tsx b/shared/chat/routes.tsx index b34bfe654bdd..d9dcd49db022 100644 --- a/shared/chat/routes.tsx +++ b/shared/chat/routes.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import chatNewChat from '../team-building/page' import {headerNavigationOptions} from './conversation/header-area' import inboxGetOptions from './inbox/get-options' diff --git a/shared/chat/selectable-big-team-channel-container.tsx b/shared/chat/selectable-big-team-channel-container.tsx index 19af38e9941a..35e0b58f9614 100644 --- a/shared/chat/selectable-big-team-channel-container.tsx +++ b/shared/chat/selectable-big-team-channel-container.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import SelectableBigTeamChannel from './selectable-big-team-channel' type OwnProps = { diff --git a/shared/chat/selectable-small-team-container.tsx b/shared/chat/selectable-small-team-container.tsx index 2248c71d3a05..0a1f5f44b24a 100644 --- a/shared/chat/selectable-small-team-container.tsx +++ b/shared/chat/selectable-small-team-container.tsx @@ -1,8 +1,8 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import type {AllowedColors} from '@/common-adapters/text' import SelectableSmallTeam from './selectable-small-team' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = { filter?: string diff --git a/shared/chat/send-to-chat/index.tsx b/shared/chat/send-to-chat/index.tsx index 521f840c96bf..532f8a5a55fd 100644 --- a/shared/chat/send-to-chat/index.tsx +++ b/shared/chat/send-to-chat/index.tsx @@ -1,14 +1,14 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import * as React from 'react' import * as Kb from '@/common-adapters' import * as Kbfs from '@/fs/common' import ConversationList from './conversation-list/conversation-list' import ChooseConversation from './conversation-list/choose-conversation' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' +import {useCurrentUserState} from '@/stores/current-user' type Props = { canBack?: boolean diff --git a/shared/common-adapters/avatar/hooks.tsx b/shared/common-adapters/avatar/hooks.tsx index 0307b138030d..e1177a28b72f 100644 --- a/shared/common-adapters/avatar/hooks.tsx +++ b/shared/common-adapters/avatar/hooks.tsx @@ -1,5 +1,5 @@ // High level avatar class. Handdles converting from usernames to urls. Deals with testing mode. -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as React from 'react' import {iconTypeToImgSet, urlsToImgSet, urlsToSrcSet, urlsToBaseSrc, type IconType} from '../icon' import * as Styles from '@/styles' @@ -7,9 +7,9 @@ import * as AvatarZus from './store' import './avatar.css' import type {Props} from '.' import {useColorScheme} from 'react-native' -import {useUsersState} from '@/constants/users' -import {useFollowerState} from '@/constants/followers' -import {navToProfile} from '@/constants/router2' +import {useUsersState} from '@/stores/users' +import {useFollowerState} from '@/stores/followers' +import {navToProfile} from '@/stores/router2' export const avatarSizes = [128, 96, 64, 48, 32, 24, 16] as const export type AvatarSize = (typeof avatarSizes)[number] diff --git a/shared/common-adapters/avatar/store.tsx b/shared/common-adapters/avatar/store.tsx index da6964f9fc9a..c73d64efeff5 100644 --- a/shared/common-adapters/avatar/store.tsx +++ b/shared/common-adapters/avatar/store.tsx @@ -1,6 +1,7 @@ import type * as T from '@/constants/types' import * as Z from '@/util/zustand' +// This store has no dependencies on other stores and is safe to import directly from other stores. type Store = T.Immutable<{ counts: Map }> diff --git a/shared/common-adapters/copy-text.tsx b/shared/common-adapters/copy-text.tsx index 7942082307fb..43a797333631 100644 --- a/shared/common-adapters/copy-text.tsx +++ b/shared/common-adapters/copy-text.tsx @@ -8,7 +8,7 @@ import {useTimeout} from './use-timers' import * as Styles from '@/styles' import logger from '@/logger' import type {MeasureRef} from './measure-ref' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const Kb = { Box2Measure, @@ -61,8 +61,8 @@ const CopyText = (props: Props) => { const popupAnchor = React.useRef(null) const textRef = React.useRef(null) - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) - const showShareActionSheet = useConfigState(s => s.dispatch.dynamic.showShareActionSheet) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) + const showShareActionSheet = useConfigState(s => s.dispatch.defer.showShareActionSheet) const copy = React.useCallback(() => { if (!text) { if (!loadText) { diff --git a/shared/common-adapters/markdown/channel.tsx b/shared/common-adapters/markdown/channel.tsx index 55a2dc3d237e..a80546942696 100644 --- a/shared/common-adapters/markdown/channel.tsx +++ b/shared/common-adapters/markdown/channel.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import type * as T from '@/constants/types' import Text, {type StylesTextCrossPlatform} from '../text' import * as React from 'react' diff --git a/shared/common-adapters/markdown/maybe-mention/index.tsx b/shared/common-adapters/markdown/maybe-mention/index.tsx index 33431631f674..86bbe6a0a396 100644 --- a/shared/common-adapters/markdown/maybe-mention/index.tsx +++ b/shared/common-adapters/markdown/maybe-mention/index.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import Text, {type StylesTextCrossPlatform} from '@/common-adapters/text' import Mention from '../../mention-container' diff --git a/shared/common-adapters/markdown/maybe-mention/team.tsx b/shared/common-adapters/markdown/maybe-mention/team.tsx index 90bd396a1ee5..e9b7f234f06b 100644 --- a/shared/common-adapters/markdown/maybe-mention/team.tsx +++ b/shared/common-adapters/markdown/maybe-mention/team.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as React from 'react' import Text, {type StylesTextCrossPlatform} from '@/common-adapters/text' import {Box2} from '@/common-adapters/box' diff --git a/shared/common-adapters/markdown/service-decoration.tsx b/shared/common-adapters/markdown/service-decoration.tsx index 9951adafed94..90879a58e0b2 100644 --- a/shared/common-adapters/markdown/service-decoration.tsx +++ b/shared/common-adapters/markdown/service-decoration.tsx @@ -1,7 +1,7 @@ import * as T from '@/constants/types' import * as React from 'react' import * as C from '@/constants' -import {useDeepLinksState} from '@/constants/deeplinks' +import {handleAppLink} from '@/constants/deeplinks' import * as Styles from '@/styles' import Channel from './channel' import KbfsPath from '@/fs/common/kbfs-path' @@ -29,10 +29,9 @@ type KeybaseLinkProps = { } const KeybaseLink = (props: KeybaseLinkProps) => { - const handleAppLink = useDeepLinksState(s => s.dispatch.handleAppLink) const onClick = React.useCallback(() => { handleAppLink(props.link) - }, [handleAppLink, props.link]) + }, [props.link]) return ( { let {username} = ownProps diff --git a/shared/common-adapters/mention.tsx b/shared/common-adapters/mention.tsx index 468c66b4297e..973ec7212085 100644 --- a/shared/common-adapters/mention.tsx +++ b/shared/common-adapters/mention.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Styles from '@/styles' import {WithProfileCardPopup} from './profile-card' import Text from './text' diff --git a/shared/common-adapters/name-with-icon.tsx b/shared/common-adapters/name-with-icon.tsx index fdadf76d3ba6..07c7f2c4f3c7 100644 --- a/shared/common-adapters/name-with-icon.tsx +++ b/shared/common-adapters/name-with-icon.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as Styles from '@/styles' import * as C from '@/constants' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import Avatar, {type AvatarSize} from './avatar' import {Box} from './box' import ClickableBox from './clickable-box' @@ -13,8 +13,8 @@ import Text, { type TextTypeBold, } from './text' import ConnectedUsernames from './usernames' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' type Size = 'smaller' | 'small' | 'default' | 'big' | 'huge' diff --git a/shared/common-adapters/profile-card.tsx b/shared/common-adapters/profile-card.tsx index 933bfa706f49..a053acccd437 100644 --- a/shared/common-adapters/profile-card.tsx +++ b/shared/common-adapters/profile-card.tsx @@ -2,7 +2,7 @@ import * as C from '@/constants' import * as React from 'react' import * as Styles from '@/styles' import * as Platforms from '@/util/platforms' -import * as Tracker from '@/constants/tracker2' +import * as Tracker from '@/stores/tracker2' import type * as T from '@/constants/types' import capitalize from 'lodash/capitalize' import Box, {Box2, Box2Measure} from './box' @@ -12,16 +12,16 @@ import {_setWithProfileCardPopup} from './usernames' import FloatingMenu from './floating-menu' import Icon from './icon' import Meta from './meta' -import {useProfileState} from '@/constants/profile' -import {useFollowerState} from '@/constants/followers' -import {useCurrentUserState} from '@/constants/current-user' +import {useProfileState} from '@/stores/profile' +import {useFollowerState} from '@/stores/followers' +import {useCurrentUserState} from '@/stores/current-user' import ProgressIndicator from './progress-indicator' import Text from './text' import WithTooltip from './with-tooltip' import DelayedMounting from './delayed-mounting' import {type default as FollowButtonType} from '../profile/user/actions/follow-button' import type ChatButtonType from '../chat/chat-button' -import {useTrackerState} from '@/constants/tracker2' +import {useTrackerState} from '@/stores/tracker2' import type {MeasureRef} from './measure-ref' const positionFallbacks = ['top center', 'bottom center'] as const diff --git a/shared/common-adapters/proof-broken-banner.tsx b/shared/common-adapters/proof-broken-banner.tsx index 861ead336641..e1ecfd0cb296 100644 --- a/shared/common-adapters/proof-broken-banner.tsx +++ b/shared/common-adapters/proof-broken-banner.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' import * as React from 'react' import {Banner, BannerParagraph} from './banner' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' const Kb = {Banner} type Props = {users?: Array} diff --git a/shared/common-adapters/reload.tsx b/shared/common-adapters/reload.tsx index d9b92a06514e..e93d4d91c339 100644 --- a/shared/common-adapters/reload.tsx +++ b/shared/common-adapters/reload.tsx @@ -9,8 +9,8 @@ import Text from './text' import Button from './button' import Icon from './icon' import type {RPCError} from '@/util/errors' -import {settingsFeedbackTab} from '@/constants/settings/util' -import {useConfigState} from '@/constants/config' +import {settingsFeedbackTab} from '@/constants/settings' +import {useConfigState} from '@/stores/config' const Kb = { Box2, diff --git a/shared/common-adapters/team-with-popup.tsx b/shared/common-adapters/team-with-popup.tsx index 4059c9608948..f907e5e1ca80 100644 --- a/shared/common-adapters/team-with-popup.tsx +++ b/shared/common-adapters/team-with-popup.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import {Box2} from './box' import * as Styles from '@/styles' diff --git a/shared/common-adapters/usernames.tsx b/shared/common-adapters/usernames.tsx index e86fcb564666..570f91b6abab 100644 --- a/shared/common-adapters/usernames.tsx +++ b/shared/common-adapters/usernames.tsx @@ -12,11 +12,11 @@ import Text, { import {backgroundModeIsNegative} from './text.shared' import isArray from 'lodash/isArray' import type {e164ToDisplay as e164ToDisplayType} from '@/util/phone-numbers' -import {useTrackerState} from '@/constants/tracker2' -import {useUsersState} from '@/constants/users' -import {useProfileState} from '@/constants/profile' -import {useFollowerState} from '@/constants/followers' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import {useUsersState} from '@/stores/users' +import {useProfileState} from '@/stores/profile' +import {useFollowerState} from '@/stores/followers' +import {useCurrentUserState} from '@/stores/current-user' export type User = { username: string diff --git a/shared/common-adapters/wave-button.tsx b/shared/common-adapters/wave-button.tsx index 40a33f359a66..63a8ab05699a 100644 --- a/shared/common-adapters/wave-button.tsx +++ b/shared/common-adapters/wave-button.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import {Box2, Box} from './box' import Icon from './icon' diff --git a/shared/constants/active/index.tsx b/shared/constants/active/index.tsx deleted file mode 100644 index cf53cc33b318..000000000000 --- a/shared/constants/active/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import * as Chat from '../chat2' -import type * as T from '../types' -import * as Z from '@/util/zustand' -import {storeRegistry} from '../store-registry' - -type Store = T.Immutable<{active: boolean}> -const initialStore: Store = {active: true} - -export interface State extends Store { - dispatch: { - resetState: 'default' - setActive: (a: boolean) => void - } -} -export const useActiveState = Z.createZustand(set => { - const dispatch: State['dispatch'] = { - resetState: 'default', - setActive: a => { - set(s => { - s.active = a - }) - const cs = storeRegistry.getConvoState(Chat.getSelectedConversation()) - cs.dispatch.markThreadAsRead() - }, - } - return {...initialStore, dispatch} -}) diff --git a/shared/constants/archive/util.tsx b/shared/constants/archive/util.tsx deleted file mode 100644 index e44657ebf42e..000000000000 --- a/shared/constants/archive/util.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifySimpleFSSimpleFSArchiveStatusChanged: - case EngineGen.chat1NotifyChatChatArchiveComplete: - case EngineGen.chat1NotifyChatChatArchiveProgress: - { - storeRegistry.getState('archive').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/autoreset/util.tsx b/shared/constants/autoreset/util.tsx deleted file mode 100644 index ccaa494f61a0..000000000000 --- a/shared/constants/autoreset/util.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyBadgesBadgeState: - { - storeRegistry.getState('autoreset').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/bots/util.tsx b/shared/constants/bots/util.tsx deleted file mode 100644 index 1af60048ec8d..000000000000 --- a/shared/constants/bots/util.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import type * as T from '../types' -import {storeRegistry} from '../store-registry' - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyFeaturedBotsFeaturedBotsUpdate: - { - storeRegistry.getState('bots').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} - - -export const getFeaturedSorted = ( - featuredBotsMap: ReadonlyMap -): Array => { - const featured = [...featuredBotsMap.values()] - featured.sort((a: T.RPCGen.FeaturedBot, b: T.RPCGen.FeaturedBot) => { - if (a.rank < b.rank) { - return 1 - } else if (a.rank > b.rank) { - return -1 - } - return 0 - }) - return featured -} diff --git a/shared/constants/chat2/common.tsx b/shared/constants/chat2/common.tsx index e69d40c23dc3..45ae0acc1c03 100644 --- a/shared/constants/chat2/common.tsx +++ b/shared/constants/chat2/common.tsx @@ -1,12 +1,12 @@ import * as T from '../types' import {isMobile, isTablet} from '../platform' -import * as Router2 from '../router2' -import {storeRegistry} from '../store-registry' +import {getVisibleScreen} from '@/constants/router2' +import {useConfigState} from '@/stores/config' export const explodingModeGregorKeyPrefix = 'exploding:' export const getSelectedConversation = (allowUnderModal: boolean = false): T.Chat.ConversationIDKey => { - const maybeVisibleScreen = Router2.getVisibleScreen(undefined, allowUnderModal) + const maybeVisibleScreen = getVisibleScreen(undefined, allowUnderModal) if (maybeVisibleScreen?.name === threadRouteName) { const mParams = maybeVisibleScreen.params as undefined | {conversationIDKey?: T.Chat.ConversationIDKey} return mParams?.conversationIDKey ?? T.Chat.noConversationIDKey @@ -25,13 +25,12 @@ export const isUserActivelyLookingAtThisThread = (conversationIDKey: T.Chat.Conv if (!isSplit) { chatThreadSelected = true // conversationIDKey === selectedConversationIDKey is the only thing that matters in the new router } else { - const maybeVisibleScreen = Router2.getVisibleScreen() + const maybeVisibleScreen = getVisibleScreen() chatThreadSelected = (maybeVisibleScreen === undefined ? undefined : maybeVisibleScreen.name) === threadRouteName } - const {appFocused} = storeRegistry.getState('config') - const {active: userActive} = storeRegistry.getState('active') + const {appFocused, active: userActive} = useConfigState.getState() return ( appFocused && // app focused? diff --git a/shared/constants/chat2/debug.tsx b/shared/constants/chat2/debug.tsx index ba94fb89a7f8..e91c336a2aca 100644 --- a/shared/constants/chat2/debug.tsx +++ b/shared/constants/chat2/debug.tsx @@ -1,5 +1,5 @@ // Debug utilities for chat -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import type * as T from '@/constants/types' import logger from '@/logger' diff --git a/shared/constants/chat2/index.tsx b/shared/constants/chat2/index.tsx index 818915ca1b15..e69de29bb2d1 100644 --- a/shared/constants/chat2/index.tsx +++ b/shared/constants/chat2/index.tsx @@ -1,2001 +0,0 @@ -import * as T from '../types' -import {ignorePromise, timeoutPromise, type ViewPropsToPageProps} from '../utils' -import * as Tabs from '../tabs' -import * as EngineGen from '@/actions/engine-gen-gen' -import type * as ConfigConstants from '../config' -import * as Message from './message' -import * as Router2 from '../router2' -import * as TeamConstants from '../teams/util' -import logger from '@/logger' -import {RPCError} from '@/util/errors' -import * as Meta from './meta' -import {isMobile, isPhone} from '../platform' -import * as Z from '@/util/zustand' -import * as Common from './common' -import {clearChatStores, chatStores} from './convostate' -import {uint8ArrayToString} from 'uint8array-extras' -import isEqual from 'lodash/isEqual' -import {bodyToJSON} from '../rpc-utils' -import {navigateAppend, navUpToScreen, switchTab} from '../router2/util' -import {storeRegistry} from '../store-registry' -import * as S from '../strings' - -const defaultTopReacjis = [ - {name: ':+1:'}, - {name: ':-1:'}, - {name: ':tada:'}, - {name: ':joy:'}, - {name: ':sunglasses:'}, -] -const defaultSkinTone = 1 -const defaultUserReacjis = {skinTone: defaultSkinTone, topReacjis: defaultTopReacjis} - -// while we're debugging chat issues -export const DEBUG_CHAT_DUMP = true - -const blockButtonsGregorPrefix = 'blockButtons.' - -export const inboxSearchMaxTextMessages = 25 -export const inboxSearchMaxTextResults = 50 -export const inboxSearchMaxNameResults = 7 -export const inboxSearchMaxUnreadNameResults = isMobile ? 5 : 10 - -export const makeInboxSearchInfo = (): T.Chat.InboxSearchInfo => ({ - botsResults: [], - botsResultsSuggested: false, - botsStatus: 'initial', - indexPercent: 0, - nameResults: [], - nameResultsUnread: false, - nameStatus: 'initial', - openTeamsResults: [], - openTeamsResultsSuggested: false, - openTeamsStatus: 'initial', - query: '', - selectedIndex: 0, - textResults: [], - textStatus: 'initial', -}) - -const getInboxSearchSelected = ( - inboxSearch: T.Immutable -): - | undefined - | { - conversationIDKey: T.Chat.ConversationIDKey - query?: string - } => { - const {selectedIndex, nameResults, botsResults, openTeamsResults, textResults} = inboxSearch - const firstTextResultIdx = botsResults.length + openTeamsResults.length + nameResults.length - const firstOpenTeamResultIdx = nameResults.length - - if (selectedIndex < firstOpenTeamResultIdx) { - const maybeNameResults = nameResults[selectedIndex] - const conversationIDKey = maybeNameResults === undefined ? undefined : maybeNameResults.conversationIDKey - if (conversationIDKey) { - return { - conversationIDKey, - query: undefined, - } - } - } else if (selectedIndex < firstTextResultIdx) { - return - } else if (selectedIndex >= firstTextResultIdx) { - const result = textResults[selectedIndex - firstTextResultIdx] - if (result) { - return { - conversationIDKey: result.conversationIDKey, - query: result.query, - } - } - } - return -} - -export const getMessageKey = (message: T.Chat.Message) => - `${message.conversationIDKey}:${T.Chat.ordinalToNumber(message.ordinal)}` - -export const getBotsAndParticipants = ( - meta: T.Immutable, - participantInfo: T.Immutable, - sort?: boolean -) => { - const isAdhocTeam = meta.teamType === 'adhoc' - const teamMembers = - storeRegistry.getState('teams').teamIDToMembers.get(meta.teamID) ?? new Map() - let bots: Array = [] - if (isAdhocTeam) { - bots = participantInfo.all.filter(p => !participantInfo.name.includes(p)) - } else { - bots = [...teamMembers.values()] - .filter( - p => - TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p.username, 'restrictedbot') || - TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p.username, 'bot') - ) - .map(p => p.username) - .sort((l, r) => l.localeCompare(r)) - } - let participants: ReadonlyArray = participantInfo.all - if (meta.channelname === 'general') { - participants = [...teamMembers.values()].reduce>((l, mi) => { - l.push(mi.username) - return l - }, []) - } - participants = participants.filter(p => !bots.includes(p)) - participants = sort - ? participants - .map(p => ({ - isAdmin: !isAdhocTeam ? TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p, 'admin') : false, - isOwner: !isAdhocTeam ? TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p, 'owner') : false, - username: p, - })) - .sort((l, r) => { - const leftIsAdmin = l.isAdmin || l.isOwner - const rightIsAdmin = r.isAdmin || r.isOwner - if (leftIsAdmin && !rightIsAdmin) { - return -1 - } else if (!leftIsAdmin && rightIsAdmin) { - return 1 - } - return l.username.localeCompare(r.username) - }) - .map(p => p.username) - : participants - return {bots, participants} -} - -export const getTeamMentionName = (name: string, channel: string) => { - return name + (channel ? `#${channel}` : '') -} - -export const isAssertion = (username: string) => username.includes('@') - -export const clampImageSize = (width: number, height: number, maxWidth: number, maxHeight: number) => { - const aspectRatio = width / height - - let newWidth = width - let newHeight = height - - if (newWidth > maxWidth) { - newWidth = maxWidth - newHeight = newWidth / aspectRatio - } - - if (newHeight > maxHeight) { - newHeight = maxHeight - newWidth = newHeight * aspectRatio - } - - return { - height: Math.ceil(newHeight), - width: Math.ceil(newWidth), - } -} - -export const zoomImage = (width: number, height: number, maxThumbSize: number) => { - const dims = - height > width - ? {height: (maxThumbSize * height) / width, width: maxThumbSize} - : {height: maxThumbSize, width: (maxThumbSize * width) / height} - const marginHeight = dims.height > maxThumbSize ? (dims.height - maxThumbSize) / 2 : 0 - const marginWidth = dims.width > maxThumbSize ? (dims.width - maxThumbSize) / 2 : 0 - return { - dims, - margins: { - marginBottom: -marginHeight, - marginLeft: -marginWidth, - marginRight: -marginWidth, - marginTop: -marginHeight, - }, - } -} - -const uiParticipantsToParticipantInfo = ( - uiParticipants: ReadonlyArray -): T.Chat.ParticipantInfo => { - const participantInfo = {all: new Array(), contactName: new Map(), name: new Array()} - uiParticipants.forEach(part => { - const {assertion, contactName, inConvName} = part - participantInfo.all.push(assertion) - if (inConvName) { - participantInfo.name.push(assertion) - } - if (contactName) { - participantInfo.contactName.set(assertion, contactName) - } - }) - return participantInfo -} - -/** - * Returns true if the team is big and you're a member - */ -export const isBigTeam = (state: State, teamID: string): boolean => { - const bigTeams = state.inboxLayout?.bigTeams - return (bigTeams || []).some(v => v.state === T.RPCChat.UIInboxBigTeamRowTyp.label && v.label.id === teamID) -} - -// prettier-ignore -type PreviewReason = - | 'appLink' | 'channelHeader' | 'convertAdHoc' | 'files' | 'forward' | 'fromAReset' - | 'journeyCardPopular' | 'manageView' | 'memberView' | 'messageLink' | 'newChannel' - | 'profile' | 'requestedPayment' | 'resetChatWithoutThem' | 'search' | 'sentPayment' - | 'teamHeader' | 'teamInvite' | 'teamMember' | 'teamMention' | 'teamRow' | 'tracker' | 'transaction' - -type Store = T.Immutable<{ - botPublicCommands: Map - createConversationError?: T.Chat.CreateConversationError - smallTeamBadgeCount: number - bigTeamBadgeCount: number - smallTeamsExpanded: boolean // if we're showing all small teams, - lastCoord?: T.Chat.Coordinate - paymentStatusMap: Map - staticConfig?: T.Chat.StaticConfig // static config stuff from the service. only needs to be loaded once. if null, it hasn't been loaded, - trustedInboxHasLoaded: boolean // if we've done initial trusted inbox load, - userReacjis: T.Chat.UserReacjis - userEmojis?: Array - userEmojisForAutocomplete?: Array - infoPanelShowing: boolean - infoPanelSelectedTab?: 'settings' | 'members' | 'attachments' | 'bots' - inboxNumSmallRows?: number - inboxHasLoaded: boolean // if we've ever loaded, - inboxLayout?: T.RPCChat.UIInboxLayout // layout of the inbox - inboxSearch?: T.Chat.InboxSearchInfo - teamIDToGeneralConvID: Map - flipStatusMap: Map - maybeMentionMap: Map - blockButtonsMap: Map // Should we show block buttons for this team ID? -}> - -const initialStore: Store = { - bigTeamBadgeCount: 0, - blockButtonsMap: new Map(), - botPublicCommands: new Map(), - createConversationError: undefined, - flipStatusMap: new Map(), - inboxHasLoaded: false, - inboxLayout: undefined, - inboxNumSmallRows: 5, - inboxSearch: undefined, - infoPanelSelectedTab: undefined, - infoPanelShowing: false, - lastCoord: undefined, - maybeMentionMap: new Map(), - paymentStatusMap: new Map(), - smallTeamBadgeCount: 0, - smallTeamsExpanded: false, - staticConfig: undefined, - teamIDToGeneralConvID: new Map(), - trustedInboxHasLoaded: false, - userEmojis: undefined, - userEmojisForAutocomplete: undefined, - userReacjis: defaultUserReacjis, -} - -export interface State extends Store { - dispatch: { - badgesUpdated: (badgeState?: T.RPCGen.BadgeState) => void - clearMetas: () => void - conversationErrored: ( - allowedUsers: ReadonlyArray, - disallowedUsers: ReadonlyArray, - code: number, - message: string - ) => void - createConversation: (participants: ReadonlyArray, highlightMessageID?: T.Chat.MessageID) => void - ensureWidgetMetas: () => void - findGeneralConvIDFromTeamID: (teamID: T.Teams.TeamID) => void - fetchUserEmoji: (conversationIDKey?: T.Chat.ConversationIDKey, onlyInTeam?: boolean) => void - inboxRefresh: ( - reason: - | 'bootstrap' - | 'componentNeverLoaded' - | 'inboxStale' - | 'inboxSyncedClear' - | 'inboxSyncedUnknown' - | 'joinedAConversation' - | 'leftAConversation' - | 'teamTypeChanged' - | 'maybeKickedFromTeam' - | 'widgetRefresh' - | 'shareConfigSearch' - ) => void - inboxSearch: (query: string) => void - inboxSearchMoveSelectedIndex: (increment: boolean) => void - inboxSearchSelect: ( - conversationIDKey?: T.Chat.ConversationIDKey, - query?: string, - selectedIndex?: number - ) => void - loadStaticConfig: () => void - loadedUserEmoji: (results: T.RPCChat.UserEmojiRes) => void - maybeChangeSelectedConv: () => void - messageSendByUsername: (username: string, text: string, waitingKey?: string) => void - metasReceived: ( - metas: ReadonlyArray, - removals?: ReadonlyArray // convs to remove - ) => void - navigateToInbox: (allowSwitchTab?: boolean) => void - onChatThreadStale: (action: EngineGen.Chat1NotifyChatChatThreadsStalePayload) => void - onEngineIncomingImpl: (action: EngineGen.Actions) => void - onChatInboxSynced: (action: EngineGen.Chat1NotifyChatChatInboxSyncedPayload) => void - onGetInboxConvsUnboxed: (action: EngineGen.Chat1ChatUiChatInboxConversationPayload) => void - onGetInboxUnverifiedConvs: (action: EngineGen.Chat1ChatUiChatInboxUnverifiedPayload) => void - onIncomingInboxUIItem: (inboxUIItem?: T.RPCChat.InboxUIItem) => void - onRouteChanged: (prev: T.Immutable, next: T.Immutable) => void - onTeamBuildingFinished: (users: ReadonlySet) => void - paymentInfoReceived: (paymentInfo: T.Chat.ChatPaymentInfo) => void - previewConversation: (p: { - participants?: ReadonlyArray - teamname?: string - channelname?: string - conversationIDKey?: T.Chat.ConversationIDKey // we only use this when we click on channel mentions. we could maybe change that plumbing but keeping it for now - highlightMessageID?: T.Chat.MessageID - reason: PreviewReason - }) => void - queueMetaToRequest: (ids: ReadonlyArray) => void - queueMetaHandle: () => void - refreshBotPublicCommands: (username: string) => void - resetConversationErrored: () => void - resetState: () => void - setMaybeMentionInfo: (name: string, info: T.RPCChat.UIMaybeMentionInfo) => void - setTrustedInboxHasLoaded: () => void - setInfoPanelTab: (tab: 'settings' | 'members' | 'attachments' | 'bots' | undefined) => void - setInboxNumSmallRows: (rows: number, ignoreWrite?: boolean) => void - toggleInboxSearch: (enabled: boolean) => void - toggleSmallTeamsExpanded: () => void - unboxRows: (ids: Array, force?: boolean) => void - updateCoinFlipStatus: (statuses: ReadonlyArray) => void - updateInboxLayout: (layout: string) => void - updateLastCoord: (coord: T.Chat.Coordinate) => void - updateUserReacjis: (userReacjis: T.RPCGen.UserReacjis) => void - updatedGregor: (items: ConfigConstants.State['gregorPushState']) => void - updateInfoPanel: (show: boolean, tab: 'settings' | 'members' | 'attachments' | 'bots' | undefined) => void - } - getBackCount: (conversationIDKey: T.Chat.ConversationIDKey) => number - getBadgeHiddenCount: (ids: Set) => {badgeCount: number; hiddenCount: number} - getUnreadIndicies: (ids: Array) => Map -} - -// Only get the untrusted conversations out -const untrustedConversationIDKeys = (ids: ReadonlyArray) => - ids.filter(id => storeRegistry.getConvoState(id).meta.trustedState === 'untrusted') - -// generic chat store -export const useChatState = Z.createZustand((set, get) => { - // We keep a set of conversations to unbox - let metaQueue = new Set() - - const dispatch: State['dispatch'] = { - badgesUpdated: b => { - if (!b) return - // clear all first - for (const [, cs] of chatStores) { - cs.getState().dispatch.badgesUpdated(0) - } - b.conversations?.forEach(c => { - const id = T.Chat.conversationIDToKey(c.convID) - storeRegistry.getConvoState(id).dispatch.badgesUpdated(c.badgeCount) - storeRegistry.getConvoState(id).dispatch.unreadUpdated(c.unreadMessages) - }) - const {bigTeamBadgeCount, smallTeamBadgeCount} = b - set(s => { - s.smallTeamBadgeCount = smallTeamBadgeCount - s.bigTeamBadgeCount = bigTeamBadgeCount - }) - }, - clearMetas: () => { - for (const [, cs] of chatStores) { - cs.getState().dispatch.setMeta() - } - }, - conversationErrored: (allowedUsers, disallowedUsers, code, message) => { - set(s => { - s.createConversationError = T.castDraft({ - allowedUsers, - code, - disallowedUsers, - message, - }) - }) - }, - createConversation: (participants, highlightMessageID) => { - // TODO This will break if you try to make 2 new conversations at the same time because there is - // only one pending conversation state. - // The fix involves being able to make multiple pending conversations - const f = async () => { - const username = storeRegistry.getState('current-user').username - if (!username) { - logger.error('Making a convo while logged out?') - return - } - try { - const result = await T.RPCChat.localNewConversationLocalRpcPromise( - { - identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, - membersType: T.RPCChat.ConversationMembersType.impteamnative, - tlfName: [...new Set([username, ...participants])].join(','), - tlfVisibility: T.RPCGen.TLFVisibility.private, - topicType: T.RPCChat.TopicType.chat, - }, - S.waitingKeyChatCreating - ) - const {conv, uiConv} = result - const conversationIDKey = T.Chat.conversationIDToKey(conv.info.id) - if (!conversationIDKey) { - logger.warn("Couldn't make a new conversation?") - } else { - const meta = Meta.inboxUIItemToConversationMeta(uiConv) - if (meta) { - get().dispatch.metasReceived([meta]) - } - - const participantInfo: T.Chat.ParticipantInfo = uiParticipantsToParticipantInfo( - uiConv.participants ?? [] - ) - if (participantInfo.all.length > 0) { - storeRegistry - .getConvoState(T.Chat.stringToConversationIDKey(uiConv.convID)) - .dispatch.setParticipants(participantInfo) - } - storeRegistry - .getConvoState(conversationIDKey) - .dispatch.navigateToThread('justCreated', highlightMessageID) - } - } catch (error) { - if (error instanceof RPCError) { - const f = error.fields as Array<{key?: string}> | undefined - const errUsernames = f?.filter(elem => elem.key === 'usernames') as - | undefined - | Array<{key: string; value: string}> - let disallowedUsers: Array = [] - if (errUsernames?.length) { - const {value} = errUsernames[0] ?? {value: ''} - disallowedUsers = value.split(',') - } - const allowedUsers = participants.filter(x => !disallowedUsers.includes(x)) - get().dispatch.conversationErrored(allowedUsers, disallowedUsers, error.code, error.desc) - storeRegistry - .getConvoState(T.Chat.pendingErrorConversationIDKey) - .dispatch.navigateToThread('justCreated', highlightMessageID) - } - } - } - ignorePromise(f()) - }, - ensureWidgetMetas: () => { - const {inboxLayout} = get() - if (!inboxLayout?.widgetList) { - return - } - const missing = inboxLayout.widgetList.reduce>((l, v) => { - if (!storeRegistry.getConvoState(v.convID).isMetaGood()) { - l.push(v.convID) - } - return l - }, []) - if (missing.length === 0) { - return - } - get().dispatch.unboxRows(missing, true) - }, - fetchUserEmoji: (conversationIDKey, onlyInTeam) => { - const f = async () => { - const results = await T.RPCChat.localUserEmojisRpcPromise( - { - convID: - conversationIDKey && conversationIDKey !== T.Chat.noConversationIDKey - ? T.Chat.keyToConversationID(conversationIDKey) - : null, - opts: { - getAliases: true, - getCreationInfo: false, - onlyInTeam: onlyInTeam ?? false, - }, - }, - S.waitingKeyChatLoadingEmoji - ) - get().dispatch.loadedUserEmoji(results) - } - ignorePromise(f()) - }, - findGeneralConvIDFromTeamID: teamID => { - const f = async () => { - try { - const conv = await T.RPCChat.localFindGeneralConvFromTeamIDRpcPromise({teamID}) - const meta = Meta.inboxUIItemToConversationMeta(conv) - if (!meta) { - logger.info(`findGeneralConvIDFromTeamID: failed to convert to meta`) - return - } - get().dispatch.metasReceived([meta]) - set(s => { - s.teamIDToGeneralConvID.set(teamID, T.Chat.stringToConversationIDKey(conv.convID)) - }) - } catch (error) { - if (error instanceof RPCError) { - logger.info(`findGeneralConvIDFromTeamID: failed to get general conv: ${error.message}`) - } - } - } - ignorePromise(f()) - }, - inboxRefresh: reason => { - const f = async () => { - const {username} = storeRegistry.getState('current-user') - const {loggedIn} = storeRegistry.getState('config') - if (!loggedIn || !username) { - return - } - const clearExistingMetas = reason === 'inboxSyncedClear' - const clearExistingMessages = reason === 'inboxSyncedClear' - - logger.info(`Inbox refresh due to ${reason}`) - const reselectMode = - get().inboxHasLoaded || isPhone - ? T.RPCChat.InboxLayoutReselectMode.default - : T.RPCChat.InboxLayoutReselectMode.force - await T.RPCChat.localRequestInboxLayoutRpcPromise({reselectMode}) - if (clearExistingMetas) { - get().dispatch.clearMetas() - } - if (clearExistingMessages) { - for (const [, cs] of chatStores) { - cs.getState().dispatch.messagesClear() - } - } - } - ignorePromise(f()) - }, - inboxSearch: query => { - set(s => { - const {inboxSearch} = s - if (inboxSearch) { - inboxSearch.query = query - } - }) - const f = async () => { - const teamType = (t: T.RPCChat.TeamType) => (t === T.RPCChat.TeamType.complex ? 'big' : 'small') - - const onConvHits = (resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchConvHits']['inParam']) => { - const results = (resp.hits.hits || []).reduce>((arr, h) => { - arr.push({ - conversationIDKey: T.Chat.stringToConversationIDKey(h.convID), - name: h.name, - teamType: teamType(h.teamType), - }) - return arr - }, []) - - set(s => { - const unread = resp.hits.unreadMatches - const {inboxSearch} = s - if (inboxSearch?.nameStatus === 'inprogress') { - inboxSearch.nameResults = results - inboxSearch.nameResultsUnread = unread - inboxSearch.nameStatus = 'success' - } - }) - - const missingMetas = results.reduce>((arr, r) => { - if (!storeRegistry.getConvoState(r.conversationIDKey).isMetaGood()) { - arr.push(r.conversationIDKey) - } - return arr - }, []) - if (missingMetas.length > 0) { - get().dispatch.unboxRows(missingMetas, true) - } - } - - const onOpenTeamHits = ( - resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchTeamHits']['inParam'] - ) => { - const results = (resp.hits.hits || []).reduce>((arr, h) => { - const {description, name, memberCount, inTeam} = h - arr.push({ - description: description ?? '', - inTeam, - memberCount, - name, - publicAdmins: [], - }) - return arr - }, []) - const suggested = resp.hits.suggestedMatches - set(s => { - const {inboxSearch} = s - if (inboxSearch?.openTeamsStatus === 'inprogress') { - inboxSearch.openTeamsResultsSuggested = suggested - inboxSearch.openTeamsResults = T.castDraft(results) - inboxSearch.openTeamsStatus = 'success' - } - }) - } - - const onBotsHits = (resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchBotHits']['inParam']) => { - const results = resp.hits.hits || [] - const suggested = resp.hits.suggestedMatches - set(s => { - const {inboxSearch} = s - if (inboxSearch?.botsStatus === 'inprogress') { - inboxSearch.botsResultsSuggested = suggested - inboxSearch.botsResults = T.castDraft(results) - inboxSearch.botsStatus = 'success' - } - }) - } - - const onTextHit = (resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchInboxHit']['inParam']) => { - const {convID, convName, hits, query, teamType: tt, time} = resp.searchHit - - const result = { - conversationIDKey: T.Chat.conversationIDToKey(convID), - name: convName, - numHits: hits?.length ?? 0, - query, - teamType: teamType(tt), - time, - } as const - set(s => { - const {inboxSearch} = s - if (inboxSearch?.textStatus === 'inprogress') { - const {conversationIDKey} = result - const textResults = inboxSearch.textResults.filter( - r => r.conversationIDKey !== conversationIDKey - ) - textResults.push(result) - inboxSearch.textResults = textResults.sort((l, r) => r.time - l.time) - } - }) - - if ( - storeRegistry.getConvoState(result.conversationIDKey).meta.conversationIDKey === - T.Chat.noConversationIDKey - ) { - get().dispatch.unboxRows([result.conversationIDKey], true) - } - } - const onStart = () => { - set(s => { - const {inboxSearch} = s - if (inboxSearch) { - inboxSearch.nameStatus = 'inprogress' - inboxSearch.selectedIndex = 0 - inboxSearch.textResults = [] - inboxSearch.textStatus = 'inprogress' - inboxSearch.openTeamsStatus = 'inprogress' - inboxSearch.botsStatus = 'inprogress' - } - }) - } - const onDone = () => { - set(s => { - const status = 'success' - const inboxSearch = s.inboxSearch ?? makeInboxSearchInfo() - s.inboxSearch = T.castDraft(inboxSearch) - inboxSearch.textStatus = status - }) - } - - const onIndexStatus = ( - resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchIndexStatus']['inParam'] - ) => { - const percent = resp.status.percentIndexed - set(s => { - const {inboxSearch} = s - if (inboxSearch?.textStatus === 'inprogress') { - inboxSearch.indexPercent = percent - } - }) - } - - try { - await T.RPCChat.localSearchInboxRpcListener({ - incomingCallMap: { - 'chat.1.chatUi.chatSearchBotHits': onBotsHits, - 'chat.1.chatUi.chatSearchConvHits': onConvHits, - 'chat.1.chatUi.chatSearchInboxDone': onDone, - 'chat.1.chatUi.chatSearchInboxHit': onTextHit, - 'chat.1.chatUi.chatSearchInboxStart': onStart, - 'chat.1.chatUi.chatSearchIndexStatus': onIndexStatus, - 'chat.1.chatUi.chatSearchTeamHits': onOpenTeamHits, - }, - params: { - identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, - namesOnly: false, - opts: { - afterContext: 0, - beforeContext: 0, - isRegex: false, - matchMentions: false, - maxBots: 10, - maxConvsHit: inboxSearchMaxTextResults, - maxConvsSearched: 0, - maxHits: inboxSearchMaxTextMessages, - maxMessages: -1, - maxNameConvs: query.length > 0 ? inboxSearchMaxNameResults : inboxSearchMaxUnreadNameResults, - maxTeams: 10, - reindexMode: T.RPCChat.ReIndexingMode.postsearchSync, - sentAfter: 0, - sentBefore: 0, - sentBy: '', - sentTo: '', - skipBotCache: false, - }, - query, - }, - }) - } catch (error) { - if (error instanceof RPCError) { - if (!(error.code === T.RPCGen.StatusCode.sccanceled)) { - logger.error('search failed: ' + error.message) - set(s => { - const status = 'error' - const inboxSearch = s.inboxSearch ?? makeInboxSearchInfo() - s.inboxSearch = T.castDraft(inboxSearch) - inboxSearch.textStatus = status - }) - } - } - } - } - ignorePromise(f()) - }, - inboxSearchMoveSelectedIndex: increment => { - set(s => { - const {inboxSearch} = s - if (inboxSearch) { - const {selectedIndex} = inboxSearch - const totalResults = inboxSearch.nameResults.length + inboxSearch.textResults.length - if (increment && selectedIndex < totalResults - 1) { - inboxSearch.selectedIndex = selectedIndex + 1 - } else if (!increment && selectedIndex > 0) { - inboxSearch.selectedIndex = selectedIndex - 1 - } - } - }) - }, - inboxSearchSelect: (_conversationIDKey, q, selectedIndex) => { - let conversationIDKey = _conversationIDKey - let query = q - set(s => { - const {inboxSearch} = s - if (inboxSearch && selectedIndex !== undefined) { - inboxSearch.selectedIndex = selectedIndex - } - }) - - const {inboxSearch} = get() - if (!inboxSearch) { - return - } - const selected = getInboxSearchSelected(inboxSearch) - if (!conversationIDKey) { - conversationIDKey = selected?.conversationIDKey - } - - if (!conversationIDKey) { - return - } - if (!query) { - query = selected?.query - } - - storeRegistry.getConvoState(conversationIDKey).dispatch.navigateToThread('inboxSearch') - if (query) { - const cs = storeRegistry.getConvoState(conversationIDKey) - cs.dispatch.setThreadSearchQuery(query) - cs.dispatch.toggleThreadSearch(false) - cs.dispatch.threadSearch(query) - } else { - get().dispatch.toggleInboxSearch(false) - } - }, - loadStaticConfig: () => { - if (get().staticConfig) { - return - } - const {handshakeVersion, dispatch} = storeRegistry.getState('daemon') - const f = async () => { - const name = 'chat.loadStatic' - dispatch.wait(name, handshakeVersion, true) - try { - const res = await T.RPCChat.localGetStaticConfigRpcPromise() - if (!res.deletableByDeleteHistory) { - logger.error('chat.loadStaticConfig: got no deletableByDeleteHistory in static config') - return - } - const deletableByDeleteHistory = res.deletableByDeleteHistory.reduce>( - (res, type) => { - const ourTypes = Message.serviceMessageTypeToMessageTypes(type) - res.push(...ourTypes) - return res - }, - [] - ) - set(s => { - s.staticConfig = { - builtinCommands: (res.builtinCommands || []).reduce( - (map, c) => { - map[c.typ] = T.castDraft(c.commands) || [] - return map - }, - { - [T.RPCChat.ConversationBuiltinCommandTyp.none]: [], - [T.RPCChat.ConversationBuiltinCommandTyp.adhoc]: [], - [T.RPCChat.ConversationBuiltinCommandTyp.smallteam]: [], - [T.RPCChat.ConversationBuiltinCommandTyp.bigteam]: [], - [T.RPCChat.ConversationBuiltinCommandTyp.bigteamgeneral]: [], - } - ), - deletableByDeleteHistory: new Set(deletableByDeleteHistory), - } - }) - } finally { - dispatch.wait(name, handshakeVersion, false) - } - } - ignorePromise(f()) - }, - loadedUserEmoji: results => { - set(s => { - const newEmojis: Array = [] - results.emojis.emojis?.forEach(group => { - group.emojis?.forEach(e => newEmojis.push(e)) - }) - s.userEmojisForAutocomplete = newEmojis - s.userEmojis = T.castDraft(results.emojis.emojis) ?? [] - }) - }, - maybeChangeSelectedConv: () => { - const {inboxLayout} = get() - const newConvID = inboxLayout?.reselectInfo?.newConvID - const oldConvID = inboxLayout?.reselectInfo?.oldConvID - - const selectedConversation = Common.getSelectedConversation() - - if (!newConvID && !oldConvID) { - return - } - - const existingValid = T.Chat.isValidConversationIDKey(selectedConversation) - // no new id, just take the opportunity to resolve - if (!newConvID) { - if (!existingValid && isPhone) { - logger.info(`maybeChangeSelectedConv: no new and no valid, so go to inbox`) - get().dispatch.navigateToInbox(false) - } - return - } - // not matching? - if (selectedConversation !== oldConvID) { - if (!existingValid && isPhone) { - logger.info(`maybeChangeSelectedConv: no new and no valid, so go to inbox`) - get().dispatch.navigateToInbox(false) - } - return - } - // matching - if (isPhone) { - // on mobile just head back to the inbox if we have something selected - if (T.Chat.isValidConversationIDKey(selectedConversation)) { - logger.info(`maybeChangeSelectedConv: mobile: navigating up on conv change`) - get().dispatch.navigateToInbox(false) - return - } - logger.info(`maybeChangeSelectedConv: mobile: ignoring conv change, no conv selected`) - return - } else { - logger.info( - `maybeChangeSelectedConv: selecting new conv: new:${newConvID} old:${oldConvID} prevselected ${selectedConversation}` - ) - storeRegistry.getConvoState(newConvID).dispatch.navigateToThread('findNewestConversation') - } - }, - messageSendByUsername: (username, text, waitingKey) => { - const f = async () => { - const tlfName = `${storeRegistry.getState('current-user').username},${username}` - try { - const result = await T.RPCChat.localNewConversationLocalRpcPromise( - { - identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, - membersType: T.RPCChat.ConversationMembersType.impteamnative, - tlfName, - tlfVisibility: T.RPCGen.TLFVisibility.private, - topicType: T.RPCChat.TopicType.chat, - }, - waitingKey - ) - storeRegistry - .getConvoState(T.Chat.conversationIDToKey(result.conv.info.id)) - .dispatch.sendMessage(text) - } catch (error) { - if (error instanceof RPCError) { - logger.warn('Could not send in messageSendByUsernames', error.message) - } - } - } - ignorePromise(f()) - }, - metasReceived: (metas, removals) => { - removals?.forEach(r => { - storeRegistry.getConvoState(r).dispatch.setMeta() - }) - metas.forEach(m => { - const {meta: oldMeta, dispatch, isMetaGood} = storeRegistry.getConvoState(m.conversationIDKey) - if (isMetaGood()) { - dispatch.updateMeta(Meta.updateMeta(oldMeta, m)) - } else { - dispatch.setMeta(m) - } - }) - - const selectedConversation = Common.getSelectedConversation() - const {isMetaGood, meta} = storeRegistry.getConvoState(selectedConversation) - if (isMetaGood()) { - const {teamID} = meta - if (!storeRegistry.getState('teams').teamIDToMembers.get(teamID) && meta.teamname) { - storeRegistry.getState('teams').dispatch.getMembers(teamID) - } - } - }, - navigateToInbox: (allowSwitchTab = true) => { - // components can call us during render sometimes so always defer - setTimeout(() => { - navUpToScreen('chatRoot') - if (allowSwitchTab) { - switchTab(Tabs.chatTab) - } - }, 1) - }, - onChatInboxSynced: action => { - const {syncRes} = action.payload.params - const {clear} = storeRegistry.getState('waiting').dispatch - const {inboxRefresh} = get().dispatch - clear(S.waitingKeyChatInboxSyncStarted) - - switch (syncRes.syncType) { - // Just clear it all - case T.RPCChat.SyncInboxResType.clear: - inboxRefresh('inboxSyncedClear') - break - // We're up to date - case T.RPCChat.SyncInboxResType.current: - break - // We got some new messages appended - case T.RPCChat.SyncInboxResType.incremental: { - const items = syncRes.incremental.items || [] - const selectedConversation = Common.getSelectedConversation() - let loadMore = false as boolean - const metas = items.reduce>((arr, i) => { - const meta = Meta.unverifiedInboxUIItemToConversationMeta(i.conv) - if (meta) { - arr.push(meta) - if (meta.conversationIDKey === selectedConversation) { - loadMore = true - } - } - return arr - }, []) - if (loadMore) { - storeRegistry.getConvoState(selectedConversation).dispatch.loadMoreMessages({reason: 'got stale'}) - } - const removals = syncRes.incremental.removals?.map(T.Chat.stringToConversationIDKey) - // Update new untrusted - if (metas.length || removals?.length) { - get().dispatch.metasReceived(metas, removals) - } - - get().dispatch.unboxRows( - items.filter(i => i.shouldUnbox).map(i => T.Chat.stringToConversationIDKey(i.conv.convID)), - true - ) - break - } - default: - inboxRefresh('inboxSyncedUnknown') - } - }, - onChatThreadStale: (action: EngineGen.Chat1NotifyChatChatThreadsStalePayload) => { - const {updates} = action.payload.params - const keys = ['clear', 'newactivity'] as const - if (__DEV__) { - if (keys.length * 2 !== Object.keys(T.RPCChat.StaleUpdateType).length) { - throw new Error('onChatThreadStale invalid enum') - } - } - let loadMore = false as boolean - const selectedConversation = Common.getSelectedConversation() - keys.forEach(key => { - const conversationIDKeys = (updates || []).reduce>((arr, u) => { - const cid = T.Chat.conversationIDToKey(u.convID) - if (u.updateType === T.RPCChat.StaleUpdateType[key]) { - arr.push(cid) - } - // mentioned? - if (cid === selectedConversation) { - loadMore = true - } - return arr - }, []) - // load the inbox instead - if (conversationIDKeys.length > 0) { - logger.info( - `onChatThreadStale: dispatching thread reload actions for ${conversationIDKeys.length} convs of type ${key}` - ) - get().dispatch.unboxRows(conversationIDKeys, true) - if (T.RPCChat.StaleUpdateType[key] === T.RPCChat.StaleUpdateType.clear) { - conversationIDKeys.forEach(convID => storeRegistry.getConvoState(convID).dispatch.messagesClear()) - } - } - }) - if (loadMore) { - storeRegistry.getConvoState(selectedConversation).dispatch.loadMoreMessages({reason: 'got stale'}) - } - }, - onEngineIncomingImpl: action => { - switch (action.type) { - case EngineGen.chat1ChatUiChatInboxFailed: // fallthrough - case EngineGen.chat1NotifyChatChatSetConvSettings: // fallthrough - case EngineGen.chat1NotifyChatChatAttachmentUploadStart: // fallthrough - case EngineGen.chat1NotifyChatChatPromptUnfurl: // fallthrough - case EngineGen.chat1NotifyChatChatPaymentInfo: // fallthrough - case EngineGen.chat1NotifyChatChatRequestInfo: // fallthrough - case EngineGen.chat1NotifyChatChatAttachmentDownloadProgress: //fallthrough - case EngineGen.chat1NotifyChatChatAttachmentDownloadComplete: //fallthrough - case EngineGen.chat1NotifyChatChatAttachmentUploadProgress: { - const {convID} = action.payload.params - const conversationIDKey = T.Chat.conversationIDToKey(convID) - storeRegistry.getConvoState(conversationIDKey).dispatch.onEngineIncoming(action) - break - } - case EngineGen.chat1ChatUiChatCommandMarkdown: //fallthrough - case EngineGen.chat1ChatUiChatGiphyToggleResultWindow: // fallthrough - case EngineGen.chat1ChatUiChatCommandStatus: // fallthrough - case EngineGen.chat1ChatUiChatBotCommandsUpdateStatus: //fallthrough - case EngineGen.chat1ChatUiChatGiphySearchResults: { - const {convID} = action.payload.params - const conversationIDKey = T.Chat.stringToConversationIDKey(convID) - storeRegistry.getConvoState(conversationIDKey).dispatch.onEngineIncoming(action) - break - } - case EngineGen.chat1NotifyChatChatParticipantsInfo: { - const {participants: participantMap} = action.payload.params - Object.keys(participantMap ?? {}).forEach(convIDStr => { - const participants = participantMap?.[convIDStr] - const conversationIDKey = T.Chat.stringToConversationIDKey(convIDStr) - if (participants) { - storeRegistry - .getConvoState(conversationIDKey) - .dispatch.setParticipants(uiParticipantsToParticipantInfo(participants)) - } - }) - break - } - case EngineGen.chat1ChatUiChatMaybeMentionUpdate: { - const {teamName, channel, info} = action.payload.params - get().dispatch.setMaybeMentionInfo(getTeamMentionName(teamName, channel), info) - break - } - case EngineGen.chat1NotifyChatChatConvUpdate: { - const {conv} = action.payload.params - if (conv) { - const meta = Meta.inboxUIItemToConversationMeta(conv) - meta && get().dispatch.metasReceived([meta]) - } - break - } - case EngineGen.chat1ChatUiChatCoinFlipStatus: { - const {statuses} = action.payload.params - get().dispatch.updateCoinFlipStatus(statuses || []) - break - } - case EngineGen.chat1NotifyChatChatThreadsStale: - get().dispatch.onChatThreadStale(action) - break - case EngineGen.chat1NotifyChatChatSubteamRename: { - const {convs} = action.payload.params - const conversationIDKeys = (convs ?? []).map(c => T.Chat.stringToConversationIDKey(c.convID)) - get().dispatch.unboxRows(conversationIDKeys, true) - break - } - case EngineGen.chat1NotifyChatChatTLFFinalize: - get().dispatch.unboxRows([T.Chat.conversationIDToKey(action.payload.params.convID)]) - break - case EngineGen.chat1NotifyChatChatIdentifyUpdate: { - // Some participants are broken/fixed now - const {update} = action.payload.params - const usernames = update.CanonicalName.split(',') - const broken = (update.breaks.breaks || []).map(b => b.user.username) - const updates = usernames.map(name => ({info: {broken: broken.includes(name)}, name})) - storeRegistry.getState('users').dispatch.updates(updates) - break - } - case EngineGen.chat1ChatUiChatInboxUnverified: - get().dispatch.onGetInboxUnverifiedConvs(action) - break - case EngineGen.chat1NotifyChatChatInboxSyncStarted: - storeRegistry.getState('waiting').dispatch.increment(S.waitingKeyChatInboxSyncStarted) - break - - case EngineGen.chat1NotifyChatChatInboxSynced: - get().dispatch.onChatInboxSynced(action) - break - case EngineGen.chat1ChatUiChatInboxLayout: - get().dispatch.updateInboxLayout(action.payload.params.layout) - get().dispatch.maybeChangeSelectedConv() - get().dispatch.ensureWidgetMetas() - break - case EngineGen.chat1NotifyChatChatInboxStale: - get().dispatch.inboxRefresh('inboxStale') - break - case EngineGen.chat1ChatUiChatInboxConversation: - get().dispatch.onGetInboxConvsUnboxed(action) - break - case EngineGen.chat1NotifyChatNewChatActivity: { - const {activity} = action.payload.params - switch (activity.activityType) { - case T.RPCChat.ChatActivityType.incomingMessage: { - const {incomingMessage} = activity - const conversationIDKey = T.Chat.conversationIDToKey(incomingMessage.convID) - storeRegistry.getConvoState(conversationIDKey).dispatch.onIncomingMessage(incomingMessage) - get().dispatch.onIncomingInboxUIItem(incomingMessage.conv ?? undefined) - break - } - case T.RPCChat.ChatActivityType.setStatus: - get().dispatch.onIncomingInboxUIItem(activity.setStatus.conv ?? undefined) - break - case T.RPCChat.ChatActivityType.readMessage: - get().dispatch.onIncomingInboxUIItem(activity.readMessage.conv ?? undefined) - break - case T.RPCChat.ChatActivityType.newConversation: - get().dispatch.onIncomingInboxUIItem(activity.newConversation.conv ?? undefined) - break - case T.RPCChat.ChatActivityType.failedMessage: { - const {failedMessage} = activity - get().dispatch.onIncomingInboxUIItem(failedMessage.conv ?? undefined) - const {outboxRecords} = failedMessage - if (!outboxRecords) return - for (const outboxRecord of outboxRecords) { - const s = outboxRecord.state - if (s.state !== T.RPCChat.OutboxStateType.error) return - const {error} = s - const conversationIDKey = T.Chat.conversationIDToKey(outboxRecord.convID) - const outboxID = T.Chat.rpcOutboxIDToOutboxID(outboxRecord.outboxID) - // This is temp until fixed by CORE-7112. We get this error but not the call to let us show the red banner - const reason = Message.rpcErrorToString(error) - storeRegistry - .getConvoState(conversationIDKey) - .dispatch.onMessageErrored(outboxID, reason, error.typ) - - if (error.typ === T.RPCChat.OutboxErrorType.identify) { - // Find out the user who failed identify - const match = error.message.match(/"(.*)"/) - const tempForceRedBox = match?.[1] - if (tempForceRedBox) { - storeRegistry - .getState('users') - .dispatch.updates([{info: {broken: true}, name: tempForceRedBox}]) - } - } - } - break - } - case T.RPCChat.ChatActivityType.membersUpdate: - get().dispatch.unboxRows([T.Chat.conversationIDToKey(activity.membersUpdate.convID)], true) - break - case T.RPCChat.ChatActivityType.setAppNotificationSettings: { - const {setAppNotificationSettings} = activity - const conversationIDKey = T.Chat.conversationIDToKey(setAppNotificationSettings.convID) - const settings = setAppNotificationSettings.settings - const cs = storeRegistry.getConvoState(conversationIDKey) - if (cs.isMetaGood()) { - cs.dispatch.updateMeta(Meta.parseNotificationSettings(settings)) - } - break - } - case T.RPCChat.ChatActivityType.expunge: { - // Get actions to update messagemap / metamap when retention policy expunge happens - const {expunge} = activity - const conversationIDKey = T.Chat.conversationIDToKey(expunge.convID) - const staticConfig = get().staticConfig - // The types here are askew. It confuses frontend MessageType with protocol MessageType. - // Placeholder is an example where it doesn't make sense. - const deletableMessageTypes = staticConfig?.deletableByDeleteHistory || Common.allMessageTypes - storeRegistry.getConvoState(conversationIDKey).dispatch.messagesWereDeleted({ - deletableMessageTypes, - upToMessageID: T.Chat.numberToMessageID(expunge.expunge.upto), - }) - break - } - case T.RPCChat.ChatActivityType.ephemeralPurge: { - const {ephemeralPurge} = activity - // Get actions to update messagemap / metamap when ephemeral messages expire - const conversationIDKey = T.Chat.conversationIDToKey(ephemeralPurge.convID) - const messageIDs = ephemeralPurge.msgs?.reduce>((arr, msg) => { - const msgID = Message.getMessageID(msg) - if (msgID) { - arr.push(msgID) - } - return arr - }, []) - - !!messageIDs && - storeRegistry.getConvoState(conversationIDKey).dispatch.messagesExploded(messageIDs) - break - } - case T.RPCChat.ChatActivityType.reactionUpdate: { - // Get actions to update the messagemap when reactions are updated - const {reactionUpdate} = activity - const conversationIDKey = T.Chat.conversationIDToKey(reactionUpdate.convID) - if (!reactionUpdate.reactionUpdates || reactionUpdate.reactionUpdates.length === 0) { - logger.warn(`Got ReactionUpdateNotif with no reactionUpdates for convID=${conversationIDKey}`) - break - } - const updates = reactionUpdate.reactionUpdates.map(ru => ({ - reactions: Message.reactionMapToReactions(ru.reactions), - targetMsgID: T.Chat.numberToMessageID(ru.targetMsgID), - })) - logger.info(`Got ${updates.length} reaction updates for convID=${conversationIDKey}`) - storeRegistry.getConvoState(conversationIDKey).dispatch.updateReactions(updates) - get().dispatch.updateUserReacjis(reactionUpdate.userReacjis) - break - } - case T.RPCChat.ChatActivityType.messagesUpdated: { - const {messagesUpdated} = activity - const conversationIDKey = T.Chat.conversationIDToKey(messagesUpdated.convID) - storeRegistry.getConvoState(conversationIDKey).dispatch.onMessagesUpdated(messagesUpdated) - break - } - default: - } - break - } - case EngineGen.chat1NotifyChatChatTypingUpdate: { - const {typingUpdates} = action.payload.params - typingUpdates?.forEach(u => { - storeRegistry - .getConvoState(T.Chat.conversationIDToKey(u.convID)) - .dispatch.setTyping(new Set(u.typers?.map(t => t.username))) - }) - break - } - case EngineGen.chat1NotifyChatChatSetConvRetention: { - const {conv, convID} = action.payload.params - if (!conv) { - logger.warn('onChatSetConvRetention: no conv given') - return - } - const meta = Meta.inboxUIItemToConversationMeta(conv) - if (!meta) { - logger.warn(`onChatSetConvRetention: no meta found for ${convID.toString()}`) - return - } - const cs = storeRegistry.getConvoState(meta.conversationIDKey) - // only insert if the convo is already in the inbox - if (cs.isMetaGood()) { - cs.dispatch.setMeta(meta) - } - break - } - case EngineGen.chat1NotifyChatChatSetTeamRetention: { - const {convs} = action.payload.params - const metas = (convs ?? []).reduce>((l, c) => { - const meta = Meta.inboxUIItemToConversationMeta(c) - if (meta) { - l.push(meta) - } - return l - }, []) - if (metas.length) { - metas.forEach(meta => { - const cs = storeRegistry.getConvoState(meta.conversationIDKey) - // only insert if the convo is already in the inbox - if (cs.isMetaGood()) { - cs.dispatch.setMeta(meta) - } - }) - storeRegistry.getState('teams').dispatch.updateTeamRetentionPolicy(metas) - } - // this is a more serious problem, but we don't need to bug the user about it - logger.error( - 'got NotifyChat.ChatSetTeamRetention with no attached InboxUIItems. The local version may be out of date' - ) - break - } - case EngineGen.keybase1NotifyBadgesBadgeState: { - const {badgeState} = action.payload.params - get().dispatch.badgesUpdated(badgeState) - break - } - case EngineGen.keybase1GregorUIPushState: { - const {state} = action.payload.params - const items = state.items || [] - const goodState = items.reduce>( - (arr, {md, item}) => { - md && item && arr.push({item, md}) - return arr - }, - [] - ) - if (goodState.length !== items.length) { - logger.warn('Lost some messages in filtering out nonNull gregor items') - } - get().dispatch.updatedGregor(goodState) - break - } - default: - } - }, - onGetInboxConvsUnboxed: (action: EngineGen.Chat1ChatUiChatInboxConversationPayload) => { - // TODO not reactive - const {infoMap} = storeRegistry.getState('users') - const {convs} = action.payload.params - const inboxUIItems = JSON.parse(convs) as Array - const metas: Array = [] - let added = false as boolean - const usernameToFullname: {[username: string]: string} = {} - inboxUIItems.forEach(inboxUIItem => { - const meta = Meta.inboxUIItemToConversationMeta(inboxUIItem) - if (meta) { - metas.push(meta) - } - const participantInfo: T.Chat.ParticipantInfo = uiParticipantsToParticipantInfo( - inboxUIItem.participants ?? [] - ) - if (participantInfo.all.length > 0) { - storeRegistry - .getConvoState(T.Chat.stringToConversationIDKey(inboxUIItem.convID)) - .dispatch.setParticipants(participantInfo) - } - inboxUIItem.participants?.forEach((part: T.RPCChat.UIParticipant) => { - const {assertion, fullName} = part - if (!infoMap.get(assertion) && fullName) { - added = true - usernameToFullname[assertion] = fullName - } - }) - }) - if (added) { - storeRegistry.getState('users').dispatch.updates( - Object.keys(usernameToFullname).map(name => ({ - info: {fullname: usernameToFullname[name]}, - name, - })) - ) - } - if (metas.length > 0) { - get().dispatch.metasReceived(metas) - } - }, - onGetInboxUnverifiedConvs: (action: EngineGen.Chat1ChatUiChatInboxUnverifiedPayload) => { - const {inbox} = action.payload.params - const result = JSON.parse(inbox) as T.RPCChat.UnverifiedInboxUIItems - const items: ReadonlyArray = result.items ?? [] - // We get a subset of meta information from the cache even in the untrusted payload - const metas = items.reduce>((arr, item) => { - const m = Meta.unverifiedInboxUIItemToConversationMeta(item) - m && arr.push(m) - return arr - }, []) - get().dispatch.setTrustedInboxHasLoaded() - // Check if some of our existing stored metas might no longer be valid - get().dispatch.metasReceived(metas) - }, - onIncomingInboxUIItem: conv => { - if (!conv) return - const meta = Meta.inboxUIItemToConversationMeta(conv) - const usernameToFullname = (conv.participants ?? []).reduce<{[key: string]: string}>((map, part) => { - if (part.fullName) { - map[part.assertion] = part.fullName - } - return map - }, {}) - - storeRegistry.getState('users').dispatch.updates( - Object.keys(usernameToFullname).map(name => ({ - info: {fullname: usernameToFullname[name]}, - name, - })) - ) - - if (meta) { - get().dispatch.metasReceived([meta]) - } - }, - onRouteChanged: (prev, next) => { - const maybeChangeChatSelection = () => { - const wasModal = prev && Router2.getModalStack(prev).length > 0 - const isModal = next && Router2.getModalStack(next).length > 0 - // ignore if changes involve a modal - if (wasModal || isModal) { - return - } - const p = Router2.getVisibleScreen(prev) - const n = Router2.getVisibleScreen(next) - const wasChat = p?.name === Common.threadRouteName - const isChat = n?.name === Common.threadRouteName - // nothing to do with chat - if (!wasChat && !isChat) { - return - } - const pParams = p?.params as undefined | {conversationIDKey?: T.Chat.ConversationIDKey} - const nParams = n?.params as undefined | {conversationIDKey?: T.Chat.ConversationIDKey} - const wasID = pParams?.conversationIDKey - const isID = nParams?.conversationIDKey - - logger.info('maybeChangeChatSelection ', {isChat, isID, wasChat, wasID}) - - // same? ignore - if (wasChat && isChat && wasID === isID) { - // if we've never loaded anything, keep going so we load it - if (!isID || storeRegistry.getConvoState(isID).loaded) { - return - } - } - - // deselect if there was one - const deselectAction = () => { - if (wasChat && wasID && T.Chat.isValidConversationIDKey(wasID)) { - get().dispatch.unboxRows([wasID], true) - // needed? - // storeRegistry.getConvoState(wasID).dispatch.clearOrangeLine('deselected') - } - } - - // still chatting? just select new one - if (wasChat && isChat && isID && T.Chat.isValidConversationIDKey(isID)) { - deselectAction() - storeRegistry.getConvoState(isID).dispatch.selectedConversation() - return - } - - // leaving a chat - if (wasChat && !isChat) { - deselectAction() - return - } - - // going into a chat - if (isChat && isID && T.Chat.isValidConversationIDKey(isID)) { - deselectAction() - storeRegistry.getConvoState(isID).dispatch.selectedConversation() - return - } - } - - const maybeChatTabSelected = () => { - if (Router2.getTab(prev) !== Tabs.chatTab && Router2.getTab(next) === Tabs.chatTab) { - const n = Router2.getVisibleScreen(next) - const nParams = n?.params as undefined | {conversationIDKey?: T.Chat.ConversationIDKey} - const isID = nParams?.conversationIDKey - isID && storeRegistry.getConvoState(isID).dispatch.tabSelected() - } - } - maybeChangeChatSelection() - maybeChatTabSelected() - }, - onTeamBuildingFinished: users => { - const f = async () => { - // need to let the mdoal hide first else its thrashy - await timeoutPromise(500) - storeRegistry - .getConvoState(T.Chat.pendingWaitingConversationIDKey) - .dispatch.navigateToThread('justCreated') - get().dispatch.createConversation([...users].map(u => u.id)) - } - ignorePromise(f()) - }, - paymentInfoReceived: paymentInfo => { - set(s => { - s.paymentStatusMap.set(paymentInfo.paymentID, paymentInfo) - }) - }, - previewConversation: p => { - // We always make adhoc convos and never preview it - const previewConversationPersonMakesAConversation = () => { - const {participants, teamname, highlightMessageID} = p - if (teamname) return - if (!participants) return - const toFind = [...participants].sort().join(',') - const toFindN = participants.length - for (const cs of chatStores.values()) { - const names = cs.getState().participants.name - if (names.length !== toFindN) continue - const p = [...names].sort().join(',') - if (p === toFind) { - storeRegistry - .getConvoState(cs.getState().id) - .dispatch.navigateToThread('justCreated', highlightMessageID) - return - } - } - - storeRegistry - .getConvoState(T.Chat.pendingWaitingConversationIDKey) - .dispatch.navigateToThread('justCreated') - get().dispatch.createConversation(participants, highlightMessageID) - } - - // We preview channels - const previewConversationTeam = async () => { - const {conversationIDKey, highlightMessageID, teamname, reason} = p - if (conversationIDKey) { - if ( - reason === 'messageLink' || - reason === 'teamMention' || - reason === 'channelHeader' || - reason === 'manageView' - ) { - // Add preview channel to inbox - await T.RPCChat.localPreviewConversationByIDLocalRpcPromise({ - convID: T.Chat.keyToConversationID(conversationIDKey), - }) - } - - storeRegistry - .getConvoState(conversationIDKey) - .dispatch.navigateToThread('previewResolved', highlightMessageID) - return - } - - if (!teamname) { - return - } - - const channelname = p.channelname || 'general' - try { - const results = await T.RPCChat.localFindConversationsLocalRpcPromise({ - identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, - membersType: T.RPCChat.ConversationMembersType.team, - oneChatPerTLF: true, - tlfName: teamname, - topicName: channelname, - topicType: T.RPCChat.TopicType.chat, - visibility: T.RPCGen.TLFVisibility.private, - }) - const resultMetas = (results.uiConversations || []) - .map(row => Meta.inboxUIItemToConversationMeta(row)) - .filter(Boolean) - - const first = resultMetas[0] - if (!first) { - if (p.reason === 'appLink') { - storeRegistry - .getState('deeplinks') - .dispatch.setLinkError( - "We couldn't find this team chat channel. Please check that you're a member of the team and the channel exists." - ) - navigateAppend('keybaseLinkError') - return - } else { - return - } - } - - const results2 = await T.RPCChat.localPreviewConversationByIDLocalRpcPromise({ - convID: T.Chat.keyToConversationID(first.conversationIDKey), - }) - const meta = Meta.inboxUIItemToConversationMeta(results2.conv) - if (meta) { - storeRegistry.getState('chat').dispatch.metasReceived([meta]) - } - - storeRegistry - .getConvoState(first.conversationIDKey) - .dispatch.navigateToThread('previewResolved', highlightMessageID) - } catch (error) { - if ( - error instanceof RPCError && - error.code === T.RPCGen.StatusCode.scteamnotfound && - reason === 'appLink' - ) { - storeRegistry - .getState('deeplinks') - .dispatch.setLinkError( - "We couldn't find this team. Please check that you're a member of the team and the channel exists." - ) - navigateAppend('keybaseLinkError') - return - } else { - throw error - } - } - } - previewConversationPersonMakesAConversation() - ignorePromise(previewConversationTeam()) - }, - queueMetaHandle: () => { - // Watch the meta queue and take up to 10 items. Choose the last items first since they're likely still visible - const f = async () => { - const maxToUnboxAtATime = 10 - const ar = [...metaQueue] - const maybeUnbox = ar.slice(0, maxToUnboxAtATime) - metaQueue = new Set(ar.slice(maxToUnboxAtATime)) - const conversationIDKeys = untrustedConversationIDKeys(maybeUnbox) - if (conversationIDKeys.length) { - get().dispatch.unboxRows(conversationIDKeys) - } - if (metaQueue.size && conversationIDKeys.length) { - await timeoutPromise(100) - } - if (metaQueue.size) { - get().dispatch.queueMetaHandle() - } - } - ignorePromise(f()) - }, - queueMetaToRequest: ids => { - let added = false as boolean - untrustedConversationIDKeys(ids).forEach(k => { - if (!metaQueue.has(k)) { - added = true - metaQueue.add(k) - } - }) - if (added) { - // only unboxMore if something changed - get().dispatch.queueMetaHandle() - } else { - logger.info('skipping meta queue run, queue unchanged') - } - }, - refreshBotPublicCommands: username => { - set(s => { - s.botPublicCommands.delete(username) - }) - const f = async () => { - let res: T.RPCChat.ListBotCommandsLocalRes | undefined - try { - res = await T.RPCChat.localListPublicBotCommandsLocalRpcPromise({ - username, - }) - } catch (error) { - if (error instanceof RPCError) { - logger.info('refreshBotPublicCommands: failed to get public commands: ' + error.message) - set(s => { - s.botPublicCommands.set(username, {commands: [], loadError: true}) - }) - } - } - const commands = (res?.commands ?? []).reduce>((l, c) => { - l.push(c.name) - return l - }, []) - - set(s => { - s.botPublicCommands.set(username, {commands, loadError: false}) - }) - } - ignorePromise(f()) - }, - resetConversationErrored: () => { - set(s => { - s.createConversationError = undefined - }) - }, - resetState: () => { - set(s => ({ - ...s, - ...initialStore, - dispatch: s.dispatch, - staticConfig: s.staticConfig, - })) - // also blow away convoState - clearChatStores() - }, - setInboxNumSmallRows: (rows, ignoreWrite) => { - set(s => { - if (rows > 0) { - s.inboxNumSmallRows = rows - } - }) - if (ignoreWrite) { - return - } - const {inboxNumSmallRows} = get() - if (inboxNumSmallRows === undefined || inboxNumSmallRows <= 0) { - return - } - const f = async () => { - try { - await T.RPCGen.configGuiSetValueRpcPromise({ - path: 'ui.inboxSmallRows', - value: {i: inboxNumSmallRows, isNull: false}, - }) - } catch {} - } - ignorePromise(f()) - }, - setInfoPanelTab: tab => { - set(s => { - s.infoPanelSelectedTab = tab - }) - }, - setMaybeMentionInfo: (name, info) => { - set(s => { - const {maybeMentionMap} = s - maybeMentionMap.set(name, T.castDraft(info)) - }) - }, - setTrustedInboxHasLoaded: () => { - set(s => { - s.trustedInboxHasLoaded = true - }) - }, - toggleInboxSearch: enabled => { - set(s => { - const {inboxSearch} = s - if (enabled && !inboxSearch) { - s.inboxSearch = T.castDraft(makeInboxSearchInfo()) - } else if (!enabled && inboxSearch) { - s.inboxSearch = undefined - } - }) - const f = async () => { - const {inboxSearch} = get() - if (!inboxSearch) { - await T.RPCChat.localCancelActiveInboxSearchRpcPromise() - return - } - if (inboxSearch.nameStatus === 'initial') { - get().dispatch.inboxSearch('') - } - } - ignorePromise(f()) - }, - toggleSmallTeamsExpanded: () => { - set(s => { - s.smallTeamsExpanded = !s.smallTeamsExpanded - }) - }, - unboxRows: (ids, force) => { - // We want to unbox rows that have scroll into view - const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { - return - } - - // Get valid keys that we aren't already loading or have loaded - const conversationIDKeys = ids.reduce((arr: Array, id) => { - if (id && T.Chat.isValidConversationIDKey(id)) { - const cs = storeRegistry.getConvoState(id) - const trustedState = cs.meta.trustedState - if (force || (trustedState !== 'requesting' && trustedState !== 'trusted')) { - arr.push(id) - cs.dispatch.updateMeta({trustedState: 'requesting'}) - } - } - return arr - }, []) - - if (!conversationIDKeys.length) { - return - } - logger.info( - `unboxRows: unboxing len: ${conversationIDKeys.length} convs: ${conversationIDKeys.join(',')}` - ) - try { - await T.RPCChat.localRequestInboxUnboxRpcPromise({ - convIDs: conversationIDKeys.map(k => T.Chat.keyToConversationID(k)), - }) - } catch (error) { - if (error instanceof RPCError) { - logger.info(`unboxRows: failed ${error.desc}`) - } - } - } - ignorePromise(f()) - }, - updateCoinFlipStatus: statuses => { - set(s => { - const {flipStatusMap} = s - statuses.forEach(status => { - flipStatusMap.set(status.gameID, T.castDraft(status)) - }) - }) - }, - updateInboxLayout: str => { - set(s => { - try { - const {inboxHasLoaded} = s - const _layout = JSON.parse(str) as unknown - if (!_layout || typeof _layout !== 'object') { - console.log('Invalid layout?') - return - } - const layout = _layout as T.RPCChat.UIInboxLayout - - if (!isEqual(s.inboxLayout, layout)) { - s.inboxLayout = T.castDraft(layout) - } - s.inboxHasLoaded = !!layout - if (!inboxHasLoaded) { - // on first layout, initialize any drafts and muted status - // After the first layout, any other updates will come in the form of meta updates. - layout.smallTeams?.forEach(t => { - const cs = storeRegistry.getConvoState(t.convID) - cs.dispatch.updateFromUIInboxLayout(t) - }) - layout.bigTeams?.forEach(t => { - if (t.state === T.RPCChat.UIInboxBigTeamRowTyp.channel) { - const cs = storeRegistry.getConvoState(t.channel.convID) - cs.dispatch.updateFromUIInboxLayout(t.channel) - } - }) - } - } catch (e) { - logger.info('failed to JSON parse inbox layout: ' + e) - } - }) - }, - updateInfoPanel: (show, tab) => { - set(s => { - s.infoPanelShowing = show - s.infoPanelSelectedTab = tab - }) - }, - updateLastCoord: coord => { - set(s => { - s.lastCoord = coord - }) - const f = async () => { - const {accuracy, lat, lon} = coord - await T.RPCChat.localLocationUpdateRpcPromise({coord: {accuracy, lat, lon}}) - } - ignorePromise(f()) - }, - updateUserReacjis: userReacjis => { - set(s => { - const {skinTone, topReacjis} = userReacjis - s.userReacjis.skinTone = skinTone - // filter out non-simple emojis - s.userReacjis.topReacjis = - T.castDraft(topReacjis)?.filter(r => /^:[^:]+:$/.test(r.name)) ?? defaultTopReacjis - }) - }, - updatedGregor: items => { - const explodingItems = items.filter(i => - i.item.category.startsWith(Common.explodingModeGregorKeyPrefix) - ) - if (!explodingItems.length) { - // No conversations have exploding modes, clear out what is set - for (const s of chatStores.values()) { - s.getState().dispatch.setExplodingMode(0, true) - } - } else { - // logger.info('Got push state with some exploding modes') - explodingItems.forEach(i => { - try { - const {category, body} = i.item - const secondsString = uint8ArrayToString(body) - const seconds = parseInt(secondsString, 10) - if (isNaN(seconds)) { - logger.warn(`Got dirty exploding mode ${secondsString} for category ${category}`) - return - } - const _conversationIDKey = category.substring(Common.explodingModeGregorKeyPrefix.length) - const conversationIDKey = T.Chat.stringToConversationIDKey(_conversationIDKey) - storeRegistry.getConvoState(conversationIDKey).dispatch.setExplodingMode(seconds, true) - } catch (e) { - logger.info('Error parsing exploding' + e) - } - }) - } - - set(s => { - const blockButtons = items.some(i => i.item.category.startsWith(blockButtonsGregorPrefix)) - if (blockButtons || s.blockButtonsMap.size > 0) { - const shouldKeepExistingBlockButtons = new Map() - s.blockButtonsMap.forEach((_, teamID: string) => shouldKeepExistingBlockButtons.set(teamID, false)) - items - .filter(i => i.item.category.startsWith(blockButtonsGregorPrefix)) - .forEach(i => { - try { - const teamID = i.item.category.substring(blockButtonsGregorPrefix.length) - if (!s.blockButtonsMap.get(teamID)) { - const body = bodyToJSON(i.item.body) as {adder: string} - const adder = body.adder - s.blockButtonsMap.set(teamID, {adder}) - } else { - shouldKeepExistingBlockButtons.set(teamID, true) - } - } catch (e) { - logger.info('block buttons parse fail', e) - } - }) - shouldKeepExistingBlockButtons.forEach((keep, teamID) => { - if (!keep) { - s.blockButtonsMap.delete(teamID) - } - }) - } - }) - }, - } - return { - ...initialStore, - dispatch, - getBackCount: conversationIDKey => { - let count = 0 - chatStores.forEach(s => { - const {id, badge} = s.getState() - // only show sum of badges that aren't for the current conversation - if (id !== conversationIDKey) { - count += badge - } - }) - return count - }, - getBadgeHiddenCount: ids => { - let badgeCount = 0 - let hiddenCount = 0 - - chatStores.forEach(s => { - const {id, badge} = s.getState() - if (ids.has(id)) { - badgeCount -= badge - hiddenCount -= 1 - } - }) - - return {badgeCount, hiddenCount} - }, - getUnreadIndicies: ids => { - const unreadIndices: Map = new Map() - ids.forEach((cur, idx) => { - Array.from(chatStores.values()).some(s => { - const {id, badge} = s.getState() - if (id === cur && badge > 0) { - unreadIndices.set(idx, badge) - return true - } - return false - }) - }) - return unreadIndices - }, - } -}) - -import {type ChatProviderProps, ProviderScreen} from './convostate' -import type {GetOptionsRet} from '@/constants/types/router2' - -export function makeChatScreen>( - Component: COM, - options?: { - getOptions?: GetOptionsRet | ((props: ChatProviderProps>) => GetOptionsRet) - skipProvider?: boolean - canBeNullConvoID?: boolean - } -) { - return { - ...options, - screen: function Screen(p: ChatProviderProps>) { - const Comp = Component as any - return options?.skipProvider ? ( - - ) : ( - - - - ) - }, - } -} - -export * from './convostate' -export * from './common' -export * from './meta' -export * from './message' - -export { - noConversationIDKey, - pendingWaitingConversationIDKey, - pendingErrorConversationIDKey, - isValidConversationIDKey, - dummyConversationIDKey, -} from '../types/chat2/common' diff --git a/shared/constants/chat2/message.tsx b/shared/constants/chat2/message.tsx index c35c576d07e2..b0cdf7684d5b 100644 --- a/shared/constants/chat2/message.tsx +++ b/shared/constants/chat2/message.tsx @@ -1,14 +1,14 @@ // Message related constants import * as T from '../types' -import * as TeamsUtil from '../teams/util' -import type * as ConvoConstants from './convostate' +import * as TeamsUtil from '@/constants/teams' +import type * as ConvoConstants from '@/stores/convostate' import HiddenString from '@/util/hidden-string' import logger from '@/logger' -import type * as MessageTypes from '../types/chat2/message' +import type * as MessageTypes from '@/constants/types/chat2/message' import type {ServiceId} from 'util/platforms' -import {noConversationIDKey} from '../types/chat2/common' +import {noConversationIDKey} from '@/constants/types/chat2/common' import invert from 'lodash/invert' -import {isIOS, isMobile} from '../platform' +import {isIOS, isMobile} from '@/constants/platform' const noString = new HiddenString('') diff --git a/shared/constants/chat2/meta.tsx b/shared/constants/chat2/meta.tsx index 9adc5eda010f..1540e8f9879f 100644 --- a/shared/constants/chat2/meta.tsx +++ b/shared/constants/chat2/meta.tsx @@ -1,10 +1,11 @@ // Meta manages the metadata about a conversation. Participants, isMuted, reset people, etc. Things that drive the inbox import {shallowEqual} from '../utils' -import * as T from '../types' -import * as Teams from '../teams/util' +import * as T from '@/constants/types' +import * as Teams from '@/constants/teams' import * as Message from './message' import {base64ToUint8Array, uint8ArrayToHex} from 'uint8array-extras' -import {storeRegistry} from '../store-registry' +import {storeRegistry} from '@/stores/store-registry' +import {useCurrentUserState} from '@/stores/current-user' const conversationMemberStatusToMembershipType = (m: T.RPCChat.ConversationMemberStatus) => { switch (m) { @@ -40,9 +41,9 @@ export const unverifiedInboxUIItemToConversationMeta = ( // We only treat implicit adhoc teams as having resetParticipants const resetParticipants: Set = new Set( i.localMetadata && - (i.membersType === T.RPCChat.ConversationMembersType.impteamnative || - i.membersType === T.RPCChat.ConversationMembersType.impteamupgrade) && - i.localMetadata.resetParticipants + (i.membersType === T.RPCChat.ConversationMembersType.impteamnative || + i.membersType === T.RPCChat.ConversationMembersType.impteamupgrade) && + i.localMetadata.resetParticipants ? i.localMetadata.resetParticipants : [] ) @@ -236,7 +237,7 @@ export const inboxUIItemToConversationMeta = ( const resetParticipants = new Set( (i.membersType === T.RPCChat.ConversationMembersType.impteamnative || i.membersType === T.RPCChat.ConversationMembersType.impteamupgrade) && - i.resetParticipants + i.resetParticipants ? i.resetParticipants : [] ) @@ -263,8 +264,8 @@ export const inboxUIItemToConversationMeta = ( const conversationIDKey = T.Chat.stringToConversationIDKey(i.convID) let pinnedMsg: T.Chat.PinnedMessageInfo | undefined if (i.pinnedMsg) { - const username = storeRegistry.getState('current-user').username - const devicename = storeRegistry.getState('current-user').deviceName + const username = useCurrentUserState.getState().username + const devicename = useCurrentUserState.getState().deviceName const getLastOrdinal = () => storeRegistry.getConvoState(conversationIDKey).messageOrdinals?.at(-1) ?? T.Chat.numberToOrdinal(0) const message = Message.uiMessageToMessage( diff --git a/shared/constants/chat2/util.tsx b/shared/constants/chat2/util.tsx deleted file mode 100644 index a1e5b2c74fc6..000000000000 --- a/shared/constants/chat2/util.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -export const onEngineConnected = () => { - const f = async () => { - try { - await T.RPCGen.delegateUiCtlRegisterChatUIRpcPromise() - await T.RPCGen.delegateUiCtlRegisterLogUIRpcPromise() - console.log('Registered Chat UI') - } catch (error) { - console.warn('Error in registering Chat UI:', error) - } - } - ignorePromise(f()) -} - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.chat1ChatUiChatInboxFailed: - case EngineGen.chat1NotifyChatChatSetConvSettings: - case EngineGen.chat1NotifyChatChatAttachmentUploadStart: - case EngineGen.chat1NotifyChatChatPromptUnfurl: - case EngineGen.chat1NotifyChatChatPaymentInfo: - case EngineGen.chat1NotifyChatChatRequestInfo: - case EngineGen.chat1NotifyChatChatAttachmentDownloadProgress: - case EngineGen.chat1NotifyChatChatAttachmentDownloadComplete: - case EngineGen.chat1NotifyChatChatAttachmentUploadProgress: - case EngineGen.chat1ChatUiChatCommandMarkdown: - case EngineGen.chat1ChatUiChatGiphyToggleResultWindow: - case EngineGen.chat1ChatUiChatCommandStatus: - case EngineGen.chat1ChatUiChatBotCommandsUpdateStatus: - case EngineGen.chat1ChatUiChatGiphySearchResults: - case EngineGen.chat1NotifyChatChatParticipantsInfo: - case EngineGen.chat1ChatUiChatMaybeMentionUpdate: - case EngineGen.chat1NotifyChatChatConvUpdate: - case EngineGen.chat1ChatUiChatCoinFlipStatus: - case EngineGen.chat1NotifyChatChatThreadsStale: - case EngineGen.chat1NotifyChatChatSubteamRename: - case EngineGen.chat1NotifyChatChatTLFFinalize: - case EngineGen.chat1NotifyChatChatIdentifyUpdate: - case EngineGen.chat1ChatUiChatInboxUnverified: - case EngineGen.chat1NotifyChatChatInboxSyncStarted: - case EngineGen.chat1NotifyChatChatInboxSynced: - case EngineGen.chat1ChatUiChatInboxLayout: - case EngineGen.chat1NotifyChatChatInboxStale: - case EngineGen.chat1ChatUiChatInboxConversation: - case EngineGen.chat1NotifyChatNewChatActivity: - case EngineGen.chat1NotifyChatChatTypingUpdate: - case EngineGen.chat1NotifyChatChatSetConvRetention: - case EngineGen.chat1NotifyChatChatSetTeamRetention: - case EngineGen.keybase1NotifyBadgesBadgeState: - case EngineGen.keybase1GregorUIPushState: - { - storeRegistry.getState('chat').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/config/util.tsx b/shared/constants/config.tsx similarity index 96% rename from shared/constants/config/util.tsx rename to shared/constants/config.tsx index b44de040a866..954a6daddde8 100644 --- a/shared/constants/config/util.tsx +++ b/shared/constants/config.tsx @@ -1,5 +1,5 @@ import uniq from 'lodash/uniq' -import {runMode} from '../platform' +import {runMode} from './platform' // An ugly error message from the service that we'd like to rewrite ourselves. export const invalidPasswordErrorString = 'Bad password: Invalid password. Server rejected login attempt..' diff --git a/shared/constants/crypto/util.tsx b/shared/constants/crypto.tsx similarity index 98% rename from shared/constants/crypto/util.tsx rename to shared/constants/crypto.tsx index 6f88e37d8e57..459215e60c1f 100644 --- a/shared/constants/crypto/util.tsx +++ b/shared/constants/crypto.tsx @@ -1,4 +1,4 @@ -import {isMobile} from '../platform' +import {isMobile} from './platform' export const saltpackDocumentation = 'https://saltpack.org' export const inputDesktopMaxHeight = {maxHeight: '30%'} as const diff --git a/shared/constants/deeplinks.tsx b/shared/constants/deeplinks.tsx new file mode 100644 index 000000000000..1b207eff3219 --- /dev/null +++ b/shared/constants/deeplinks.tsx @@ -0,0 +1,265 @@ +import * as Tabs from './tabs' +import URL from 'url-parse' +import logger from '@/logger' +import * as T from '@/constants/types' +import {navigateAppend, switchTab} from './router2' +import {storeRegistry} from '@/stores/store-registry' +import {useChatState} from '@/stores/chat2' +import {useProfileState} from '@/stores/profile' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {useTeamsState} from '@/stores/teams' + +const prefix = 'keybase://' +export const linkFromConvAndMessage = (conv: string, messageID: number) => + `${prefix}chat/${conv}/${messageID}` + +const isTeamPageAction = (a?: string): a is TeamPageAction => { + switch (a) { + case 'add_or_invite': + case 'manage_settings': + case 'join': + return true + default: + return false + } +} + +type TeamPageAction = 'add_or_invite' | 'manage_settings' | 'join' + +// This logic is copied from go/protocol/keybase1/extras.go. +const validTeamnamePart = (s: string): boolean => { + if (s.length < 2 || s.length > 16) { + return false + } + + return /^([a-zA-Z0-9][a-zA-Z0-9_]?)+$/.test(s) +} + +const validTeamname = (s: string) => s.split('.').every(validTeamnamePart) +const handleShowUserProfileLink = (username: string) => { + switchTab(Tabs.peopleTab) + useProfileState.getState().dispatch.showUserProfile(username) +} + +const isKeybaseIoUrl = (url: URL) => { + const {protocol} = url + if (protocol !== 'http:' && protocol !== 'https:') return false + if (url.username || url.password) return false + const {hostname} = url + if (hostname !== 'keybase.io' && hostname !== 'www.keybase.io') return false + const {port} = url + if (port) { + if (protocol === 'http:' && port !== '80') return false + if (protocol === 'https:' && port !== '443') return false + } + return true +} + +const urlToUsername = (url: URL) => { + if (!isKeybaseIoUrl(url)) { + return null + } + // Adapted username regexp (see libkb/checkers.go) with a leading /, an + // optional trailing / and a dash for custom links. + const match = url.pathname.match(/^\/((?:[a-zA-Z0-9][a-zA-Z0-9_-]?)+)\/?$/) + if (!match) { + return null + } + const usernameMatch = match[1] + if (!usernameMatch || usernameMatch.length < 2 || usernameMatch.length > 16) { + return null + } + // Ignore query string and hash parameters. + return usernameMatch.toLowerCase() +} + +const urlToTeamDeepLink = (url: URL) => { + if (!isKeybaseIoUrl(url)) { + return null + } + // Similar regexp to username but allow `.` for subteams + const match = url.pathname.match(/^\/team\/((?:[a-zA-Z0-9][a-zA-Z0-9_.-]?)+)\/?$/) + if (!match) { + return null + } + const teamName = match[1] + if (!teamName || teamName.length < 2 || teamName.length > 255) { + return null + } + // `url.query` has a wrong type in @types/url-parse. It's a `string` in the + // code, but @types claim it's a {[k: string]: string | undefined}. + const queryString = url.query as unknown as string + // URLSearchParams is not available in react-native. See if any of recognized + // query parameters is passed using regular expressions. + const action = (['add_or_invite', 'manage_settings'] satisfies readonly TeamPageAction[]).find( + x => queryString.search(`[?&]applink=${x}([?&].+)?$`) !== -1 + ) + return {action, teamName} +} + +const handleTeamPageLink = (teamname: string, action?: TeamPageAction) => { + useTeamsState + .getState() + .dispatch.showTeamByName( + teamname, + action === 'manage_settings' ? 'settings' : undefined, + action === 'join' ? true : undefined, + action === 'add_or_invite' ? true : undefined + ) +} + +export const handleAppLink = (link: string) => { + if (link.startsWith('keybase://')) { + handleKeybaseLink(link.replace('keybase://', '')) + return + } else { + // Normal deeplink + const url = new URL(link) + const username = urlToUsername(url) + if (username === 'phone-app') { + const phoneState = useSettingsPhoneState.getState() + const phones = (phoneState as {phones?: Map}).phones + if (!phones || phones.size > 0) { + return + } + switchTab(Tabs.settingsTab) + navigateAppend('settingsAddPhone') + } else if (username && username !== 'app') { + handleShowUserProfileLink(username) + return + } + const teamLink = urlToTeamDeepLink(url) + if (teamLink) { + handleTeamPageLink(teamLink.teamName, teamLink.action) + return + } + } +} + +export const handleKeybaseLink = (link: string) => { + if (!link) return + const error = + "We couldn't read this link. The link might be bad, or your Keybase app might be out of date and needs to be updated." + const parts = link.split('/') + // List guaranteed to contain at least one elem. + switch (parts[0]) { + case 'profile': + if (parts[1] === 'new-proof' && (parts.length === 3 || parts.length === 4)) { + parts.length === 4 && parts[3] && useProfileState.getState().dispatch.showUserProfile(parts[3]) + useProfileState.getState().dispatch.addProof(parts[2]!, 'appLink') + return + } else if (parts[1] === 'show' && parts.length === 3) { + // Username is basically a team name part, we can use the same logic to + // validate deep link. + const username = parts[2]! + if (username.length && validTeamnamePart(username)) { + return handleShowUserProfileLink(username) + } + } + break + // Fall-through + case 'private': + case 'public': + case 'team': + try { + const decoded = decodeURIComponent(link) + switchTab(Tabs.fsTab) + navigateAppend({props: {path: `/keybase/${decoded}`}, selected: 'fsRoot'}) + return + } catch { + logger.warn("Coudn't decode KBFS URI") + return + } + case 'convid': + if (parts.length === 2) { + const conversationIDKey = parts[1] + if (conversationIDKey) { + storeRegistry.getConvoState(conversationIDKey).dispatch.navigateToThread('navChanged') + } + return + } + break + case 'chat': + if (parts.length === 2 || parts.length === 3) { + if (parts[1]!.includes('#')) { + const teamChat = parts[1]!.split('#') + if (teamChat.length !== 2) { + navigateAppend({props: {error}, selected: 'keybaseLinkError'}) + return + } + const [teamname, channelname] = teamChat + const _highlightMessageID = parseInt(parts[2]!, 10) + if (_highlightMessageID < 0) { + logger.warn(`invalid chat message id: ${_highlightMessageID}`) + return + } + + const highlightMessageID = T.Chat.numberToMessageID(_highlightMessageID) + const {previewConversation} = useChatState.getState().dispatch + previewConversation({ + channelname, + highlightMessageID, + reason: 'appLink', + teamname, + }) + return + } else { + const highlightMessageID = parseInt(parts[2]!, 10) + if (highlightMessageID < 0) { + logger.warn(`invalid chat message id: ${highlightMessageID}`) + return + } + const {previewConversation} = useChatState.getState().dispatch + previewConversation({ + highlightMessageID: T.Chat.numberToMessageID(highlightMessageID), + participants: parts[1]!.split(','), + reason: 'appLink', + }) + return + } + } + break + case 'team-page': // keybase://team-page/{team_name}/{manage_settings,add_or_invite}? + if (parts.length >= 2) { + const teamName = parts[1]! + if (teamName.length && validTeamname(teamName)) { + const actionPart = parts[2] + const action = isTeamPageAction(actionPart) ? actionPart : undefined + handleTeamPageLink(teamName, action) + return + } + } + break + case 'incoming-share': + // android needs to render first when coming back + setTimeout(() => { + navigateAppend('incomingShareNew') + }, 500) + return + case 'team-invite-link': + useTeamsState.getState().dispatch.openInviteLink(parts[1] ?? '', parts[2] || '') + return + case 'settingsPushPrompt': + navigateAppend('settingsPushPrompt') + return + case Tabs.teamsTab: + switchTab(Tabs.teamsTab) + return + case Tabs.fsTab: + switchTab(Tabs.fsTab) + return + case Tabs.chatTab: + switchTab(Tabs.chatTab) + return + case Tabs.peopleTab: + switchTab(Tabs.peopleTab) + return + case Tabs.settingsTab: + switchTab(Tabs.settingsTab) + return + default: + // Fall through to the error return below. + } + navigateAppend({props: {error}, selected: 'keybaseLinkError'}) +} + diff --git a/shared/constants/deeplinks/index.tsx b/shared/constants/deeplinks/index.tsx deleted file mode 100644 index 9dfdf5e6e749..000000000000 --- a/shared/constants/deeplinks/index.tsx +++ /dev/null @@ -1,343 +0,0 @@ -import * as Crypto from '../crypto/util' -import * as Tabs from '../tabs' -import {isPathSaltpackEncrypted, isPathSaltpackSigned} from '@/util/path' -import * as Z from '@/util/zustand' -import * as EngineGen from '@/actions/engine-gen-gen' -import type HiddenString from '@/util/hidden-string' -import URL from 'url-parse' -import logger from '@/logger' -import * as T from '@/constants/types' -import {navigateAppend, switchTab} from '../router2/util' -import {storeRegistry} from '../store-registry' - -const prefix = 'keybase://' -type Store = T.Immutable<{ - keybaseLinkError: string -}> -export const linkFromConvAndMessage = (conv: string, messageID: number) => - `${prefix}chat/${conv}/${messageID}` - -const isTeamPageAction = (a?: string): a is TeamPageAction => { - switch (a) { - case 'add_or_invite': - case 'manage_settings': - case 'join': - return true - default: - return false - } -} - -type TeamPageAction = 'add_or_invite' | 'manage_settings' | 'join' - -// This logic is copied from go/protocol/keybase1/extras.go. -const validTeamnamePart = (s: string): boolean => { - if (s.length < 2 || s.length > 16) { - return false - } - - return /^([a-zA-Z0-9][a-zA-Z0-9_]?)+$/.test(s) -} - -const validTeamname = (s: string) => s.split('.').every(validTeamnamePart) - -const initialStore: Store = { - keybaseLinkError: '', -} - -export interface State extends Store { - dispatch: { - handleAppLink: (link: string) => void - handleKeybaseLink: (link: string) => void - handleSaltPackOpen: (_path: string | HiddenString) => void - onEngineIncomingImpl: (action: EngineGen.Actions) => void - resetState: 'default' - setLinkError: (e: string) => void - } -} - -export const useDeepLinksState = Z.createZustand((set, get) => { - const handleShowUserProfileLink = (username: string) => { - switchTab(Tabs.peopleTab) - storeRegistry.getState('profile').dispatch.showUserProfile(username) - } - - const isKeybaseIoUrl = (url: URL) => { - const {protocol} = url - if (protocol !== 'http:' && protocol !== 'https:') return false - if (url.username || url.password) return false - const {hostname} = url - if (hostname !== 'keybase.io' && hostname !== 'www.keybase.io') return false - const {port} = url - if (port) { - if (protocol === 'http:' && port !== '80') return false - if (protocol === 'https:' && port !== '443') return false - } - return true - } - - const urlToUsername = (url: URL) => { - if (!isKeybaseIoUrl(url)) { - return null - } - // Adapted username regexp (see libkb/checkers.go) with a leading /, an - // optional trailing / and a dash for custom links. - const match = url.pathname.match(/^\/((?:[a-zA-Z0-9][a-zA-Z0-9_-]?)+)\/?$/) - if (!match) { - return null - } - const usernameMatch = match[1] - if (!usernameMatch || usernameMatch.length < 2 || usernameMatch.length > 16) { - return null - } - // Ignore query string and hash parameters. - return usernameMatch.toLowerCase() - } - - const urlToTeamDeepLink = (url: URL) => { - if (!isKeybaseIoUrl(url)) { - return null - } - // Similar regexp to username but allow `.` for subteams - const match = url.pathname.match(/^\/team\/((?:[a-zA-Z0-9][a-zA-Z0-9_.-]?)+)\/?$/) - if (!match) { - return null - } - const teamName = match[1] - if (!teamName || teamName.length < 2 || teamName.length > 255) { - return null - } - // `url.query` has a wrong type in @types/url-parse. It's a `string` in the - // code, but @types claim it's a {[k: string]: string | undefined}. - const queryString = url.query as unknown as string - // URLSearchParams is not available in react-native. See if any of recognized - // query parameters is passed using regular expressions. - const action = (['add_or_invite', 'manage_settings'] satisfies readonly TeamPageAction[]).find( - x => queryString.search(`[?&]applink=${x}([?&].+)?$`) !== -1 - ) - return {action, teamName} - } - - const handleTeamPageLink = (teamname: string, action?: TeamPageAction) => { - storeRegistry - .getState('teams') - .dispatch.showTeamByName( - teamname, - action === 'manage_settings' ? 'settings' : undefined, - action === 'join' ? true : undefined, - action === 'add_or_invite' ? true : undefined - ) - } - - const dispatch: State['dispatch'] = { - handleAppLink: link => { - if (link.startsWith('keybase://')) { - get().dispatch.handleKeybaseLink(link.replace('keybase://', '')) - return - } else { - // Normal deeplink - const url = new URL(link) - const username = urlToUsername(url) - if (username === 'phone-app') { - const phones = storeRegistry.getState('settings-phone').phones - if (!phones || phones.size > 0) { - return - } - switchTab(Tabs.settingsTab) - navigateAppend('settingsAddPhone') - } else if (username && username !== 'app') { - handleShowUserProfileLink(username) - return - } - const teamLink = urlToTeamDeepLink(url) - if (teamLink) { - handleTeamPageLink(teamLink.teamName, teamLink.action) - return - } - } - }, - handleKeybaseLink: link => { - if (!link) return - const error = - "We couldn't read this link. The link might be bad, or your Keybase app might be out of date and needs to be updated." - const parts = link.split('/') - // List guaranteed to contain at least one elem. - switch (parts[0]) { - case 'profile': - if (parts[1] === 'new-proof' && (parts.length === 3 || parts.length === 4)) { - parts.length === 4 && - parts[3] && - storeRegistry.getState('profile').dispatch.showUserProfile(parts[3]) - storeRegistry.getState('profile').dispatch.addProof(parts[2]!, 'appLink') - return - } else if (parts[1] === 'show' && parts.length === 3) { - // Username is basically a team name part, we can use the same logic to - // validate deep link. - const username = parts[2]! - if (username.length && validTeamnamePart(username)) { - return handleShowUserProfileLink(username) - } - } - break - // Fall-through - case 'private': - case 'public': - case 'team': - try { - const decoded = decodeURIComponent(link) - switchTab(Tabs.fsTab) - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {path: `/keybase/${decoded}`}, selected: 'fsRoot'}) - return - } catch { - logger.warn("Coudn't decode KBFS URI") - return - } - case 'convid': - if (parts.length === 2) { - const conversationIDKey = parts[1] - if (conversationIDKey) { - storeRegistry.getConvoState(conversationIDKey).dispatch.navigateToThread('navChanged') - } - return - } - break - case 'chat': - if (parts.length === 2 || parts.length === 3) { - if (parts[1]!.includes('#')) { - const teamChat = parts[1]!.split('#') - if (teamChat.length !== 2) { - get().dispatch.setLinkError(error) - navigateAppend('keybaseLinkError') - return - } - const [teamname, channelname] = teamChat - const _highlightMessageID = parseInt(parts[2]!, 10) - if (_highlightMessageID < 0) { - logger.warn(`invalid chat message id: ${_highlightMessageID}`) - return - } - - const highlightMessageID = T.Chat.numberToMessageID(_highlightMessageID) - const {previewConversation} = storeRegistry.getState('chat').dispatch - previewConversation({ - channelname, - highlightMessageID, - reason: 'appLink', - teamname, - }) - return - } else { - const highlightMessageID = parseInt(parts[2]!, 10) - if (highlightMessageID < 0) { - logger.warn(`invalid chat message id: ${highlightMessageID}`) - return - } - const {previewConversation} = storeRegistry.getState('chat').dispatch - previewConversation({ - highlightMessageID: T.Chat.numberToMessageID(highlightMessageID), - participants: parts[1]!.split(','), - reason: 'appLink', - }) - return - } - } - break - case 'team-page': // keybase://team-page/{team_name}/{manage_settings,add_or_invite}? - if (parts.length >= 2) { - const teamName = parts[1]! - if (teamName.length && validTeamname(teamName)) { - const actionPart = parts[2] - const action = isTeamPageAction(actionPart) ? actionPart : undefined - handleTeamPageLink(teamName, action) - return - } - } - break - case 'incoming-share': - // android needs to render first when coming back - setTimeout(() => { - const selectedConversationIDKey = parts[1] - ? T.Chat.stringToConversationIDKey(parts[1]) - : undefined - navigateAppend({ - props: selectedConversationIDKey ? {selectedConversationIDKey} : {}, - selected: 'incomingShareNew', - }) - }, 500) - return - case 'team-invite-link': - storeRegistry.getState('teams').dispatch.openInviteLink(parts[1] ?? '', parts[2] || '') - return - case 'settingsPushPrompt': - navigateAppend('settingsPushPrompt') - return - case Tabs.teamsTab: - switchTab(Tabs.teamsTab) - return - case Tabs.fsTab: - switchTab(Tabs.fsTab) - return - case Tabs.chatTab: - switchTab(Tabs.chatTab) - return - case Tabs.peopleTab: - switchTab(Tabs.peopleTab) - return - case Tabs.settingsTab: - switchTab(Tabs.settingsTab) - return - default: - // Fall through to the error return below. - } - get().dispatch.setLinkError(error) - navigateAppend('keybaseLinkError') - }, - handleSaltPackOpen: _path => { - const path = typeof _path === 'string' ? _path : _path.stringValue() - - if (!storeRegistry.getState('config').loggedIn) { - console.warn('Tried to open a saltpack file before being logged in') - return - } - let operation: T.Crypto.Operations | undefined - if (isPathSaltpackEncrypted(path)) { - operation = Crypto.Operations.Decrypt - } else if (isPathSaltpackSigned(path)) { - operation = Crypto.Operations.Verify - } else { - logger.warn( - 'Deeplink received saltpack file path not ending in ".encrypted.saltpack" or ".signed.saltpack"' - ) - return - } - storeRegistry.getState('crypto').dispatch.onSaltpackOpenFile(operation, path) - switchTab(Tabs.cryptoTab) - }, - - onEngineIncomingImpl: action => { - switch (action.type) { - case EngineGen.keybase1NotifyServiceHandleKeybaseLink: { - const {link, deferred} = action.payload.params - if (deferred && !link.startsWith('keybase://team-invite-link/')) { - return - } - get().dispatch.handleKeybaseLink(link) - break - } - default: - } - }, - resetState: 'default', - setLinkError: e => { - set(s => { - s.keybaseLinkError = e - }) - }, - } - return { - ...initialStore, - dispatch, - } -}) diff --git a/shared/constants/deeplinks/util.tsx b/shared/constants/deeplinks/util.tsx deleted file mode 100644 index 7d1d3078ac54..000000000000 --- a/shared/constants/deeplinks/util.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyServiceHandleKeybaseLink: - { - storeRegistry.getState('deeplinks').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/devices/util.tsx b/shared/constants/devices/util.tsx deleted file mode 100644 index 23b3694282b6..000000000000 --- a/shared/constants/devices/util.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -let loaded = false - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyBadgesBadgeState: - { - const {badgeState} = action.payload.params - const {newDevices, revokedDevices} = badgeState - const hasValue = (newDevices?.length ?? 0) + (revokedDevices?.length ?? 0) > 0 - if (loaded || hasValue) { - loaded = true - storeRegistry.getState('devices').dispatch.onEngineIncomingImpl(action) - } - } - break - default: - } -} diff --git a/shared/constants/engine/index.tsx b/shared/constants/engine/index.tsx deleted file mode 100644 index e2d651083c6b..000000000000 --- a/shared/constants/engine/index.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import * as Z from '@/util/zustand' -import {storeRegistry} from '../store-registry' -import type * as EngineGen from '@/actions/engine-gen-gen' -import * as ArchiveUtil from '../archive/util' -import * as AutoResetUtil from '../autoreset/util' -import * as BotsUtil from '../bots/util' -import * as ChatUtil from '../chat2/util' -import * as DeepLinksUtil from '../deeplinks/util' -import * as DevicesUtil from '../devices/util' -import * as FSUtil from '../fs/util' -import * as GitUtil from '../git/util' -import * as NotifUtil from '../notifications/util' -import * as PeopleUtil from '../people/util' -import * as PinentryUtil from '../pinentry/util' -import * as SettingsUtil from '../settings/util' -import * as SignupUtil from '../signup/util' -import * as TeamsUtil from '../teams/util' -import * as TrackerUtil from '../tracker2/util' -import * as UnlockFoldersUtil from '../unlock-folders/util' -import * as UsersUtil from '../users/util' - -type Store = object -const initialStore: Store = {} - -export interface State extends Store { - dispatch: { - onEngineConnected: () => void - onEngineDisconnected: () => void - onEngineIncoming: (action: EngineGen.Actions) => void - resetState: () => void - } -} - -export const useEngineState = Z.createZustand(set => { - let incomingTimeout: NodeJS.Timeout - const dispatch: State['dispatch'] = { - onEngineConnected: () => { - ChatUtil.onEngineConnected() - storeRegistry.getState('config').dispatch.onEngineConnected() - NotifUtil.onEngineConnected() - PeopleUtil.onEngineConnected() - PinentryUtil.onEngineConnected() - TrackerUtil.onEngineConnected() - UnlockFoldersUtil.onEngineConnected() - }, - onEngineDisconnected: () => { - storeRegistry.getState('config').dispatch.onEngineDisonnected() - }, - onEngineIncoming: action => { - // defer a frame so its more like before - incomingTimeout = setTimeout(() => { - // we delegate to these utils so we don't need to load stores that we don't need yet - ArchiveUtil.onEngineIncoming(action) - AutoResetUtil.onEngineIncoming(action) - BotsUtil.onEngineIncoming(action) - ChatUtil.onEngineIncoming(action) - storeRegistry.getState('config').dispatch.dynamic.onEngineIncomingDesktop?.(action) - storeRegistry.getState('config').dispatch.dynamic.onEngineIncomingNative?.(action) - storeRegistry.getState('config').dispatch.onEngineIncoming(action) - DeepLinksUtil.onEngineIncoming(action) - DevicesUtil.onEngineIncoming(action) - FSUtil.onEngineIncoming(action) - GitUtil.onEngineIncoming(action) - NotifUtil.onEngineIncoming(action) - PeopleUtil.onEngineIncoming(action) - PinentryUtil.onEngineIncoming(action) - SettingsUtil.onEngineIncoming(action) - SignupUtil.onEngineIncoming(action) - TeamsUtil.onEngineIncoming(action) - TrackerUtil.onEngineIncoming(action) - UnlockFoldersUtil.onEngineIncoming(action) - UsersUtil.onEngineIncoming(action) - }, 0) - }, - resetState: () => { - set(s => ({...s, ...initialStore, dispatch: s.dispatch})) - clearTimeout(incomingTimeout) - }, - } - return { - ...initialStore, - dispatch, - } -}) diff --git a/shared/constants/fs.tsx b/shared/constants/fs.tsx new file mode 100644 index 000000000000..13b1a8580584 --- /dev/null +++ b/shared/constants/fs.tsx @@ -0,0 +1,772 @@ +import * as T from '@/constants/types' +import {isLinux, isMobile} from '@/constants/platform' +import {navigateAppend} from '@/constants/router2' + +// Prefetch Constants +const prefetchNotStarted: T.FS.PrefetchNotStarted = { + state: T.FS.PrefetchState.NotStarted, +} + +const prefetchComplete: T.FS.PrefetchComplete = { + state: T.FS.PrefetchState.Complete, +} + +export {prefetchNotStarted, prefetchComplete} + +export const navToPath = ( + // TODO: remove the second arg when we are done with migrating to nav2 + path: T.FS.Path +) => { + navigateAppend({props: {path}, selected: 'fsRoot'}) +} + +// Path Constants +export const defaultPath = T.FS.stringToPath('/keybase') + +// PathItem Constants +const pathItemMetadataDefault = { + lastModifiedTimestamp: 0, + lastWriter: '', + name: 'unknown', + prefetchStatus: prefetchNotStarted, + size: 0, + writable: false, +} + +export const emptyFolder: T.FS.FolderPathItem = { + ...pathItemMetadataDefault, + children: new Set(), + progress: T.FS.ProgressType.Pending, + type: T.FS.PathType.Folder, +} + +export const emptyFile: T.FS.FilePathItem = { + ...pathItemMetadataDefault, + type: T.FS.PathType.File, +} + +export const emptySymlink: T.FS.SymlinkPathItem = { + ...pathItemMetadataDefault, + linkTarget: '', + type: T.FS.PathType.Symlink, +} + +export const unknownPathItem: T.FS.UnknownPathItem = { + ...pathItemMetadataDefault, + type: T.FS.PathType.Unknown, +} + +// Factory Functions +export const unknownTlf = (() => { + const tlfSyncDisabled: T.FS.TlfSyncDisabled = { + mode: T.FS.TlfSyncMode.Disabled, + } + const makeConflictStateNormalView = ({ + localViewTlfPaths, + resolvingConflict, + stuckInConflict, + }: Partial): T.FS.ConflictStateNormalView => ({ + localViewTlfPaths: [...(localViewTlfPaths || [])], + resolvingConflict: resolvingConflict || false, + stuckInConflict: stuckInConflict || false, + type: T.FS.ConflictStateType.NormalView, + }) + const tlfNormalViewWithNoConflict = makeConflictStateNormalView({}) + const makeTlf = (p: Partial): T.FS.Tlf => { + const { + conflictState, + isFavorite, + isIgnored, + isNew, + name, + resetParticipants, + syncConfig, + teamId, + tlfMtime, + } = p + return { + conflictState: conflictState || tlfNormalViewWithNoConflict, + isFavorite: isFavorite || false, + isIgnored: isIgnored || false, + isNew: isNew || false, + name: name || '', + resetParticipants: [...(resetParticipants || [])], + syncConfig: syncConfig || tlfSyncDisabled, + teamId: teamId || '', + tlfMtime: tlfMtime || 0, + /* See comment in constants/types/fs.js + needsRekey: false, + waitingForParticipantUnlock: I.List(), + youCanUnlock: I.List(), + */ + } + } + return makeTlf({}) +})() + +// Empty/Default Objects +export const emptyNewFolder: T.FS.Edit = { + error: undefined, + name: 'New Folder', + originalName: 'New Folder', + parentPath: T.FS.stringToPath('/keybase'), + type: T.FS.EditType.NewFolder, +} + +export const emptySyncingFoldersProgress: T.FS.SyncingFoldersProgress = { + bytesFetched: 0, + bytesTotal: 0, + endEstimate: 0, + start: 0, +} + +export const emptyOverallSyncStatus: T.FS.OverallSyncStatus = { + diskSpaceStatus: T.FS.DiskSpaceStatus.Ok, + showingBanner: false, + syncingFoldersProgress: emptySyncingFoldersProgress, +} + +export const defaultPathUserSetting: T.FS.PathUserSetting = { + sort: T.FS.SortSetting.NameAsc, +} + +export const defaultTlfListPathUserSetting: T.FS.PathUserSetting = { + sort: T.FS.SortSetting.TimeAsc, +} + +export const emptyDownloadState: T.FS.DownloadState = { + canceled: false, + done: false, + endEstimate: 0, + error: '', + localPath: '', + progress: 0, +} + +export const emptyDownloadInfo: T.FS.DownloadInfo = { + filename: '', + isRegularDownload: false, + path: defaultPath, + startTime: 0, +} + +export const emptyPathItemActionMenu: T.FS.PathItemActionMenu = { + downloadID: undefined, + downloadIntent: undefined, + previousView: T.FS.PathItemActionMenuView.Root, + view: T.FS.PathItemActionMenuView.Root, +} + +export const emptySettings: T.FS.Settings = { + isLoading: false, + loaded: false, + sfmiBannerDismissed: false, + spaceAvailableNotificationThreshold: 0, + syncOnCellular: false, +} + +export const emptyPathInfo: T.FS.PathInfo = { + deeplinkPath: '', + platformAfterMountPath: '', +} + +export const emptyFileContext: T.FS.FileContext = { + contentType: '', + url: '', + viewType: T.RPCGen.GUIViewType.default, +} + +// Driver Status Constants +export const driverStatusUnknown: T.FS.DriverStatusUnknown = { + type: T.FS.DriverStatusType.Unknown, +} as const + +export const emptyDriverStatusEnabled: T.FS.DriverStatusEnabled = { + dokanOutdated: false, + dokanUninstallExecPath: undefined, + isDisabling: false, + type: T.FS.DriverStatusType.Enabled, +} as const + +export const emptyDriverStatusDisabled: T.FS.DriverStatusDisabled = { + isEnabling: false, + kextPermissionError: false, + type: T.FS.DriverStatusType.Disabled, +} as const + +export const defaultDriverStatus: T.FS.DriverStatus = isLinux ? emptyDriverStatusEnabled : driverStatusUnknown + +export const unknownKbfsDaemonStatus: T.FS.KbfsDaemonStatus = { + onlineStatus: T.FS.KbfsDaemonOnlineStatus.Unknown, + rpcStatus: T.FS.KbfsDaemonRpcStatus.Waiting, +} + +// Parsed Path Constants +const parsedPathRoot: T.FS.ParsedPathRoot = {kind: T.FS.PathKind.Root} + +const parsedPathPrivateList: T.FS.ParsedPathTlfList = { + kind: T.FS.PathKind.TlfList, + tlfType: T.FS.TlfType.Private, +} + +const parsedPathPublicList: T.FS.ParsedPathTlfList = { + kind: T.FS.PathKind.TlfList, + tlfType: T.FS.TlfType.Public, +} + +const parsedPathTeamList: T.FS.ParsedPathTlfList = { + kind: T.FS.PathKind.TlfList, + tlfType: T.FS.TlfType.Team, +} + +// Conversion Functions +export const pathToRPCPath = ( + path: T.FS.Path +): {PathType: T.RPCGen.PathType.kbfs; kbfs: T.RPCGen.KBFSPath} => ({ + PathType: T.RPCGen.PathType.kbfs, + kbfs: { + identifyBehavior: T.RPCGen.TLFIdentifyBehavior.fsGui, + path: T.FS.pathToString(path).substring('/keybase'.length) || '/', + }, +}) + +// Path/PathItem Utilities +export const pathTypeToTextType = (type: T.FS.PathType) => + type === T.FS.PathType.Folder ? 'BodySemibold' : 'Body' + +export const getPathItem = ( + pathItems: T.Immutable>, + path: T.Immutable +): T.Immutable => pathItems.get(path) || (unknownPathItem as T.FS.PathItem) + +export const getTlfPath = (path: T.FS.Path): T.FS.Path => { + const elems = T.FS.getPathElements(path) + return elems.length > 2 ? T.FS.pathConcat(T.FS.pathConcat(defaultPath, elems[1]!), elems[2]!) : undefined +} + +export const getTlfListFromType = ( + tlfs: T.Immutable, + tlfType: T.Immutable +): T.Immutable => { + switch (tlfType) { + case T.FS.TlfType.Private: + return tlfs.private + case T.FS.TlfType.Public: + return tlfs.public + case T.FS.TlfType.Team: + return tlfs.team + default: + return new Map() + } +} + +export const getTlfListAndTypeFromPath = ( + tlfs: T.Immutable, + path: T.Immutable +): T.Immutable<{ + tlfList: T.FS.TlfList + tlfType: T.FS.TlfType +}> => { + const visibility = T.FS.getPathVisibility(path) + switch (visibility) { + case T.FS.TlfType.Private: + case T.FS.TlfType.Public: + case T.FS.TlfType.Team: { + const tlfType: T.FS.TlfType = visibility + return {tlfList: getTlfListFromType(tlfs, tlfType), tlfType} + } + default: + return {tlfList: new Map(), tlfType: T.FS.TlfType.Private} + } +} + +export const getTlfFromPathInFavoritesOnly = (tlfs: T.Immutable, path: T.FS.Path): T.FS.Tlf => { + const elems = T.FS.getPathElements(path) + if (elems.length < 3) { + return unknownTlf + } + const {tlfList} = getTlfListAndTypeFromPath(tlfs, path) + return tlfList.get(elems[2]!) || unknownTlf +} + +export const getTlfFromPath = (tlfs: T.Immutable, path: T.FS.Path): T.FS.Tlf => { + const fromFavorites = getTlfFromPathInFavoritesOnly(tlfs, path) + return fromFavorites !== unknownTlf + ? fromFavorites + : tlfs.additionalTlfs.get(getTlfPath(path)) || unknownTlf +} + +export const getTlfFromTlfs = (tlfs: T.FS.Tlfs, tlfType: T.FS.TlfType, name: string): T.FS.Tlf => { + switch (tlfType) { + case T.FS.TlfType.Private: + return tlfs.private.get(name) || unknownTlf + case T.FS.TlfType.Public: + return tlfs.public.get(name) || unknownTlf + case T.FS.TlfType.Team: + return tlfs.team.get(name) || unknownTlf + default: + return unknownTlf + } +} + +export const tlfTypeAndNameToPath = (tlfType: T.FS.TlfType, name: string): T.FS.Path => + T.FS.stringToPath(`/keybase/${tlfType}/${name}`) + +export const getUploadedPath = (parentPath: T.FS.Path, localPath: string) => + T.FS.pathConcat(parentPath, T.FS.getLocalPathName(localPath)) + +export const pathsInSameTlf = (a: T.FS.Path, b: T.FS.Path): boolean => { + const elemsA = T.FS.getPathElements(a) + const elemsB = T.FS.getPathElements(b) + return elemsA.length >= 3 && elemsB.length >= 3 && elemsA[1] === elemsB[1] && elemsA[2] === elemsB[2] +} + +const slashKeybaseSlashLength = '/keybase/'.length +export const escapePath = (path: T.FS.Path): string => + 'keybase://' + + encodeURIComponent(T.FS.pathToString(path).slice(slashKeybaseSlashLength)).replace( + // We need to do this because otherwise encodeURIComponent would encode + // "/"s. + /%2F/g, + '/' + ) + +export const rebasePathToDifferentTlf = (path: T.FS.Path, newTlfPath: T.FS.Path) => + T.FS.pathConcat(newTlfPath, T.FS.getPathElements(path).slice(3).join('/')) + +export const isFolder = (path: T.FS.Path, pathItem: T.FS.PathItem) => + T.FS.getPathLevel(path) <= 3 || pathItem.type === T.FS.PathType.Folder + +export const isInTlf = (path: T.FS.Path) => T.FS.getPathLevel(path) > 2 + +export const hasPublicTag = (path: T.FS.Path): boolean => { + const publicPrefix = '/keybase/public/' + // The slash after public in `publicPrefix` prevents /keybase/public from counting. + return T.FS.pathToString(path).startsWith(publicPrefix) +} + +export const hasSpecialFileElement = (path: T.FS.Path): boolean => + T.FS.getPathElements(path).some(elem => elem.startsWith('.kbfs')) + +// Username/User Utilities +export const splitTlfIntoUsernames = (tlf: string): ReadonlyArray => + tlf.split(' ')[0]?.replace(/#/g, ',').split(',') ?? [] + +export const getUsernamesFromPath = (path: T.FS.Path): ReadonlyArray => { + const elems = T.FS.getPathElements(path) + return elems.length < 3 ? [] : splitTlfIntoUsernames(elems[2]!) +} + +export const usernameInPath = (username: string, path: T.FS.Path) => { + const elems = T.FS.getPathElements(path) + return elems.length >= 3 && elems[2]!.split(',').includes(username) +} + +const splitTlfIntoReadersAndWriters = ( + tlf: string +): { + readers?: Array + writers: Array +} => { + const [w, r] = tlf.split('#') + return { + readers: r ? r.split(',').filter(i => !!i) : undefined, + writers: w?.split(',').filter(i => !!i) ?? [], + } +} + +export const getUsernamesFromTlfName = (tlfName: string): Array => { + const split = splitTlfIntoReadersAndWriters(tlfName) + return split.writers.concat(split.readers || []) +} + +// TLF/List Utilities +export const computeBadgeNumberForTlfList = (tlfList: T.Immutable): number => + [...tlfList.values()].reduce((accumulator, tlf) => (tlfIsBadged(tlf) ? accumulator + 1 : accumulator), 0) + +export const computeBadgeNumberForAll = (tlfs: T.Immutable): number => + [T.FS.TlfType.Private, T.FS.TlfType.Public, T.FS.TlfType.Team] + .map(tlfType => computeBadgeNumberForTlfList(getTlfListFromType(tlfs, tlfType))) + .reduce((sum, count) => sum + count, 0) + +export const tlfIsBadged = (tlf: T.FS.Tlf) => !tlf.isIgnored && tlf.isNew + +export const tlfIsStuckInConflict = (tlf: T.FS.Tlf) => + tlf.conflictState.type === T.FS.ConflictStateType.NormalView && tlf.conflictState.stuckInConflict + +// Path Parsing +export const parsePath = (path: T.FS.Path): T.FS.ParsedPath => { + const elems = T.FS.getPathElements(path) + if (elems.length <= 1) { + return parsedPathRoot + } + switch (elems[1]) { + case 'private': + switch (elems.length) { + case 2: + return parsedPathPrivateList + case 3: + return { + kind: T.FS.PathKind.GroupTlf, + tlfName: elems[2]!, + tlfType: T.FS.TlfType.Private, + ...splitTlfIntoReadersAndWriters(elems[2]!), + } + default: + return { + kind: T.FS.PathKind.InGroupTlf, + rest: elems.slice(3), + tlfName: elems[2] ?? '', + tlfType: T.FS.TlfType.Private, + ...splitTlfIntoReadersAndWriters(elems[2] ?? ''), + } + } + case 'public': + switch (elems.length) { + case 2: + return parsedPathPublicList + case 3: + return { + kind: T.FS.PathKind.GroupTlf, + tlfName: elems[2]!, + tlfType: T.FS.TlfType.Public, + ...splitTlfIntoReadersAndWriters(elems[2]!), + } + default: + return { + kind: T.FS.PathKind.InGroupTlf, + rest: elems.slice(3), + tlfName: elems[2] ?? '', + tlfType: T.FS.TlfType.Public, + ...splitTlfIntoReadersAndWriters(elems[2] ?? ''), + } + } + case 'team': + switch (elems.length) { + case 2: + return parsedPathTeamList + case 3: + return { + kind: T.FS.PathKind.TeamTlf, + team: elems[2]!, + tlfName: elems[2]!, + tlfType: T.FS.TlfType.Team, + } + default: + return { + kind: T.FS.PathKind.InTeamTlf, + rest: elems.slice(3), + team: elems[2] ?? '', + tlfName: elems[2] ?? '', + tlfType: T.FS.TlfType.Team, + } + } + default: + return parsedPathRoot + } +} + +// Chat/Share Utilities +export const canChat = (path: T.FS.Path) => { + const parsedPath = parsePath(path) + switch (parsedPath.kind) { + case T.FS.PathKind.Root: + case T.FS.PathKind.TlfList: + return false + case T.FS.PathKind.GroupTlf: + case T.FS.PathKind.TeamTlf: + return true + case T.FS.PathKind.InGroupTlf: + case T.FS.PathKind.InTeamTlf: + return true + default: + return false + } +} + +export const isTeamPath = (path: T.FS.Path): boolean => { + const parsedPath = parsePath(path) + return parsedPath.kind !== T.FS.PathKind.Root && parsedPath.tlfType === T.FS.TlfType.Team +} + +export const getChatTarget = (path: T.FS.Path, me: string): string => { + const parsedPath = parsePath(path) + if (parsedPath.kind !== T.FS.PathKind.Root && parsedPath.tlfType === T.FS.TlfType.Team) { + return 'team conversation' + } + if (parsedPath.kind === T.FS.PathKind.GroupTlf || parsedPath.kind === T.FS.PathKind.InGroupTlf) { + if (parsedPath.writers.length === 1 && !parsedPath.readers && parsedPath.writers[0] === me) { + return 'yourself' + } + if (parsedPath.writers.length + (parsedPath.readers ? parsedPath.readers.length : 0) === 2) { + const notMe = parsedPath.writers.concat(parsedPath.readers || []).filter(u => u !== me) + if (notMe.length === 1) { + return notMe[0]! + } + } + return 'group conversation' + } + return 'conversation' +} + +export const getSharePathArrayDescription = (paths: ReadonlyArray): string => { + return !paths.length ? '' : paths.length === 1 ? T.FS.getPathName(paths[0]) : `${paths.length} items` +} + +export const getDestinationPickerPathName = (picker: T.FS.DestinationPicker): string => + picker.source.type === T.FS.DestinationPickerSource.MoveOrCopy + ? T.FS.getPathName(picker.source.path) + : picker.source.type === T.FS.DestinationPickerSource.IncomingShare + ? getSharePathArrayDescription( + picker.source.source + .map(({originalPath}) => (originalPath ? T.FS.getLocalPathName(originalPath) : '')) + .filter(Boolean) + ) + : '' + +// File/Download Utilities +export const humanReadableFileSize = (size: number) => { + const kib = 1024 + const mib = kib * kib + const gib = mib * kib + const tib = gib * kib + + if (!size) return '' + if (size >= tib) return `${Math.round(size / tib)} TB` + if (size >= gib) return `${Math.round(size / gib)} GB` + if (size >= mib) return `${Math.round(size / mib)} MB` + if (size >= kib) return `${Math.round(size / kib)} KB` + return `${size} B` +} + +export const humanizeBytes = (n: number, numDecimals: number): string => { + const kb = 1024 + const mb = kb * 1024 + const gb = mb * 1024 + + if (n < kb) { + return `${n} bytes` + } else if (n < mb) { + return `${(n / kb).toFixed(numDecimals)} KB` + } else if (n < gb) { + return `${(n / mb).toFixed(numDecimals)} MB` + } + return `${(n / gb).toFixed(numDecimals)} GB` +} + +export const humanizeBytesOfTotal = (n: number, d: number): string => { + const kb = 1024 + const mb = kb * 1024 + const gb = mb * 1024 + + if (d < kb) { + return `${n} of ${d} bytes` + } else if (d < mb) { + return `${(n / kb).toFixed(2)} of ${(d / kb).toFixed(2)} KB` + } else if (d < gb) { + return `${(n / mb).toFixed(2)} of ${(d / mb).toFixed(2)} MB` + } + return `${(n / gb).toFixed(2)} of ${(d / gb).toFixed(2)} GB` +} + +export const downloadIsOngoing = (dlState: T.FS.DownloadState) => + dlState !== emptyDownloadState && !dlState.error && !dlState.done && !dlState.canceled + +export const getDownloadIntent = ( + path: T.FS.Path, + downloads: T.FS.Downloads, + pathItemActionMenu: T.FS.PathItemActionMenu +): T.FS.DownloadIntent | undefined => { + const found = [...downloads.info].find(([_, info]) => info.path === path) + if (!found) { + return undefined + } + const [downloadID] = found + const dlState = downloads.state.get(downloadID) || emptyDownloadState + if (!downloadIsOngoing(dlState)) { + return undefined + } + if (pathItemActionMenu.downloadID === downloadID) { + return pathItemActionMenu.downloadIntent + } + return T.FS.DownloadIntent.None +} + +export const canSaveMedia = (pathItem: T.FS.PathItem, fileContext: T.FS.FileContext): boolean => { + if (pathItem.type !== T.FS.PathType.File || fileContext === emptyFileContext) { + return false + } + return ( + fileContext.viewType === T.RPCGen.GUIViewType.image || fileContext.viewType === T.RPCGen.GUIViewType.video + ) +} + +export const isOfflineUnsynced = ( + daemonStatus: T.FS.KbfsDaemonStatus, + pathItem: T.FS.PathItem, + path: T.FS.Path +) => + daemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline && + T.FS.getPathLevel(path) > 2 && + pathItem.prefetchStatus !== prefetchComplete + +// Status/Icon Utilities +export const getUploadIconForTlfType = ( + kbfsDaemonStatus: T.FS.KbfsDaemonStatus, + uploads: T.FS.Uploads, + tlfList: T.FS.TlfList, + tlfType: T.FS.TlfType +): T.FS.UploadIcon | undefined => { + if ( + [...tlfList].some( + ([_, tlf]) => + tlf.conflictState.type === T.FS.ConflictStateType.NormalView && tlf.conflictState.stuckInConflict + ) + ) { + return T.FS.UploadIcon.UploadingStuck + } + + const prefix = T.FS.pathToString(T.FS.getTlfTypePathFromTlfType(tlfType)) + if ( + [...uploads.syncingPaths].some(p => T.FS.pathToString(p).startsWith(prefix)) || + [...uploads.writingToJournal.keys()].some(p => T.FS.pathToString(p).startsWith(prefix)) + ) { + return kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline + ? T.FS.UploadIcon.AwaitingToUpload + : T.FS.UploadIcon.Uploading + } + + return undefined +} + +export const isPathEnabledForSync = (syncConfig: T.FS.TlfSyncConfig, path: T.FS.Path): boolean => { + switch (syncConfig.mode) { + case T.FS.TlfSyncMode.Disabled: + return false + case T.FS.TlfSyncMode.Enabled: + return true + case T.FS.TlfSyncMode.Partial: + // TODO: when we enable partial sync lookup, remember to deal with + // potential ".." traversal as well. + return syncConfig.enabledPaths.includes(path) + default: + return false + } +} + +export const getPathStatusIconInMergeProps = ( + kbfsDaemonStatus: T.FS.KbfsDaemonStatus, + tlf: T.Immutable, + pathItem: T.Immutable, + uploadingPaths: T.Immutable>, + path: T.Immutable +): T.FS.PathStatusIcon => { + // There's no upload or sync for local conflict view. + if (tlf.conflictState.type === T.FS.ConflictStateType.ManualResolvingLocalView) { + return T.FS.LocalConflictStatus + } + + // uploading state has higher priority + if (uploadingPaths.has(path)) { + // eslint-disable-next-line + return tlf.conflictState.type === T.FS.ConflictStateType.NormalView && tlf.conflictState.stuckInConflict + ? T.FS.UploadIcon.UploadingStuck + : kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline + ? T.FS.UploadIcon.AwaitingToUpload + : T.FS.UploadIcon.Uploading + } + if (!isPathEnabledForSync(tlf.syncConfig, path)) { + return T.FS.NonUploadStaticSyncStatus.OnlineOnly + } + + if (pathItem === unknownPathItem && tlf.syncConfig.mode !== T.FS.TlfSyncMode.Disabled) { + return T.FS.NonUploadStaticSyncStatus.Unknown + } + + // TODO: what about 'sync-error'? + + // We don't have an upload state, and sync is enabled for this path. + switch (pathItem.prefetchStatus.state) { + case T.FS.PrefetchState.NotStarted: + return T.FS.NonUploadStaticSyncStatus.AwaitingToSync + case T.FS.PrefetchState.Complete: + return T.FS.NonUploadStaticSyncStatus.Synced + case T.FS.PrefetchState.InProgress: { + if (kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline) { + return T.FS.NonUploadStaticSyncStatus.AwaitingToSync + } + const inProgress: T.FS.PrefetchInProgress = pathItem.prefetchStatus + if (inProgress.bytesTotal === 0) { + return T.FS.NonUploadStaticSyncStatus.AwaitingToSync + } + return inProgress.bytesFetched / inProgress.bytesTotal + } + default: + return T.FS.NonUploadStaticSyncStatus.Unknown + } +} + +export const getMainBannerType = ( + kbfsDaemonStatus: T.FS.KbfsDaemonStatus, + overallSyncStatus: T.FS.OverallSyncStatus +): T.FS.MainBannerType => { + if (kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline) { + return T.FS.MainBannerType.Offline + } else if (kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Trying) { + return T.FS.MainBannerType.TryingToConnect + } else if (overallSyncStatus.diskSpaceStatus === T.FS.DiskSpaceStatus.Error) { + return T.FS.MainBannerType.OutOfSpace + } else { + return T.FS.MainBannerType.None + } +} + +// Settings/Configuration Utilities +export const getPathUserSetting = ( + pathUserSettings: T.Immutable>, + path: T.Immutable +): T.FS.PathUserSetting => + pathUserSettings.get(path) || + (T.FS.getPathLevel(path) < 3 ? defaultTlfListPathUserSetting : defaultPathUserSetting) + +export const showSortSetting = ( + path: T.FS.Path, + pathItem: T.FS.PathItem, + kbfsDaemonStatus: T.FS.KbfsDaemonStatus +) => + !isMobile && + path !== defaultPath && + (T.FS.getPathLevel(path) === 2 || (pathItem.type === T.FS.PathType.Folder && !!pathItem.children.size)) && + !isOfflineUnsynced(kbfsDaemonStatus, pathItem, path) + +export const getSoftError = (softErrors: T.FS.SoftErrors, path: T.FS.Path): T.FS.SoftError | undefined => { + const pathError = softErrors.pathErrors.get(path) + if (pathError) { + return pathError + } + if (!softErrors.tlfErrors.size) { + return undefined + } + const tlfPath = getTlfPath(path) + return (tlfPath && softErrors.tlfErrors.get(tlfPath)) || undefined +} + +export const sfmiInfoLoaded = (settings: T.FS.Settings, driverStatus: T.FS.DriverStatus): boolean => + settings.loaded && driverStatus !== driverStatusUnknown + +export const hideOrDisableInDestinationPicker = ( + tlfType: T.FS.TlfType, + name: string, + username: string, + destinationPickerIndex?: number +) => typeof destinationPickerIndex === 'number' && tlfType === T.FS.TlfType.Public && name !== username + +// Other Utilities + +export const showIgnoreFolder = (path: T.FS.Path, username?: string): boolean => { + const elems = T.FS.getPathElements(path) + if (elems.length !== 3) { + return false + } + return ['public', 'private'].includes(elems[1]!) && elems[2]! !== username +} diff --git a/shared/constants/fs/common.native.tsx b/shared/constants/fs/common.native.tsx deleted file mode 100644 index c75025efc827..000000000000 --- a/shared/constants/fs/common.native.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import logger from '@/logger' -import {ignorePromise} from '../utils' -import {wrapErrors} from '@/util/debug' -import * as T from '../types' -import * as Styles from '@/styles' -import * as FS from '@/constants/fs' -import {launchImageLibraryAsync} from '@/util/expo-image-picker.native' -import {saveAttachmentToCameraRoll, showShareActionSheet} from '../platform-specific' -import {useFSState} from '.' - -export default function initNative() { - useFSState.setState(s => { - s.dispatch.dynamic.pickAndUploadMobile = wrapErrors( - (type: T.FS.MobilePickType, parentPath: T.FS.Path) => { - const f = async () => { - try { - const result = await launchImageLibraryAsync(type, true, true) - if (result.canceled) return - result.assets.map(r => - useFSState.getState().dispatch.upload(parentPath, Styles.unnormalizePath(r.uri)) - ) - } catch (e) { - FS.errorToActionOrThrow(e) - } - } - ignorePromise(f()) - } - ) - - s.dispatch.dynamic.finishedDownloadWithIntentMobile = wrapErrors( - (downloadID: string, downloadIntent: T.FS.DownloadIntent, mimeType: string) => { - const f = async () => { - const {downloads, dispatch} = useFSState.getState() - const downloadState = downloads.state.get(downloadID) || FS.emptyDownloadState - if (downloadState === FS.emptyDownloadState) { - logger.warn('missing download', downloadID) - return - } - const dismissDownload = dispatch.dismissDownload - if (downloadState.error) { - dispatch.redbar(downloadState.error) - dismissDownload(downloadID) - return - } - const {localPath} = downloadState - try { - switch (downloadIntent) { - case T.FS.DownloadIntent.CameraRoll: - await saveAttachmentToCameraRoll(localPath, mimeType) - dismissDownload(downloadID) - return - case T.FS.DownloadIntent.Share: - await showShareActionSheet({filePath: localPath, mimeType}) - dismissDownload(downloadID) - return - case T.FS.DownloadIntent.None: - return - default: - return - } - } catch (err) { - FS.errorToActionOrThrow(err) - } - } - ignorePromise(f()) - } - ) - }) -} diff --git a/shared/constants/fs/platform-specific.android.tsx b/shared/constants/fs/platform-specific.android.tsx deleted file mode 100644 index 8e7f3ff2a04b..000000000000 --- a/shared/constants/fs/platform-specific.android.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import * as T from '../types' -import {ignorePromise, wrapErrors} from '../utils' -import * as FS from '@/constants/fs' -import logger from '@/logger' -import nativeInit from './common.native' -import {useFSState} from '.' -import {androidAddCompleteDownload, fsCacheDir, fsDownloadDir} from 'react-native-kb' - -const finishedRegularDownloadIDs = new Set() - -export default function initPlatformSpecific() { - nativeInit() - - useFSState.setState(s => { - s.dispatch.dynamic.afterKbfsDaemonRpcStatusChanged = wrapErrors(() => { - const f = async () => { - await T.RPCGen.SimpleFSSimpleFSConfigureDownloadRpcPromise({ - // Android's cache dir is (when I tried) [app]/cache but Go side uses - // [app]/.cache by default, which can't be used for sharing to other apps. - cacheDirOverride: fsCacheDir, - downloadDirOverride: fsDownloadDir, - }) - } - ignorePromise(f()) - }) - // needs to be called, TODO could make this better - s.dispatch.dynamic.afterKbfsDaemonRpcStatusChanged() - - s.dispatch.dynamic.finishedRegularDownloadMobile = wrapErrors( - (downloadID: string, mimeType: string) => { - const f = async () => { - // This is fired from a hook and can happen more than once per downloadID. - // So just deduplicate them here. This is small enough and won't happen - // constantly, so don't worry about clearing them. - if (finishedRegularDownloadIDs.has(downloadID)) { - return - } - finishedRegularDownloadIDs.add(downloadID) - - const {downloads} = useFSState.getState() - - const downloadState = downloads.state.get(downloadID) || FS.emptyDownloadState - const downloadInfo = downloads.info.get(downloadID) || FS.emptyDownloadInfo - if (downloadState === FS.emptyDownloadState || downloadInfo === FS.emptyDownloadInfo) { - logger.warn('missing download', downloadID) - return - } - if (downloadState.error) { - return - } - try { - await androidAddCompleteDownload({ - description: `Keybase downloaded ${downloadInfo.filename}`, - mime: mimeType, - path: downloadState.localPath, - showNotification: true, - title: downloadInfo.filename, - }) - } catch { - logger.warn('Failed to addCompleteDownload') - } - // No need to dismiss here as the download wrapper does it for Android. - } - ignorePromise(f()) - } - ) - }) -} diff --git a/shared/constants/fs/platform-specific.d.ts b/shared/constants/fs/platform-specific.d.ts deleted file mode 100644 index e5bdb364c59a..000000000000 --- a/shared/constants/fs/platform-specific.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare function initPlatformSpecific(): void -export default initPlatformSpecific diff --git a/shared/constants/fs/platform-specific.desktop.tsx b/shared/constants/fs/platform-specific.desktop.tsx deleted file mode 100644 index b9e372eded7c..000000000000 --- a/shared/constants/fs/platform-specific.desktop.tsx +++ /dev/null @@ -1,310 +0,0 @@ -import * as T from '@/constants/types' -import {ignorePromise, wrapErrors} from '../utils' -import * as Constants from '../fs' -import * as Tabs from '../tabs' -import {isWindows, isLinux, pathSep, isDarwin} from '../platform.desktop' -import logger from '@/logger' -import * as Path from '@/util/path' -import KB2 from '@/util/electron.desktop' -import {uint8ArrayToHex} from 'uint8array-extras' -import {useFSState} from '.' -import {navigateAppend} from '../router2/util' -import {storeRegistry} from '../store-registry' - -const {openPathInFinder, openURL, getPathType, selectFilesToUploadDialog} = KB2.functions -const {darwinCopyToKBFSTempUploadFile, relaunchApp, uninstallKBFSDialog, uninstallDokanDialog} = KB2.functions -const {exitApp, windowsCheckMountFromOtherDokanInstall, installCachedDokan, uninstallDokan} = KB2.functions - -// _openPathInSystemFileManagerPromise opens `openPath` in system file manager. -// If isFolder is true, it just opens it. Otherwise, it shows it in its parent -// folder. This function does not check if the file exists, or try to convert -// KBFS paths. Caller should take care of those. -const _openPathInSystemFileManagerPromise = async (openPath: string, isFolder: boolean): Promise => - openPathInFinder?.(openPath, isFolder) - -const escapeBackslash = isWindows - ? (pathElem: string): string => - pathElem - .replace(/‰/g, '‰2030') - .replace(/([<>:"/\\|?*])/g, (_, c: Uint8Array) => '‰' + uint8ArrayToHex(c)) - : (pathElem: string): string => pathElem - -const _rebaseKbfsPathToMountLocation = (kbfsPath: T.FS.Path, mountLocation: string) => - Path.join(mountLocation, T.FS.getPathElements(kbfsPath).slice(1).map(escapeBackslash).join(pathSep)) - -const fuseStatusToUninstallExecPath = isWindows - ? (status: T.RPCGen.FuseStatus) => { - const field = status.status.fields?.find(({key}) => key === 'uninstallString') - return field?.value - } - : () => undefined - -const fuseStatusToActions = - (previousStatusType: T.FS.DriverStatusType) => (status: T.RPCGen.FuseStatus | undefined) => { - if (!status) { - useFSState.getState().dispatch.setDriverStatus(Constants.defaultDriverStatus) - return - } - - if (status.kextStarted) { - useFSState.getState().dispatch.setDriverStatus({ - ...Constants.emptyDriverStatusEnabled, - dokanOutdated: status.installAction === T.RPCGen.InstallAction.upgrade, - dokanUninstallExecPath: fuseStatusToUninstallExecPath(status), - }) - } else { - useFSState.getState().dispatch.setDriverStatus(Constants.emptyDriverStatusDisabled) - } - - if (status.kextStarted && previousStatusType === T.FS.DriverStatusType.Disabled) { - useFSState - .getState() - .dispatch.dynamic.openPathInSystemFileManagerDesktop?.(T.FS.stringToPath('/keybase')) - } - } - -const fuseInstallResultIsKextPermissionError = (result: T.RPCGen.InstallResult): boolean => - result.componentResults?.findIndex( - c => c.name === 'fuse' && c.exitCode === Constants.ExitCodeFuseKextPermissionError - ) !== -1 - -const driverEnableFuse = async (isRetry: boolean) => { - const result = await T.RPCGen.installInstallFuseRpcPromise() - if (fuseInstallResultIsKextPermissionError(result)) { - useFSState.getState().dispatch.driverKextPermissionError() - if (!isRetry) { - navigateAppend('kextPermission') - } - } else { - await T.RPCGen.installInstallKBFSRpcPromise() // restarts kbfsfuse - await T.RPCGen.kbfsMountWaitForMountsRpcPromise() - useFSState.getState().dispatch.dynamic.refreshDriverStatusDesktop?.() - } -} - -const uninstallKBFSConfirm = async () => { - const remove = await (uninstallKBFSDialog?.() ?? Promise.resolve(false)) - if (remove) { - useFSState.getState().dispatch.driverDisabling() - } -} - -const uninstallKBFS = async () => - T.RPCGen.installUninstallKBFSRpcPromise().then(() => { - // Restart since we had to uninstall KBFS and it's needed by the service (for chat) - relaunchApp?.() - exitApp?.(0) - }) - -const uninstallDokanConfirm = async () => { - const driverStatus = useFSState.getState().sfmi.driverStatus - if (driverStatus.type !== T.FS.DriverStatusType.Enabled) { - return - } - if (!driverStatus.dokanUninstallExecPath) { - await uninstallDokanDialog?.() - useFSState.getState().dispatch.dynamic.refreshDriverStatusDesktop?.() - return - } - useFSState.getState().dispatch.driverDisabling() -} - -const onUninstallDokan = async () => { - const driverStatus = useFSState.getState().sfmi.driverStatus - if (driverStatus.type !== T.FS.DriverStatusType.Enabled) return - const execPath: string = driverStatus.dokanUninstallExecPath || '' - logger.info('Invoking dokan uninstaller', execPath) - try { - await uninstallDokan?.(execPath) - } catch {} - useFSState.getState().dispatch.dynamic.refreshDriverStatusDesktop?.() -} - -// Invoking the cached installer package has to happen from the topmost process -// or it won't be visible to the user. The service also does this to support command line -// operations. -const onInstallCachedDokan = async () => { - try { - await installCachedDokan?.() - useFSState.getState().dispatch.dynamic.refreshDriverStatusDesktop?.() - } catch (e) { - Constants.errorToActionOrThrow(e) - } -} - -const initPlatformSpecific = () => { - storeRegistry.getStore('config').subscribe((s, old) => { - if (s.appFocused === old.appFocused) return - useFSState.getState().dispatch.onChangedFocus(s.appFocused) - }) - - useFSState.setState(s => { - s.dispatch.dynamic.uploadFromDragAndDropDesktop = wrapErrors( - (parentPath: T.FS.Path, localPaths: string[]) => { - const {upload} = useFSState.getState().dispatch - const f = async () => { - if (isDarwin && darwinCopyToKBFSTempUploadFile) { - const dir = await T.RPCGen.SimpleFSSimpleFSMakeTempDirForUploadRpcPromise() - const lp = await Promise.all( - localPaths.map(async localPath => darwinCopyToKBFSTempUploadFile(dir, localPath)) - ) - lp.forEach(localPath => upload(parentPath, localPath)) - } else { - localPaths.forEach(localPath => upload(parentPath, localPath)) - } - } - ignorePromise(f()) - } - ) - - s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop = wrapErrors((localPath: string) => { - const f = async () => { - try { - if (getPathType) { - const pathType = await getPathType(localPath) - await _openPathInSystemFileManagerPromise(localPath, pathType === 'directory') - } - } catch (e) { - Constants.errorToActionOrThrow(e) - } - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.openPathInSystemFileManagerDesktop = wrapErrors((path: T.FS.Path) => { - const f = async () => { - const {sfmi, pathItems} = useFSState.getState() - return sfmi.driverStatus.type === T.FS.DriverStatusType.Enabled && sfmi.directMountDir - ? _openPathInSystemFileManagerPromise( - _rebaseKbfsPathToMountLocation(path, sfmi.directMountDir), - ![T.FS.PathKind.InGroupTlf, T.FS.PathKind.InTeamTlf].includes(Constants.parsePath(path).kind) || - Constants.getPathItem(pathItems, path).type === T.FS.PathType.Folder - ).catch((e: unknown) => Constants.errorToActionOrThrow(e, path)) - : new Promise((resolve, reject) => { - if (sfmi.driverStatus.type !== T.FS.DriverStatusType.Enabled) { - // This usually indicates a developer error as - // openPathInSystemFileManager shouldn't be used when FUSE integration - // is not enabled. So just blackbar to encourage a log send. - reject(new Error('FUSE integration is not enabled')) - } else { - logger.warn('empty directMountDir') // if this happens it might be a race? - resolve() - } - }) - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.refreshDriverStatusDesktop = wrapErrors(() => { - const f = async () => { - let status = await T.RPCGen.installFuseStatusRpcPromise({ - bundleVersion: '', - }) - if (isWindows && status.installStatus !== T.RPCGen.InstallStatus.installed) { - const m = await T.RPCGen.kbfsMountGetCurrentMountDirRpcPromise() - status = await (windowsCheckMountFromOtherDokanInstall?.(m, status) ?? Promise.resolve(status)) - } - fuseStatusToActions(useFSState.getState().sfmi.driverStatus.type)(status) - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.refreshMountDirsDesktop = wrapErrors(() => { - const f = async () => { - const {sfmi, dispatch} = useFSState.getState() - const driverStatus = sfmi.driverStatus - if (driverStatus.type !== T.FS.DriverStatusType.Enabled) { - return - } - const directMountDir = await T.RPCGen.kbfsMountGetCurrentMountDirRpcPromise() - const preferredMountDirs = await T.RPCGen.kbfsMountGetPreferredMountDirsRpcPromise() - dispatch.setDirectMountDir(directMountDir) - dispatch.setPreferredMountDirs(preferredMountDirs || []) - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.setSfmiBannerDismissedDesktop = wrapErrors((dismissed: boolean) => { - const f = async () => { - await T.RPCGen.SimpleFSSimpleFSSetSfmiBannerDismissedRpcPromise({dismissed}) - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.afterDriverEnabled = wrapErrors((isRetry: boolean) => { - const f = async () => { - useFSState.getState().dispatch.dynamic.setSfmiBannerDismissedDesktop?.(false) - if (isWindows) { - await onInstallCachedDokan() - } else { - await driverEnableFuse(isRetry) - } - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.afterDriverDisable = wrapErrors(() => { - const f = async () => { - useFSState.getState().dispatch.dynamic.setSfmiBannerDismissedDesktop?.(false) - if (isWindows) { - await uninstallDokanConfirm() - } else { - await uninstallKBFSConfirm() - } - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.afterDriverDisabling = wrapErrors(() => { - const f = async () => { - if (isWindows) { - await onUninstallDokan() - } else { - await uninstallKBFS() - } - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.openSecurityPreferencesDesktop = wrapErrors(() => { - const f = async () => { - await openURL?.('x-apple.systempreferences:com.apple.preference.security?General', {activate: true}) - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.openFilesFromWidgetDesktop = wrapErrors((path: T.FS.Path) => { - storeRegistry.getState('config').dispatch.showMain() - if (path) { - Constants.makeActionForOpenPathInFilesTab(path) - } else { - navigateAppend(Tabs.fsTab) - } - }) - - s.dispatch.dynamic.openAndUploadDesktop = wrapErrors( - (type: T.FS.OpenDialogType, parentPath: T.FS.Path) => { - const f = async () => { - const localPaths = await (selectFilesToUploadDialog?.(type, parentPath ?? undefined) ?? - Promise.resolve([])) - localPaths.forEach(localPath => useFSState.getState().dispatch.upload(parentPath, localPath)) - } - ignorePromise(f()) - } - ) - - if (!isLinux) { - s.dispatch.dynamic.afterKbfsDaemonRpcStatusChanged = wrapErrors(() => { - const {kbfsDaemonStatus, dispatch} = useFSState.getState() - if (kbfsDaemonStatus.rpcStatus === T.FS.KbfsDaemonRpcStatus.Connected) { - dispatch.dynamic.refreshDriverStatusDesktop?.() - } - dispatch.dynamic.refreshMountDirsDesktop?.() - }) - // force call as it could have happened already - s.dispatch.dynamic.afterKbfsDaemonRpcStatusChanged() - } - }) -} - -export default initPlatformSpecific diff --git a/shared/constants/fs/platform-specific.ios.tsx b/shared/constants/fs/platform-specific.ios.tsx deleted file mode 100644 index abfbc2d4b997..000000000000 --- a/shared/constants/fs/platform-specific.ios.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import nativeInit from './common.native' -export default nativeInit diff --git a/shared/constants/fs/util.tsx b/shared/constants/fs/util.tsx deleted file mode 100644 index 0ecea95b7a97..000000000000 --- a/shared/constants/fs/util.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import type * as T from '../types' -import {navigateAppend} from '../router2/util' -import {storeRegistry} from '../store-registry' - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyFSFSOverallSyncStatusChanged: - case EngineGen.keybase1NotifyFSFSSubscriptionNotifyPath: - case EngineGen.keybase1NotifyFSFSSubscriptionNotify: - { - storeRegistry.getState('fs').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} - -export const makeActionForOpenPathInFilesTab = ( - // TODO: remove the second arg when we are done with migrating to nav2 - path: T.FS.Path -) => { - navigateAppend({props: {path}, selected: 'fsRoot'}) -} diff --git a/shared/constants/git/util.tsx b/shared/constants/git/util.tsx deleted file mode 100644 index c72418bcf735..000000000000 --- a/shared/constants/git/util.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -let loadedStore = false -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyBadgesBadgeState: - { - const {badgeState} = action.payload.params - const badges = new Set(badgeState.newGitRepoGlobalUniqueIDs) - // don't bother loading the store if no badges - if (loadedStore || badges.size) { - loadedStore = true - storeRegistry.getState('git').dispatch.onEngineIncomingImpl(action) - } - } - break - default: - } -} diff --git a/shared/constants/index.tsx b/shared/constants/index.tsx index b3359e15383d..7065a91ae687 100644 --- a/shared/constants/index.tsx +++ b/shared/constants/index.tsx @@ -1,10 +1,9 @@ export * from './platform' export * from './values' export * from './strings' -export {useRouterState, makeScreen} from './router2' -export * as Router2 from './router2' +export {useRouterState, makeScreen} from '@/stores/router2' +export * as Router2 from '@/stores/router2' export * as Tabs from './tabs' -export {useWaitingState} from './waiting' -export * as Waiting from './waiting' -export * as PlatformSpecific from './platform-specific' +export {useWaitingState} from '@/stores/waiting' +export * as Waiting from '@/stores/waiting' export * from './utils' diff --git a/shared/constants/init/index.d.ts b/shared/constants/init/index.d.ts new file mode 100644 index 000000000000..f08146c34c87 --- /dev/null +++ b/shared/constants/init/index.d.ts @@ -0,0 +1,7 @@ +// links all the stores together, stores never import this +import type * as EngineGen from '@/actions/engine-gen-gen' + +export declare function initPlatformListener(): void +export declare function onEngineConnected(): void +export declare function onEngineDisconnected(): void +export declare function onEngineIncoming(action: EngineGen.Actions): void diff --git a/shared/constants/init/index.desktop.tsx b/shared/constants/init/index.desktop.tsx new file mode 100644 index 000000000000..08d952d63514 --- /dev/null +++ b/shared/constants/init/index.desktop.tsx @@ -0,0 +1,583 @@ +// links all the stores together, stores never import this +import * as Chat from '@/stores/chat2' +import {ignorePromise} from '@/constants/utils' +import {useConfigState} from '@/stores/config' +import * as ConfigConstants from '@/stores/config' +import {useDaemonState} from '@/stores/daemon' +import {useFSState} from '@/stores/fs' +import {useProfileState} from '@/stores/profile' +import {useRouterState} from '@/stores/router2' +import * as EngineGen from '@/actions/engine-gen-gen' +import * as T from '@/constants/types' +import InputMonitor from '@/util/platform-specific/input-monitor.desktop' +import KB2 from '@/util/electron.desktop' +import logger from '@/logger' +import type {RPCError} from '@/util/errors' +import {getEngine} from '@/engine' +import {isLinux, isWindows, isDarwin, pathSep} from '@/constants/platform.desktop' +import {kbfsNotification} from '@/util/platform-specific/kbfs-notifications' +import {skipAppFocusActions} from '@/local-debug.desktop' +import NotifyPopup from '@/util/notify-popup' +import {noKBFSFailReason} from '@/constants/config' +import {initSharedSubscriptions, _onEngineIncoming} from './shared' +import {wrapErrors} from '@/util/debug' +import * as Constants from '@/constants/fs' +import * as Tabs from '@/constants/tabs' +import * as Path from '@/util/path' +import {uint8ArrayToHex} from 'uint8array-extras' +import {navigateAppend} from '@/constants/router2' +import {errorToActionOrThrow} from '@/stores/fs' +import {ExitCodeFuseKextPermissionError} from '@/constants/values' + +const {showMainWindow, activeChanged, requestWindowsStartService, ctlQuit, dumpNodeLogger} = KB2.functions +const {quitApp, exitApp, setOpenAtLogin, copyToClipboard} = KB2.functions +const {openPathInFinder, openURL, getPathType, selectFilesToUploadDialog} = KB2.functions +const {darwinCopyToKBFSTempUploadFile, relaunchApp, uninstallKBFSDialog, uninstallDokanDialog} = KB2.functions +const {windowsCheckMountFromOtherDokanInstall, installCachedDokan, uninstallDokan} = KB2.functions + +const dumpLogs = async (reason?: string) => { + await logger.dump() + await (dumpNodeLogger?.() ?? Promise.resolve([])) + // quit as soon as possible + if (reason === 'quitting through menu') { + ctlQuit?.() + } +} + +const maybePauseVideos = () => { + const {appFocused} = useConfigState.getState() + const videos = document.querySelectorAll('video') + const allVideos = Array.from(videos) + + allVideos.forEach(v => { + if (appFocused) { + if (v.hasAttribute('data-focus-paused')) { + if (v.paused) { + v.play() + .then(() => {}) + .catch(() => {}) + } + } + } else { + // only pause looping videos + if (!v.paused && v.hasAttribute('loop') && v.hasAttribute('autoplay')) { + v.setAttribute('data-focus-paused', 'true') + v.pause() + } + } + }) +} + +export const onEngineIncoming = (action: EngineGen.Actions) => { + _onEngineIncoming(action) + switch (action.type) { + case EngineGen.keybase1LogsendPrepareLogsend: { + const f = async () => { + const response = action.payload.response + try { + await dumpLogs() + } finally { + response.result() + } + } + ignorePromise(f()) + break + } + case EngineGen.keybase1NotifyAppExit: + console.log('App exit requested') + exitApp?.(0) + break + case EngineGen.keybase1NotifyFSFSActivity: + kbfsNotification(action.payload.params.notification, NotifyPopup) + break + case EngineGen.keybase1NotifyPGPPgpKeyInSecretStoreFile: { + const f = async () => { + try { + await T.RPCGen.pgpPgpStorageDismissRpcPromise() + } catch (err) { + console.warn('Error in sending pgpPgpStorageDismissRpc:', err) + } + } + ignorePromise(f()) + break + } + case EngineGen.keybase1NotifyServiceShutdown: { + const {code} = action.payload.params + if (isWindows && code !== (T.RPCGen.ExitCode.restart as number)) { + console.log('Quitting due to service shutdown with code: ', code) + // Quit just the app, not the service + quitApp?.() + } + break + } + + case EngineGen.keybase1LogUiLog: { + const {params} = action.payload + const {level, text} = params + logger.info('keybase.1.logUi.log:', params.text.data) + if (level >= T.RPCGen.LogLevel.error) { + NotifyPopup(text.data) + } + break + } + + case EngineGen.keybase1NotifySessionClientOutOfDate: { + const {upgradeTo, upgradeURI, upgradeMsg} = action.payload.params + const body = upgradeMsg || `Please update to ${upgradeTo} by going to ${upgradeURI}` + NotifyPopup('Client out of date!', {body}, 60 * 60) + // This is from the API server. Consider notifications from server always critical. + useConfigState + .getState() + .dispatch.setOutOfDate({critical: true, message: upgradeMsg, outOfDate: true, updating: false}) + break + } + default: + } +} + +// _openPathInSystemFileManagerPromise opens `openPath` in system file manager. +// If isFolder is true, it just opens it. Otherwise, it shows it in its parent +// folder. This function does not check if the file exists, or try to convert +// KBFS paths. Caller should take care of those. +const _openPathInSystemFileManagerPromise = async (openPath: string, isFolder: boolean): Promise => + openPathInFinder?.(openPath, isFolder) + +const escapeBackslash = isWindows + ? (pathElem: string): string => + pathElem + .replace(/‰/g, '‰2030') + .replace(/([<>:"/\\|?*])/g, (_, c: Uint8Array) => '‰' + uint8ArrayToHex(c)) + : (pathElem: string): string => pathElem + +const _rebaseKbfsPathToMountLocation = (kbfsPath: T.FS.Path, mountLocation: string) => + Path.join(mountLocation, T.FS.getPathElements(kbfsPath).slice(1).map(escapeBackslash).join(pathSep)) + +const fuseStatusToUninstallExecPath = isWindows + ? (status: T.RPCGen.FuseStatus) => { + const field = status.status.fields?.find(({key}) => key === 'uninstallString') + return field?.value + } + : () => undefined + +const fuseStatusToActions = + (previousStatusType: T.FS.DriverStatusType) => (status: T.RPCGen.FuseStatus | undefined) => { + if (!status) { + useFSState.getState().dispatch.setDriverStatus(Constants.defaultDriverStatus) + return + } + + if (status.kextStarted) { + useFSState.getState().dispatch.setDriverStatus({ + ...Constants.emptyDriverStatusEnabled, + dokanOutdated: status.installAction === T.RPCGen.InstallAction.upgrade, + dokanUninstallExecPath: fuseStatusToUninstallExecPath(status), + }) + } else { + useFSState.getState().dispatch.setDriverStatus(Constants.emptyDriverStatusDisabled) + } + + if (status.kextStarted && previousStatusType === T.FS.DriverStatusType.Disabled) { + useFSState + .getState() + .dispatch.defer.openPathInSystemFileManagerDesktop?.(T.FS.stringToPath('/keybase')) + } + } + +const fuseInstallResultIsKextPermissionError = (result: T.RPCGen.InstallResult): boolean => + result.componentResults?.findIndex( + c => c.name === 'fuse' && c.exitCode === ExitCodeFuseKextPermissionError + ) !== -1 + +const driverEnableFuse = async (isRetry: boolean) => { + const result = await T.RPCGen.installInstallFuseRpcPromise() + if (fuseInstallResultIsKextPermissionError(result)) { + useFSState.getState().dispatch.driverKextPermissionError() + if (!isRetry) { + navigateAppend('kextPermission') + } + } else { + await T.RPCGen.installInstallKBFSRpcPromise() // restarts kbfsfuse + await T.RPCGen.kbfsMountWaitForMountsRpcPromise() + useFSState.getState().dispatch.defer.refreshDriverStatusDesktop?.() + } +} + +const uninstallKBFSConfirm = async () => { + const remove = await (uninstallKBFSDialog?.() ?? Promise.resolve(false)) + if (remove) { + useFSState.getState().dispatch.driverDisabling() + } +} + +const uninstallKBFS = async () => + T.RPCGen.installUninstallKBFSRpcPromise().then(() => { + // Restart since we had to uninstall KBFS and it's needed by the service (for chat) + relaunchApp?.() + exitApp?.(0) + }) + +const uninstallDokanConfirm = async () => { + const driverStatus = useFSState.getState().sfmi.driverStatus + if (driverStatus.type !== T.FS.DriverStatusType.Enabled) { + return + } + if (!driverStatus.dokanUninstallExecPath) { + await uninstallDokanDialog?.() + useFSState.getState().dispatch.defer.refreshDriverStatusDesktop?.() + return + } + useFSState.getState().dispatch.driverDisabling() +} + +const onUninstallDokan = async () => { + const driverStatus = useFSState.getState().sfmi.driverStatus + if (driverStatus.type !== T.FS.DriverStatusType.Enabled) return + const execPath: string = driverStatus.dokanUninstallExecPath || '' + logger.info('Invoking dokan uninstaller', execPath) + try { + await uninstallDokan?.(execPath) + } catch {} + useFSState.getState().dispatch.defer.refreshDriverStatusDesktop?.() +} + +// Invoking the cached installer package has to happen from the topmost process +// or it won't be visible to the user. The service also does this to support command line +// operations. +const onInstallCachedDokan = async () => { + try { + await installCachedDokan?.() + useFSState.getState().dispatch.defer.refreshDriverStatusDesktop?.() + } catch (e) { + errorToActionOrThrow(e) + } +} + +export const initPlatformListener = () => { + useConfigState.setState(s => { + s.dispatch.defer.dumpLogsNative = dumpLogs + s.dispatch.defer.showMainNative = wrapErrors(() => showMainWindow?.()) + s.dispatch.defer.copyToClipboard = wrapErrors((s: string) => copyToClipboard?.(s)) + s.dispatch.defer.onEngineConnectedDesktop = wrapErrors(() => { + // Introduce ourselves to the service + const f = async () => { + await T.RPCGen.configHelloIAmRpcPromise({details: KB2.constants.helloDetails}) + } + ignorePromise(f()) + }) + }) + + useConfigState.subscribe((s, old) => { + if (s.appFocused === old.appFocused) return + useFSState.getState().dispatch.onChangedFocus(s.appFocused) + }) + + useConfigState.subscribe((s, old) => { + if (s.loggedIn !== old.loggedIn) { + s.dispatch.osNetworkStatusChanged(navigator.onLine, 'notavailable', true) + } + + if (s.appFocused !== old.appFocused) { + maybePauseVideos() + } + + if (s.openAtLogin !== old.openAtLogin) { + const {openAtLogin} = s + const f = async () => { + if (__DEV__) { + console.log('onSetOpenAtLogin disabled for dev mode') + return + } else { + await T.RPCGen.configGuiSetValueRpcPromise({ + path: ConfigConstants.openAtLoginKey, + value: {b: openAtLogin, isNull: false}, + }) + } + if (isLinux || isWindows) { + const enabled = + (await T.RPCGen.ctlGetOnLoginStartupRpcPromise()) === T.RPCGen.OnLoginStartupStatus.enabled + if (enabled !== openAtLogin) { + try { + await T.RPCGen.ctlSetOnLoginStartupRpcPromise({enabled: openAtLogin}) + } catch (error_) { + const error = error_ as RPCError + logger.warn(`Error in sending ctlSetOnLoginStartup: ${error.message}`) + } + } + } else { + logger.info(`Login item settings changed! now ${openAtLogin ? 'on' : 'off'}`) + await setOpenAtLogin?.(openAtLogin) + } + } + ignorePromise(f()) + } + }) + + const handleWindowFocusEvents = () => { + const handle = (appFocused: boolean) => { + if (skipAppFocusActions) { + console.log('Skipping app focus actions!') + } else { + useConfigState.getState().dispatch.changedFocus(appFocused) + } + } + window.addEventListener('focus', () => handle(true)) + window.addEventListener('blur', () => handle(false)) + } + handleWindowFocusEvents() + + const setupReachabilityWatcher = () => { + window.addEventListener('online', () => + useConfigState.getState().dispatch.osNetworkStatusChanged(true, 'notavailable') + ) + window.addEventListener('offline', () => + useConfigState.getState().dispatch.osNetworkStatusChanged(false, 'notavailable') + ) + } + setupReachabilityWatcher() + + useDaemonState.subscribe((s, old) => { + if (s.handshakeVersion !== old.handshakeVersion) { + if (!isWindows) return + + const f = async () => { + const waitKey = 'pipeCheckFail' + const version = s.handshakeVersion + const {wait} = s.dispatch + wait(waitKey, version, true) + try { + logger.info('Checking RPC ownership') + if (KB2.functions.winCheckRPCOwnership) { + await KB2.functions.winCheckRPCOwnership() + } + wait(waitKey, version, false) + } catch (error_) { + // error will be logged in bootstrap check + getEngine().reset() + const error = error_ as RPCError + wait(waitKey, version, false, error.message || 'windows pipe owner fail', true) + } + } + ignorePromise(f()) + } + + if (s.handshakeState !== old.handshakeState && s.handshakeState === 'done') { + useConfigState.getState().dispatch.setStartupDetails({ + conversation: Chat.noConversationIDKey, + followUser: '', + link: '', + tab: undefined, + }) + } + }) + + if (isLinux) { + useConfigState.getState().dispatch.initUseNativeFrame() + } + useConfigState.getState().dispatch.initNotifySound() + useConfigState.getState().dispatch.initForceSmallNav() + useConfigState.getState().dispatch.initOpenAtLogin() + useConfigState.getState().dispatch.initAppUpdateLoop() + + useProfileState.setState(s => { + s.dispatch.editAvatar = () => { + useRouterState + .getState() + .dispatch.navigateAppend({props: {image: undefined}, selected: 'profileEditAvatar'}) + } + }) + + const initializeInputMonitor = () => { + const inputMonitor = new InputMonitor() + inputMonitor.notifyActive = (userActive: boolean) => { + if (skipAppFocusActions) { + console.log('Skipping app focus actions!') + } else { + useConfigState.getState().dispatch.setActive(userActive) + // let node thread save file + activeChanged?.(Date.now(), userActive) + } + } + } + initializeInputMonitor() + + useDaemonState.setState(s => { + s.dispatch.onRestartHandshakeNative = () => { + const {handshakeFailedReason} = useDaemonState.getState() + if (isWindows && handshakeFailedReason === noKBFSFailReason) { + requestWindowsStartService?.() + } + } + }) + + useFSState.setState(s => { + s.dispatch.defer.uploadFromDragAndDropDesktop = wrapErrors( + (parentPath: T.FS.Path, localPaths: string[]) => { + const {upload} = useFSState.getState().dispatch + const f = async () => { + if (isDarwin && darwinCopyToKBFSTempUploadFile) { + const dir = await T.RPCGen.SimpleFSSimpleFSMakeTempDirForUploadRpcPromise() + const lp = await Promise.all( + localPaths.map(async localPath => darwinCopyToKBFSTempUploadFile(dir, localPath)) + ) + lp.forEach(localPath => upload(parentPath, localPath)) + } else { + localPaths.forEach(localPath => upload(parentPath, localPath)) + } + } + ignorePromise(f()) + } + ) + + s.dispatch.defer.openLocalPathInSystemFileManagerDesktop = wrapErrors((localPath: string) => { + const f = async () => { + try { + if (getPathType) { + const pathType = await getPathType(localPath) + await _openPathInSystemFileManagerPromise(localPath, pathType === 'directory') + } + } catch (e) { + errorToActionOrThrow(e) + } + } + ignorePromise(f()) + }) + + s.dispatch.defer.openPathInSystemFileManagerDesktop = wrapErrors((path: T.FS.Path) => { + const f = async () => { + const {sfmi, pathItems} = useFSState.getState() + return sfmi.driverStatus.type === T.FS.DriverStatusType.Enabled && sfmi.directMountDir + ? _openPathInSystemFileManagerPromise( + _rebaseKbfsPathToMountLocation(path, sfmi.directMountDir), + ![T.FS.PathKind.InGroupTlf, T.FS.PathKind.InTeamTlf].includes(Constants.parsePath(path).kind) || + Constants.getPathItem(pathItems, path).type === T.FS.PathType.Folder + ).catch((e: unknown) => errorToActionOrThrow(e, path)) + : new Promise((resolve, reject) => { + if (sfmi.driverStatus.type !== T.FS.DriverStatusType.Enabled) { + // This usually indicates a developer error as + // openPathInSystemFileManager shouldn't be used when FUSE integration + // is not enabled. So just blackbar to encourage a log send. + reject(new Error('FUSE integration is not enabled')) + } else { + logger.warn('empty directMountDir') // if this happens it might be a race? + resolve() + } + }) + } + ignorePromise(f()) + }) + + s.dispatch.defer.refreshDriverStatusDesktop = wrapErrors(() => { + const f = async () => { + let status = await T.RPCGen.installFuseStatusRpcPromise({ + bundleVersion: '', + }) + if (isWindows && status.installStatus !== T.RPCGen.InstallStatus.installed) { + const m = await T.RPCGen.kbfsMountGetCurrentMountDirRpcPromise() + status = await (windowsCheckMountFromOtherDokanInstall?.(m, status) ?? Promise.resolve(status)) + } + fuseStatusToActions(useFSState.getState().sfmi.driverStatus.type)(status) + } + ignorePromise(f()) + }) + + s.dispatch.defer.refreshMountDirsDesktop = wrapErrors(() => { + const f = async () => { + const {sfmi, dispatch} = useFSState.getState() + const driverStatus = sfmi.driverStatus + if (driverStatus.type !== T.FS.DriverStatusType.Enabled) { + return + } + const directMountDir = await T.RPCGen.kbfsMountGetCurrentMountDirRpcPromise() + const preferredMountDirs = await T.RPCGen.kbfsMountGetPreferredMountDirsRpcPromise() + dispatch.setDirectMountDir(directMountDir) + dispatch.setPreferredMountDirs(preferredMountDirs || []) + } + ignorePromise(f()) + }) + + s.dispatch.defer.setSfmiBannerDismissedDesktop = wrapErrors((dismissed: boolean) => { + const f = async () => { + await T.RPCGen.SimpleFSSimpleFSSetSfmiBannerDismissedRpcPromise({dismissed}) + } + ignorePromise(f()) + }) + + s.dispatch.defer.afterDriverEnabled = wrapErrors((isRetry: boolean) => { + const f = async () => { + useFSState.getState().dispatch.defer.setSfmiBannerDismissedDesktop?.(false) + if (isWindows) { + await onInstallCachedDokan() + } else { + await driverEnableFuse(isRetry) + } + } + ignorePromise(f()) + }) + + s.dispatch.defer.afterDriverDisable = wrapErrors(() => { + const f = async () => { + useFSState.getState().dispatch.defer.setSfmiBannerDismissedDesktop?.(false) + if (isWindows) { + await uninstallDokanConfirm() + } else { + await uninstallKBFSConfirm() + } + } + ignorePromise(f()) + }) + + s.dispatch.defer.afterDriverDisabling = wrapErrors(() => { + const f = async () => { + if (isWindows) { + await onUninstallDokan() + } else { + await uninstallKBFS() + } + } + ignorePromise(f()) + }) + + s.dispatch.defer.openSecurityPreferencesDesktop = wrapErrors(() => { + const f = async () => { + await openURL?.('x-apple.systempreferences:com.apple.preference.security?General', {activate: true}) + } + ignorePromise(f()) + }) + + s.dispatch.defer.openFilesFromWidgetDesktop = wrapErrors((path: T.FS.Path) => { + useConfigState.getState().dispatch.showMain() + if (path) { + Constants.navToPath(path) + } else { + navigateAppend(Tabs.fsTab) + } + }) + + s.dispatch.defer.openAndUploadDesktop = wrapErrors( + (type: T.FS.OpenDialogType, parentPath: T.FS.Path) => { + const f = async () => { + const localPaths = await (selectFilesToUploadDialog?.(type, parentPath ?? undefined) ?? + Promise.resolve([])) + localPaths.forEach(localPath => useFSState.getState().dispatch.upload(parentPath, localPath)) + } + ignorePromise(f()) + } + ) + + if (!isLinux) { + s.dispatch.defer.afterKbfsDaemonRpcStatusChanged = wrapErrors(() => { + const {kbfsDaemonStatus, dispatch} = useFSState.getState() + if (kbfsDaemonStatus.rpcStatus === T.FS.KbfsDaemonRpcStatus.Connected) { + dispatch.defer.refreshDriverStatusDesktop?.() + } + dispatch.defer.refreshMountDirsDesktop?.() + }) + // force call as it could have happened already + s.dispatch.defer.afterKbfsDaemonRpcStatusChanged() + } + }) + + initSharedSubscriptions() +} + +export {onEngineConnected, onEngineDisconnected} from './shared' diff --git a/shared/constants/platform-specific/index.native.tsx b/shared/constants/init/index.native.tsx similarity index 58% rename from shared/constants/platform-specific/index.native.tsx rename to shared/constants/init/index.native.tsx index 82d9536f9600..31e1b322cdd1 100644 --- a/shared/constants/platform-specific/index.native.tsx +++ b/shared/constants/init/index.native.tsx @@ -1,139 +1,54 @@ +// links all the stores together, stores never import this import {ignorePromise, neverThrowPromiseFunc, timeoutPromise} from '../utils' -import {storeRegistry} from '../store-registry' -import * as T from '../types' +import {useChatState} from '@/stores/chat2' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' +import {useDaemonState} from '@/stores/daemon' +import {useDarkModeState} from '@/stores/darkmode' +import {useFSState} from '@/stores/fs' +import {useProfileState} from '@/stores/profile' +import {useRouterState} from '@/stores/router2' +import {useSettingsContactsState} from '@/stores/settings-contacts' +import * as T from '@/constants/types' import * as Clipboard from 'expo-clipboard' import * as EngineGen from '@/actions/engine-gen-gen' import * as ExpoLocation from 'expo-location' import * as ExpoTaskManager from 'expo-task-manager' -import * as MediaLibrary from 'expo-media-library' -import * as Tabs from '../tabs' +import * as Tabs from '@/constants/tabs' import * as NetInfo from '@react-native-community/netinfo' import NotifyPopup from '@/util/notify-popup' -import {addNotificationRequest} from 'react-native-kb' import logger from '@/logger' -import {Alert, Linking, ActionSheetIOS} from 'react-native' -import {isIOS, isAndroid} from '../platform.native' +import {Alert, Linking} from 'react-native' +import {isAndroid} from '@/constants/platform.native' import {wrapErrors} from '@/util/debug' -import {getTab, getVisiblePath, logState} from '../router2' +import {getTab, getVisiblePath, logState} from '@/constants/router2' import {launchImageLibraryAsync} from '@/util/expo-image-picker.native' import {setupAudioMode} from '@/util/audio.native' import { + androidAddCompleteDownload, androidOpenSettings, - androidShare, - androidShareText, - androidUnlink, fsCacheDir, fsDownloadDir, androidAppColorSchemeChanged, guiConfig, shareListenersRegistered, } from 'react-native-kb' -import {initPushListener, getStartupDetailsFromInitialPush} from './push.native' +import {initPushListener, getStartupDetailsFromInitialPush} from './push-listener.native' +import {initSharedSubscriptions, _onEngineIncoming} from './shared' import type {ImageInfo} from '@/util/expo-image-picker.native' -import {noConversationIDKey} from '@/constants/types/chat2/common' - -export const requestPermissionsToWrite = async () => { - if (isAndroid) { - const p = await MediaLibrary.requestPermissionsAsync(false) - return p.granted ? Promise.resolve() : Promise.reject(new Error('Unable to acquire storage permissions')) - } - return Promise.resolve() -} - -export const requestLocationPermission = async (mode: T.RPCChat.UIWatchPositionPerm) => { - if (isIOS) { - logger.info('[location] Requesting location perms', mode) - switch (mode) { - case T.RPCChat.UIWatchPositionPerm.base: - { - const iosFGPerms = await ExpoLocation.requestForegroundPermissionsAsync() - if (iosFGPerms.ios?.scope === 'none') { - throw new Error('Please allow Keybase to access your location in the phone settings.') - } - } - break - case T.RPCChat.UIWatchPositionPerm.always: { - const iosBGPerms = await ExpoLocation.requestBackgroundPermissionsAsync() - if (iosBGPerms.status !== ExpoLocation.PermissionStatus.GRANTED) { - throw new Error( - 'Please allow Keybase to access your location even if the app is not running for live location.' - ) - } - break - } - } - } else if (isAndroid) { - const androidBGPerms = await ExpoLocation.requestForegroundPermissionsAsync() - if (androidBGPerms.status !== ExpoLocation.PermissionStatus.GRANTED) { - throw new Error('Unable to acquire location permissions') - } - } -} - -export async function saveAttachmentToCameraRoll(filePath: string, mimeType: string): Promise { - const fileURL = 'file://' + filePath - const saveType: 'video' | 'photo' = mimeType.startsWith('video') ? 'video' : 'photo' - const logPrefix = '[saveAttachmentToCameraRoll] ' - try { - try { - // see it we can keep going anyways, android perms are needed sometimes and sometimes not w/ 33 - await requestPermissionsToWrite() - } catch {} - logger.info(logPrefix + `Attempting to save as ${saveType}`) - await MediaLibrary.saveToLibraryAsync(fileURL) - logger.info(logPrefix + 'Success') - } catch (e) { - // This can fail if the user backgrounds too quickly, so throw up a local notification - // just in case to get their attention. - addNotificationRequest({ - body: `Failed to save ${saveType} to camera roll`, - id: Math.floor(Math.random() * 2 ** 32).toString(), - }).catch(() => {}) - logger.debug(logPrefix + 'failed to save: ' + e) - throw e - } finally { - try { - await androidUnlink(filePath) - } catch { - logger.warn('failed to unlink') - } - } -} - -export const showShareActionSheet = async (options: { - filePath?: string - message?: string - mimeType: string -}) => { - if (isIOS) { - return new Promise((resolve, reject) => { - ActionSheetIOS.showShareActionSheetWithOptions( - { - message: options.message, - url: options.filePath, - }, - reject, - resolve - ) - }) - } else { - if (!options.filePath && options.message) { - try { - await androidShareText(options.message, options.mimeType) - return {completed: true, method: ''} - } catch (e) { - throw new Error('Failed to share: ' + String(e)) - } - } +import {noConversationIDKey} from '../types/chat2/common' +import {getSelectedConversation} from '../chat2/common' +import {getConvoState} from '@/stores/convostate' +import { + requestLocationPermission, + saveAttachmentToCameraRoll, + showShareActionSheet, +} from '@/util/platform-specific/index.native' +import * as FS from '@/constants/fs' +import {errorToActionOrThrow} from '@/stores/fs' +import * as Styles from '@/styles' - try { - await androidShare(options.filePath ?? '', options.mimeType) - return {completed: true, method: ''} - } catch (e) { - throw new Error('Failed to share: ' + String(e)) - } - } -} +const finishedRegularDownloadIDs = new Set() const loadStartupDetails = async () => { const [routeState, initialUrl, push] = await Promise.all([ @@ -191,7 +106,7 @@ const loadStartupDetails = async () => { tab = '' } - storeRegistry.getState('config').dispatch.setStartupDetails({ + useConfigState.getState().dispatch.setStartupDetails({ conversation: conversation ?? noConversationIDKey, followUser, link, @@ -207,8 +122,42 @@ const loadStartupDetails = async () => { ) } +const locationTaskName = 'background-location-task' +let locationRefs = 0 +let madeBackgroundTask = false + +const ensureBackgroundTask = () => { + if (madeBackgroundTask) return + madeBackgroundTask = true + + ExpoTaskManager.defineTask(locationTaskName, async ({data, error}) => { + if (error) { + // check `error.message` for more details. + return Promise.resolve() + } + + if (!data) { + return Promise.resolve() + } + const d = data as {locations?: Array} + const locations = d.locations + if (!locations?.length) { + return Promise.resolve() + } + const pos = locations.at(-1) + const coord = { + accuracy: Math.floor(pos?.coords.accuracy ?? 0), + lat: pos?.coords.latitude ?? 0, + lon: pos?.coords.longitude ?? 0, + } + + useChatState.getState().dispatch.updateLastCoord(coord) + return Promise.resolve() + }) +} + const setPermissionDeniedCommandStatus = (conversationIDKey: T.Chat.ConversationIDKey, text: string) => { - storeRegistry.getConvoState(conversationIDKey).dispatch.setCommandStatusInfo({ + getConvoState(conversationIDKey).dispatch.setCommandStatusInfo({ actions: [T.RPCChat.UICommandStatusActionTyp.appsettings], displayText: text, displayType: T.RPCChat.UICommandStatusDisplayTyp.error, @@ -265,76 +214,35 @@ const onChatClearWatch = async () => { } } -const locationTaskName = 'background-location-task' -let locationRefs = 0 -let madeBackgroundTask = false - -const ensureBackgroundTask = () => { - if (madeBackgroundTask) return - madeBackgroundTask = true - - ExpoTaskManager.defineTask(locationTaskName, async ({data, error}) => { - if (error) { - // check `error.message` for more details. - return Promise.resolve() - } - - if (!data) { - return Promise.resolve() - } - const d = data as {locations?: Array} - const locations = d.locations - if (!locations?.length) { - return Promise.resolve() - } - const pos = locations.at(-1) - const coord = { - accuracy: Math.floor(pos?.coords.accuracy ?? 0), - lat: pos?.coords.latitude ?? 0, - lon: pos?.coords.longitude ?? 0, - } - - storeRegistry.getState('chat').dispatch.updateLastCoord(coord) - return Promise.resolve() - }) -} - -export const watchPositionForMap = async (conversationIDKey: T.Chat.ConversationIDKey) => { - try { - logger.info('[location] perms check due to map') - await requestLocationPermission(T.RPCChat.UIWatchPositionPerm.base) - } catch (_error) { - const error = _error as {message?: string} - logger.info('failed to get location perms: ' + error.message) - setPermissionDeniedCommandStatus(conversationIDKey, `Failed to access location. ${error.message}`) - return () => {} - } - - try { - const sub = await ExpoLocation.watchPositionAsync( - {accuracy: ExpoLocation.LocationAccuracy.Highest}, - location => { - const coord = { - accuracy: Math.floor(location.coords.accuracy ?? 0), - lat: location.coords.latitude, - lon: location.coords.longitude, - } - storeRegistry.getState('chat').dispatch.updateLastCoord(coord) +export const onEngineIncoming = (action: EngineGen.Actions) => { + _onEngineIncoming(action) + switch (action.type) { + case EngineGen.chat1ChatUiTriggerContactSync: + useSettingsContactsState.getState().dispatch.manageContactsCache() + break + case EngineGen.keybase1LogUiLog: { + const {params} = action.payload + const {level, text} = params + logger.info('keybase.1.logUi.log:', params.text.data) + if (level >= T.RPCGen.LogLevel.error) { + NotifyPopup(text.data) } - ) - return () => sub.remove() - } catch (_error) { - const error = _error as {message?: string} - logger.info('failed to get location: ' + error.message) - setPermissionDeniedCommandStatus(conversationIDKey, `Failed to access location. ${error.message}`) - return () => {} + break + } + case EngineGen.chat1ChatUiChatWatchPosition: + ignorePromise(onChatWatchPosition(action)) + break + case EngineGen.chat1ChatUiChatClearWatch: + ignorePromise(onChatClearWatch()) + break + default: } } export const initPlatformListener = () => { let _lastPersist = '' - storeRegistry.getStore('config').setState(s => { - s.dispatch.dynamic.persistRoute = wrapErrors((clear: boolean, immediate: boolean) => { + useConfigState.setState(s => { + s.dispatch.defer.persistRoute = wrapErrors((clear: boolean, immediate: boolean) => { const doClear = async () => { try { await T.RPCGen.configGuiSetValueRpcPromise({ @@ -344,7 +252,7 @@ export const initPlatformListener = () => { } catch {} } const doPersist = async () => { - if (!storeRegistry.getState('config').startup.loaded) { + if (!useConfigState.getState().startup.loaded) { return } let param = {} @@ -387,15 +295,9 @@ export const initPlatformListener = () => { ignorePromise(f()) } }) - - s.dispatch.dynamic.onEngineIncomingNative = wrapErrors((action: EngineGen.Actions) => { - switch (action.type) { - default: - } - }) }) - storeRegistry.getStore('config').subscribe((s, old) => { + useConfigState.subscribe((s, old) => { if (s.mobileAppState === old.mobileAppState) return let appFocused: boolean let logState: T.RPCGen.MobileAppState @@ -407,7 +309,7 @@ export const initPlatformListener = () => { case 'background': appFocused = false logState = T.RPCGen.MobileAppState.background - storeRegistry.getState('config').dispatch.dynamic.persistRoute?.(false, true) + useConfigState.getState().dispatch.defer.persistRoute?.(false, true) break case 'inactive': appFocused = false @@ -419,11 +321,17 @@ export const initPlatformListener = () => { } logger.info(`setting app state on service to: ${logState}`) - storeRegistry.getState('config').dispatch.changedFocus(appFocused) + s.dispatch.changedFocus(appFocused) + + if (appFocused && old.mobileAppState !== 'active') { + const {dispatch} = getConvoState(getSelectedConversation()) + dispatch.loadMoreMessages({reason: 'foregrounding'}) + dispatch.markThreadAsRead() + } }) - storeRegistry.getStore('config').setState(s => { - s.dispatch.dynamic.copyToClipboard = wrapErrors((s: string) => { + useConfigState.setState(s => { + s.dispatch.defer.copyToClipboard = wrapErrors((s: string) => { Clipboard.setStringAsync(s) .then(() => {}) .catch(() => {}) @@ -451,7 +359,7 @@ export const initPlatformListener = () => { } } - storeRegistry.getStore('daemon').subscribe((s, old) => { + useDaemonState.subscribe((s, old) => { const versionChanged = s.handshakeVersion !== old.handshakeVersion const stateChanged = s.handshakeState !== old.handshakeState const justBecameReady = stateChanged && s.handshakeState === 'done' && old.handshakeState !== 'done' @@ -461,11 +369,11 @@ export const initPlatformListener = () => { } }) - storeRegistry.getStore('config').setState(s => { - s.dispatch.dynamic.onFilePickerError = wrapErrors((error: Error) => { + useConfigState.setState(s => { + s.dispatch.defer.onFilePickerError = wrapErrors((error: Error) => { Alert.alert('Error', String(error)) }) - s.dispatch.dynamic.openAppStore = wrapErrors(() => { + s.dispatch.defer.openAppStore = wrapErrors(() => { Linking.openURL( isAndroid ? 'http://play.google.com/store/apps/details?id=io.keybase.ossifrage' @@ -474,7 +382,7 @@ export const initPlatformListener = () => { }) }) - storeRegistry.getStore('profile').setState(s => { + useProfileState.setState(s => { s.dispatch.editAvatar = () => { const f = async () => { try { @@ -486,30 +394,28 @@ export const initPlatformListener = () => { return acc }, undefined) if (!result.canceled && first) { - storeRegistry - .getState('router') + useRouterState + .getState() .dispatch.navigateAppend({props: {image: first}, selected: 'profileEditAvatar'}) } } catch (error) { - storeRegistry.getState('config').dispatch.filePickerError(new Error(String(error))) + useConfigState.getState().dispatch.filePickerError(new Error(String(error))) } } ignorePromise(f()) } }) - storeRegistry.getStore('config').subscribe((s, old) => { + useConfigState.subscribe((s, old) => { if (s.loggedIn === old.loggedIn) return const f = async () => { const {type} = await NetInfo.fetch() - storeRegistry - .getState('config') - .dispatch.osNetworkStatusChanged(type !== NetInfo.NetInfoStateType.none, type, true) + s.dispatch.osNetworkStatusChanged(type !== NetInfo.NetInfoStateType.none, type, true) } ignorePromise(f()) }) - storeRegistry.getStore('config').subscribe((s, old) => { + useConfigState.subscribe((s, old) => { if (s.networkStatus === old.networkStatus) return const type = s.networkStatus?.type if (!type) return @@ -523,8 +429,8 @@ export const initPlatformListener = () => { ignorePromise(f()) }) - storeRegistry.getStore('config').setState(s => { - s.dispatch.dynamic.showShareActionSheet = wrapErrors( + useConfigState.setState(s => { + s.dispatch.defer.showShareActionSheet = wrapErrors( (filePath: string, message: string, mimeType: string) => { const f = async () => { await showShareActionSheet({filePath, message, mimeType}) @@ -534,31 +440,30 @@ export const initPlatformListener = () => { ) }) - storeRegistry.getStore('config').subscribe((s, old) => { + useConfigState.subscribe((s, old) => { if (s.mobileAppState === old.mobileAppState) return if (s.mobileAppState === 'active') { // only reload on foreground - storeRegistry.getState('settings-contacts').dispatch.loadContactPermissions() + useSettingsContactsState.getState().dispatch.loadContactPermissions() } }) // Location if (isAndroid) { - storeRegistry.getStore('dark-mode').subscribe((s, old) => { + useDarkModeState.subscribe((s, old) => { if (s.darkModePreference === old.darkModePreference) return - const {darkModePreference} = storeRegistry.getState('dark-mode') - androidAppColorSchemeChanged(darkModePreference) + androidAppColorSchemeChanged(s.darkModePreference) }) } // we call this when we're logged in. let calledShareListenersRegistered = false - storeRegistry.getStore('router').subscribe((s, old) => { + useRouterState.subscribe((s, old) => { const next = s.navState const prev = old.navState if (next === prev) return - storeRegistry.getState('config').dispatch.dynamic.persistRoute?.(false, false) + useConfigState.getState().dispatch.defer.persistRoute?.(false, false) if (!calledShareListenersRegistered && logState().loggedIn) { calledShareListenersRegistered = true @@ -571,9 +476,7 @@ export const initPlatformListener = () => { initPushListener() NetInfo.addEventListener(({type}) => { - storeRegistry - .getState('config') - .dispatch.osNetworkStatusChanged(type !== NetInfo.NetInfoStateType.none, type) + useConfigState.getState().dispatch.osNetworkStatusChanged(type !== NetInfo.NetInfoStateType.none, type) }) const initAudioModes = () => { @@ -582,14 +485,14 @@ export const initPlatformListener = () => { initAudioModes() if (isAndroid) { - const daemonState = storeRegistry.getState('daemon') + const daemonState = useDaemonState.getState() if (daemonState.handshakeState === 'done' || daemonState.handshakeVersion > 0) { configureAndroidCacheDir() } } - storeRegistry.getStore('config').setState(s => { - s.dispatch.dynamic.openAppSettings = wrapErrors(() => { + useConfigState.setState(s => { + s.dispatch.defer.openAppSettings = wrapErrors(() => { const f = async () => { if (isAndroid) { androidOpenSettings() @@ -605,44 +508,137 @@ export const initPlatformListener = () => { } ignorePromise(f()) }) - - s.dispatch.dynamic.onEngineIncomingNative = wrapErrors((action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.chat1ChatUiTriggerContactSync: - storeRegistry.getState('settings-contacts').dispatch.manageContactsCache() - break - case EngineGen.keybase1LogUiLog: { - const {params} = action.payload - const {level, text} = params - logger.info('keybase.1.logUi.log:', params.text.data) - if (level >= T.RPCGen.LogLevel.error) { - NotifyPopup(text.data) - } - break - } - case EngineGen.chat1ChatUiChatWatchPosition: - ignorePromise(onChatWatchPosition(action)) - break - case EngineGen.chat1ChatUiChatClearWatch: - ignorePromise(onChatClearWatch()) - break - default: - } - }) }) - storeRegistry.getStore('router').setState(s => { - s.dispatch.dynamic.tabLongPress = wrapErrors((tab: string) => { + useRouterState.setState(s => { + s.dispatch.defer.tabLongPress = wrapErrors((tab: string) => { if (tab !== Tabs.peopleTab) return - const accountRows = storeRegistry.getState('config').configuredAccounts - const current = storeRegistry.getState('current-user').username + const accountRows = useConfigState.getState().configuredAccounts + const current = useCurrentUserState.getState().username const row = accountRows.find(a => a.username !== current && a.hasStoredSecret) if (row) { - storeRegistry.getState('config').dispatch.setUserSwitching(true) - storeRegistry.getState('config').dispatch.login(row.username, '') + useConfigState.getState().dispatch.setUserSwitching(true) + useConfigState.getState().dispatch.login(row.username, '') } }) }) - ignorePromise(storeRegistry.getState('fs').dispatch.setupSubscriptions()) + useFSState.setState(s => { + s.dispatch.defer.pickAndUploadMobile = wrapErrors( + (type: T.FS.MobilePickType, parentPath: T.FS.Path) => { + const f = async () => { + try { + const result = await launchImageLibraryAsync(type, true, true) + if (result.canceled) return + result.assets.map(r => + useFSState.getState().dispatch.upload(parentPath, Styles.unnormalizePath(r.uri)) + ) + } catch (e) { + errorToActionOrThrow(e) + } + } + ignorePromise(f()) + } + ) + + s.dispatch.defer.finishedDownloadWithIntentMobile = wrapErrors( + (downloadID: string, downloadIntent: T.FS.DownloadIntent, mimeType: string) => { + const f = async () => { + const {downloads, dispatch} = useFSState.getState() + const downloadState = downloads.state.get(downloadID) || FS.emptyDownloadState + if (downloadState === FS.emptyDownloadState) { + logger.warn('missing download', downloadID) + return + } + const dismissDownload = dispatch.dismissDownload + if (downloadState.error) { + dispatch.redbar(downloadState.error) + dismissDownload(downloadID) + return + } + const {localPath} = downloadState + try { + switch (downloadIntent) { + case T.FS.DownloadIntent.CameraRoll: + await saveAttachmentToCameraRoll(localPath, mimeType) + dismissDownload(downloadID) + return + case T.FS.DownloadIntent.Share: + await showShareActionSheet({filePath: localPath, mimeType}) + dismissDownload(downloadID) + return + case T.FS.DownloadIntent.None: + return + default: + return + } + } catch (err) { + errorToActionOrThrow(err) + } + } + ignorePromise(f()) + } + ) + }) + + if (isAndroid) { + useFSState.setState(s => { + s.dispatch.defer.afterKbfsDaemonRpcStatusChanged = wrapErrors(() => { + const f = async () => { + await T.RPCGen.SimpleFSSimpleFSConfigureDownloadRpcPromise({ + // Android's cache dir is (when I tried) [app]/cache but Go side uses + // [app]/.cache by default, which can't be used for sharing to other apps. + cacheDirOverride: fsCacheDir, + downloadDirOverride: fsDownloadDir, + }) + } + ignorePromise(f()) + }) + // needs to be called, TODO could make this better + s.dispatch.defer.afterKbfsDaemonRpcStatusChanged() + + s.dispatch.defer.finishedRegularDownloadMobile = wrapErrors( + (downloadID: string, mimeType: string) => { + const f = async () => { + // This is fired from a hook and can happen more than once per downloadID. + // So just deduplicate them here. This is small enough and won't happen + // constantly, so don't worry about clearing them. + if (finishedRegularDownloadIDs.has(downloadID)) { + return + } + finishedRegularDownloadIDs.add(downloadID) + + const {downloads} = useFSState.getState() + + const downloadState = downloads.state.get(downloadID) || FS.emptyDownloadState + const downloadInfo = downloads.info.get(downloadID) || FS.emptyDownloadInfo + if (downloadState === FS.emptyDownloadState || downloadInfo === FS.emptyDownloadInfo) { + logger.warn('missing download', downloadID) + return + } + if (downloadState.error) { + return + } + try { + await androidAddCompleteDownload({ + description: `Keybase downloaded ${downloadInfo.filename}`, + mime: mimeType, + path: downloadState.localPath, + showNotification: true, + title: downloadInfo.filename, + }) + } catch { + logger.warn('Failed to addCompleteDownload') + } + // No need to dismiss here as the download wrapper does it for Android. + } + ignorePromise(f()) + } + ) + }) + } + + initSharedSubscriptions() } + +export {onEngineConnected, onEngineDisconnected} from './shared' diff --git a/shared/constants/platform-specific/push.native.tsx b/shared/constants/init/push-listener.native.tsx similarity index 87% rename from shared/constants/platform-specific/push.native.tsx rename to shared/constants/init/push-listener.native.tsx index 5da8dcdb800c..a30a3e2bfe0b 100644 --- a/shared/constants/platform-specific/push.native.tsx +++ b/shared/constants/init/push-listener.native.tsx @@ -1,7 +1,8 @@ -import * as T from '../types' -import {ignorePromise, timeoutPromise} from '../utils' +import * as T from '@/constants/types' +import {ignorePromise, timeoutPromise} from '@/constants/utils' import logger from '@/logger' -import {isAndroid, isIOS} from '../platform' +import {handleAppLink} from '@/constants/deeplinks' +import {isAndroid, isIOS} from '@/constants/platform' import { getRegistrationToken, setApplicationIconBadgeNumber, @@ -9,8 +10,9 @@ import { getInitialNotification, removeAllPendingNotificationRequests, } from 'react-native-kb' -import {storeRegistry} from '../store-registry' -import {DeviceEventEmitter} from 'react-native' +import {useConfigState} from '@/stores/config' +import {useLogoutState} from '@/stores/logout' +import {usePushState} from '@/stores/push' type DataCommon = { userInteraction: boolean @@ -162,7 +164,7 @@ const getStartupDetailsFromInitialPush = async () => { export const initPushListener = () => { // Permissions - storeRegistry.getStore('config').subscribe((s, old) => { + useConfigState.subscribe((s, old) => { if (s.mobileAppState === old.mobileAppState) return // Only recheck on foreground, not background if (s.mobileAppState !== 'active') { @@ -170,21 +172,21 @@ export const initPushListener = () => { return } logger.debug(`[PushCheck] checking on foreground`) - storeRegistry - .getState('push') + usePushState + .getState() .dispatch.checkPermissions() .then(() => {}) .catch(() => {}) }) // Token handling - storeRegistry.getStore('logout').subscribe((s, old) => { + useLogoutState.subscribe((s, old) => { if (s.version === old.version) return - storeRegistry.getState('push').dispatch.deleteToken(s.version) + usePushState.getState().dispatch.deleteToken(s.version) }) let lastCount = -1 - storeRegistry.getStore('config').subscribe((s, old) => { + useConfigState.subscribe((s, old) => { if (s.badgeState === old.badgeState) return if (!s.badgeState) return const count = s.badgeState.bigTeamBadgeCount + s.badgeState.smallTeamBadgeCount @@ -196,7 +198,7 @@ export const initPushListener = () => { lastCount = count }) - storeRegistry.getState('push').dispatch.initialPermissionsCheck() + usePushState.getState().dispatch.initialPermissionsCheck() const listenNative = async () => { const RNEmitter = getNativeEmitter() @@ -210,7 +212,7 @@ export const initPushListener = () => { logger.warn('[onNotification]: normalized notification is null/undefined') return } - storeRegistry.getState('push').dispatch.handlePush(notification) + usePushState.getState().dispatch.handlePush(notification) } try { @@ -224,14 +226,14 @@ export const initPushListener = () => { const token = payload?.token if (token) { logger.debug('[PushToken] received token via onPushToken event: ', token) - storeRegistry.getState('push').dispatch.setPushToken(token) + usePushState.getState().dispatch.setPushToken(token) } }) } if (isAndroid) { - DeviceEventEmitter.addListener('onShareData', (evt: {text?: string; localPaths?: Array}) => { - const {setAndroidShare} = storeRegistry.getState('config').dispatch + RNEmitter.addListener('onShareData', (evt: {text?: string; localPaths?: Array}) => { + const {setAndroidShare} = useConfigState.getState().dispatch const text = evt.text const urls = evt.localPaths @@ -244,7 +246,7 @@ export const initPushListener = () => { return } try { - storeRegistry.getState('deeplinks').dispatch.handleAppLink('keybase://incoming-share') + handleAppLink('keybase://incoming-share') } catch {} }) } @@ -256,7 +258,7 @@ export const initPushListener = () => { try { const pushToken = await getRegistrationToken() logger.debug('[PushToken] received new token: ', pushToken) - storeRegistry.getState('push').dispatch.setPushToken(pushToken) + usePushState.getState().dispatch.setPushToken(pushToken) } catch (e) { logger.warn('[PushToken] failed to get token (will retry later): ', e) // Token will be retrieved later when permissions are checked diff --git a/shared/constants/init/shared.tsx b/shared/constants/init/shared.tsx new file mode 100644 index 000000000000..830d47dd9019 --- /dev/null +++ b/shared/constants/init/shared.tsx @@ -0,0 +1,923 @@ +import * as EngineGen from '@/actions/engine-gen-gen' +import * as T from '../types' +import isEqual from 'lodash/isEqual' +import logger from '@/logger' +import * as Tabs from '@/constants/tabs' +import type * as UseArchiveStateType from '@/stores/archive' +import type * as UseAutoResetStateType from '@/stores/autoreset' +import type * as UseBotsStateType from '@/stores/bots' +import type * as UseChatStateType from '@/stores/chat2' +import type * as UseDevicesStateType from '@/stores/devices' +import type * as UseFSStateType from '@/stores/fs' +import type * as UseGitStateType from '@/stores/git' +import type * as UseNotificationsStateType from '@/stores/notifications' +import type * as UsePeopleStateType from '@/stores/people' +import type * as UsePinentryStateType from '@/stores/pinentry' +import type * as UseSettingsPasswordStateType from '@/stores/settings-password' +import type * as UseSignupStateType from '@/stores/signup' +import type * as UseTeamsStateType from '@/stores/teams' +import type * as UseTracker2StateType from '@/stores/tracker2' +import type * as UseUnlockFoldersStateType from '@/stores/unlock-folders' +import type * as UseUsersStateType from '@/stores/users' +import {createTBStore, getTBStore} from '@/stores/team-building' +import {getSelectedConversation} from '@/constants/chat2/common' +import {handleKeybaseLink} from '@/constants/deeplinks' +import {ignorePromise} from '../utils' +import {isMobile, serverConfigFileName} from '../platform' +import {storeRegistry} from '@/stores/store-registry' +import {useAutoResetState} from '@/stores/autoreset' +import {useAvatarState} from '@/common-adapters/avatar/store' +import {useChatState} from '@/stores/chat2' +import {useConfigState} from '@/stores/config' +import {useCryptoState} from '@/stores/crypto' +import {useCurrentUserState} from '@/stores/current-user' +import {useDaemonState} from '@/stores/daemon' +import {useDarkModeState} from '@/stores/darkmode' +import {useFSState} from '@/stores/fs' +import {useFollowerState} from '@/stores/followers' +import {useNotifState} from '@/stores/notifications' +import {useProfileState} from '@/stores/profile' +import {useProvisionState} from '@/stores/provision' +import {usePushState} from '@/stores/push' +import {useSettingsContactsState} from '@/stores/settings-contacts' +import {useSettingsEmailState} from '@/stores/settings-email' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {useSettingsState} from '@/stores/settings' +import {useSignupState} from '@/stores/signup' +import {useState as useRecoverPasswordState} from '@/stores/recover-password' +import {useTeamsState} from '@/stores/teams' +import {useTrackerState} from '@/stores/tracker2' +import {useUsersState} from '@/stores/users' +import {useWhatsNewState} from '@/stores/whats-new' +import {useRouterState} from '@/stores/router2' +import * as Util from '@/constants/router2' +import {setConvoDefer} from '@/stores/convostate' + +let _emitStartupOnLoadDaemonConnectedOnce = false +let _devicesLoaded = false +let _gitLoaded = false + +export const onEngineConnected = () => { + { + const registerUIs = async () => { + try { + await T.RPCGen.delegateUiCtlRegisterChatUIRpcPromise() + await T.RPCGen.delegateUiCtlRegisterLogUIRpcPromise() + logger.info('Registered Chat UI') + await T.RPCGen.delegateUiCtlRegisterHomeUIRpcPromise() + logger.info('Registered home UI') + await T.RPCGen.delegateUiCtlRegisterSecretUIRpcPromise() + logger.info('Registered secret ui') + await T.RPCGen.delegateUiCtlRegisterIdentify3UIRpcPromise() + logger.info('Registered identify ui') + await T.RPCGen.delegateUiCtlRegisterRekeyUIRpcPromise() + logger.info('Registered rekey ui') + } catch (error) { + logger.error('Error in registering UIs:', error) + } + } + ignorePromise(registerUIs()) + } + useConfigState.getState().dispatch.onEngineConnected() + storeRegistry.getState('daemon').dispatch.startHandshake() + { + const notifyCtl = async () => { + try { + // prettier-ignore + await T.RPCGen.notifyCtlSetNotificationsRpcPromise({ + channels: { + allowChatNotifySkips: true, app: true, audit: true, badges: true, chat: true, chatarchive: true, + chatattachments: true, chatdev: false, chatemoji: false, chatemojicross: false, chatkbfsedits: false, + deviceclone: false, ephemeral: false, favorites: false, featuredBots: true, kbfs: true, kbfsdesktop: !isMobile, + kbfslegacy: false, kbfsrequest: false, kbfssubscription: true, keyfamily: false, notifysimplefs: true, + paperkeys: false, pgp: true, reachability: true, runtimestats: true, saltpack: true, service: true, session: true, + team: true, teambot: false, tracking: true, users: true, wallet: false, + }, + }) + } catch (error) { + if (error) { + logger.warn('error in toggling notifications: ', error) + } + } + } + ignorePromise(notifyCtl()) + } +} + +export const onEngineDisconnected = () => { + const f = async () => { + await logger.dump() + } + ignorePromise(f()) + storeRegistry.getState('daemon').dispatch.setError(new Error('Disconnected')) +} + +// Initialize team building callbacks. Not ideal but keeping all the existing logic for now. +export const initTeamBuildingCallbacks = () => { + const commonCallbacks = { + onAddMembersWizardPushMembers: (members: Array) => { + useTeamsState.getState().dispatch.addMembersWizardPushMembers(members) + }, + onGetSettingsContactsImportEnabled: () => { + return useSettingsContactsState.getState().importEnabled + }, + onGetSettingsContactsUserCountryCode: () => { + return useSettingsContactsState.getState().userCountryCode + }, + onShowUserProfile: (username: string) => { + useProfileState.getState().dispatch.showUserProfile(username) + }, + onUsersGetBlockState: (usernames: ReadonlyArray) => { + useUsersState.getState().dispatch.getBlockState(usernames) + }, + onUsersUpdates: (infos: ReadonlyArray<{name: string; info: Partial}>) => { + useUsersState.getState().dispatch.updates(infos) + }, + } + + const namespaces: Array = ['chat2', 'crypto', 'teams', 'people'] + for (const namespace of namespaces) { + const store = createTBStore(namespace) + const currentState = store.getState() + store.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + ...commonCallbacks, + ...(namespace === 'chat2' + ? { + onFinishedTeamBuildingChat: users => { + storeRegistry.getState('chat').dispatch.onTeamBuildingFinished(users) + }, + } + : {}), + ...(namespace === 'crypto' + ? { + onFinishedTeamBuildingCrypto: users => { + useCryptoState.getState().dispatch.onTeamBuildingFinished(users) + }, + } + : {}), + }, + }, + }) + } +} + +export const initAutoResetCallbacks = () => { + const currentState = useAutoResetState.getState() + useAutoResetState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + onGetRecoverPasswordUsername: () => { + return storeRegistry.getState('recover-password').username + }, + onStartProvision: (username: string, fromReset: boolean) => { + storeRegistry.getState('provision').dispatch.startProvision(username, fromReset) + }, + }, + }, + }) +} + +export const initChat2Callbacks = () => { + const currentState = useChatState.getState() + useChatState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + onGetDaemonState: () => { + const daemonState = storeRegistry.getState('daemon') + return {dispatch: daemonState.dispatch, handshakeVersion: daemonState.handshakeVersion} + }, + onGetTeamsTeamIDToMembers: (teamID: T.Teams.TeamID) => { + return storeRegistry.getState('teams').teamIDToMembers.get(teamID) + }, + onGetUsersInfoMap: () => { + return storeRegistry.getState('users').infoMap + }, + onTeamsGetMembers: (teamID: T.Teams.TeamID) => { + storeRegistry.getState('teams').dispatch.getMembers(teamID) + }, + onTeamsUpdateTeamRetentionPolicy: (metas: ReadonlyArray) => { + storeRegistry.getState('teams').dispatch.updateTeamRetentionPolicy(metas) + }, + onUsersUpdates: (updates: ReadonlyArray<{name: string; info: Partial}>) => { + storeRegistry.getState('users').dispatch.updates(updates) + }, + }, + }, + }) +} + +export const initTeamsCallbacks = () => { + const currentState = useTeamsState.getState() + useTeamsState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + onChatNavigateToInbox: (allowSwitchTab?: boolean) => { + storeRegistry.getState('chat').dispatch.navigateToInbox(allowSwitchTab) + }, + onChatPreviewConversation: ( + p: Parameters['dispatch']['previewConversation']>[0] + ) => { + storeRegistry.getState('chat').dispatch.previewConversation(p) + }, + onUsersUpdates: (updates: ReadonlyArray<{name: string; info: Partial}>) => { + storeRegistry.getState('users').dispatch.updates(updates) + }, + }, + }, + }) +} + +export const initFSCallbacks = () => { + const currentState = useFSState.getState() + useFSState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + onBadgeApp: (key: 'kbfsUploading' | 'outOfSpace', on: boolean) => { + useNotifState.getState().dispatch.badgeApp(key, on) + }, + onSetBadgeCounts: (counts: Map) => { + useNotifState.getState().dispatch.setBadgeCounts(counts) + }, + }, + }, + }) +} + +export const initNotificationsCallbacks = () => { + const currentState = useNotifState.getState() + useNotifState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + onFavoritesLoad: () => { + useFSState.getState().dispatch.favoritesLoad() + }, + }, + }, + }) +} + +export const initProfileCallbacks = () => { + const currentState = useProfileState.getState() + useProfileState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + onTracker2GetDetails: (username: string) => { + return useTrackerState.getState().getDetails(username) + }, + onTracker2Load: ( + params: Parameters['dispatch']['load']>[0] + ) => { + useTrackerState.getState().dispatch.load(params) + }, + onTracker2ShowUser: (username: string, asTracker: boolean, skipNav?: boolean) => { + useTrackerState.getState().dispatch.showUser(username, asTracker, skipNav) + }, + onTracker2UpdateResult: (guiID: string, result: T.Tracker.DetailsState, reason?: string) => { + useTrackerState.getState().dispatch.updateResult(guiID, result, reason) + }, + }, + }, + }) +} + +export const initPushCallbacks = () => { + const currentState = usePushState.getState() + usePushState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + onGetDaemonHandshakeState: () => { + return useDaemonState.getState().handshakeState + }, + onNavigateToThread: ( + conversationIDKey: T.Chat.ConversationIDKey, + reason: 'push' | 'extension', + pushBody?: string + ) => { + storeRegistry + .getConvoState(conversationIDKey) + .dispatch.navigateToThread(reason, undefined, pushBody) + }, + onShowUserProfile: (username: string) => { + useProfileState.getState().dispatch.showUserProfile(username) + }, + }, + }, + }) +} + +export const initRecoverPasswordCallbacks = () => { + const currentState = useRecoverPasswordState.getState() + useRecoverPasswordState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + onProvisionCancel: (ignoreWarning?: boolean) => { + useProvisionState.getState().dispatch.dynamic.cancel?.(ignoreWarning) + }, + onStartAccountReset: (skipPassword: boolean, username: string) => { + useAutoResetState.getState().dispatch.startAccountReset(skipPassword, username) + }, + }, + }, + }) +} + +export const initSignupCallbacks = () => { + const currentState = useSignupState.getState() + useSignupState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + onEditEmail: (p: {email: string; makeSearchable: boolean}) => { + useSettingsEmailState.getState().dispatch.editEmail(p) + }, + onShowPermissionsPrompt: (p: {justSignedUp?: boolean}) => { + usePushState.getState().dispatch.showPermissionsPrompt(p) + }, + }, + }, + }) +} + +export const initTracker2Callbacks = () => { + const currentState = useTrackerState.getState() + useTrackerState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + onShowUserProfile: (username: string) => { + useProfileState.getState().dispatch.showUserProfile(username) + }, + onUsersUpdates: (updates: ReadonlyArray<{name: string; info: Partial}>) => { + useUsersState.getState().dispatch.updates(updates) + }, + }, + }, + }) +} + +export const initSettingsCallbacks = () => { + const currentState = useSettingsState.getState() + useSettingsState.setState({ + dispatch: { + ...currentState.dispatch, + defer: { + ...currentState.dispatch.defer, + getSettingsPhonePhones: () => { + return useSettingsPhoneState.getState().phones + }, + onSettingsEmailNotifyEmailsChanged: (emails: ReadonlyArray) => { + useSettingsEmailState.getState().dispatch.notifyEmailAddressEmailsChanged(emails) + }, + onSettingsPhoneSetNumbers: (phoneNumbers?: ReadonlyArray) => { + useSettingsPhoneState.getState().dispatch.setNumbers(phoneNumbers) + }, + }, + }, + }) +} + +export const initSharedSubscriptions = () => { + setConvoDefer({ + chatBlockButtonsMapHas: teamID => + storeRegistry.getState('chat').blockButtonsMap.has(teamID), + chatInboxLayoutSmallTeamsFirstConvID: () => + storeRegistry.getState('chat').inboxLayout?.smallTeams?.[0]?.convID, + chatInboxRefresh: reason => + storeRegistry.getState('chat').dispatch.inboxRefresh(reason), + chatMetasReceived: metas => + storeRegistry.getState('chat').dispatch.metasReceived(metas), + chatNavigateToInbox: () => + storeRegistry.getState('chat').dispatch.navigateToInbox(), + chatPaymentInfoReceived: (_messageID, paymentInfo) => + storeRegistry.getState('chat').dispatch.paymentInfoReceived(paymentInfo), + chatPreviewConversation: p => + storeRegistry.getState('chat').dispatch.previewConversation(p), + chatResetConversationErrored: () => + storeRegistry.getState('chat').dispatch.resetConversationErrored(), + chatUnboxRows: (convIDs, force) => + storeRegistry.getState('chat').dispatch.unboxRows(convIDs, force), + chatUpdateInfoPanel: (show, tab) => + storeRegistry.getState('chat').dispatch.updateInfoPanel(show, tab), + teamsGetMembers: teamID => + storeRegistry.getState('teams').dispatch.getMembers(teamID), + usersGetBio: username => + storeRegistry.getState('users').dispatch.getBio(username), + }) + useConfigState.subscribe((s, old) => { + if (s.loadOnStartPhase !== old.loadOnStartPhase) { + if (s.loadOnStartPhase === 'startupOrReloginButNotInARush') { + const getFollowerInfo = () => { + const {uid} = useCurrentUserState.getState() + logger.info(`getFollowerInfo: init; uid=${uid}`) + if (uid) { + // request follower info in the background + T.RPCGen.configRequestFollowingAndUnverifiedFollowersRpcPromise() + .then(() => {}) + .catch(() => {}) + } + } + + const updateServerConfig = async () => { + if (s.loggedIn) { + try { + await T.RPCGen.configUpdateLastLoggedInAndServerConfigRpcPromise({ + serverConfigPath: serverConfigFileName, + }) + } catch {} + } + } + + const updateTeams = () => { + useTeamsState.getState().dispatch.getTeams() + useTeamsState.getState().dispatch.refreshTeamRoleMap() + } + + const updateSettings = () => { + useSettingsContactsState.getState().dispatch.loadContactImportEnabled() + } + + const updateChat = async () => { + // On login lets load the untrusted inbox. This helps make some flows easier + if (useCurrentUserState.getState().username) { + const {inboxRefresh} = useChatState.getState().dispatch + inboxRefresh('bootstrap') + } + try { + const rows = await T.RPCGen.configGuiGetValueRpcPromise({path: 'ui.inboxSmallRows'}) + const ri = rows.i ?? -1 + if (ri > 0) { + useChatState.getState().dispatch.setInboxNumSmallRows(ri, true) + } + } catch {} + } + + getFollowerInfo() + ignorePromise(updateServerConfig()) + updateTeams() + updateSettings() + ignorePromise(updateChat()) + } + } + + if (s.gregorReachable !== old.gregorReachable) { + // Re-get info about our account if you log in/we're done handshaking/became reachable + if (s.gregorReachable === T.RPCGen.Reachable.yes) { + // not in waiting state + if (storeRegistry.getState('daemon').handshakeWaiters.size === 0) { + ignorePromise(storeRegistry.getState('daemon').dispatch.loadDaemonBootstrapStatus()) + } + storeRegistry.getState('teams').dispatch.eagerLoadTeams() + } + } + + if (s.installerRanCount !== old.installerRanCount) { + storeRegistry.getState('fs').dispatch.checkKbfsDaemonRpcStatus() + } + + if (s.loggedIn !== old.loggedIn) { + if (s.loggedIn) { + ignorePromise(storeRegistry.getState('daemon').dispatch.loadDaemonBootstrapStatus()) + storeRegistry.getState('fs').dispatch.checkKbfsDaemonRpcStatus() + } + storeRegistry + .getState('daemon') + .dispatch.loadDaemonAccounts( + s.configuredAccounts.length, + s.loggedIn, + useConfigState.getState().dispatch.refreshAccounts + ) + if (!s.loggedInCausedbyStartup) { + ignorePromise(useConfigState.getState().dispatch.refreshAccounts()) + } + } + + if (s.mobileAppState !== old.mobileAppState) { + if (s.mobileAppState === 'background' && storeRegistry.getState('chat').inboxSearch) { + storeRegistry.getState('chat').dispatch.toggleInboxSearch(false) + } + } + + if (s.revokedTrigger !== old.revokedTrigger) { + storeRegistry + .getState('daemon') + .dispatch.loadDaemonAccounts( + s.configuredAccounts.length, + s.loggedIn, + useConfigState.getState().dispatch.refreshAccounts + ) + } + + if (s.configuredAccounts !== old.configuredAccounts) { + const updates = s.configuredAccounts.map(account => ({ + info: {fullname: account.fullname ?? ''}, + name: account.username, + })) + if (updates.length > 0) { + storeRegistry.getState('users').dispatch.updates(updates) + } + } + + if (s.gregorPushState !== old.gregorPushState) { + const lastSeenItem = s.gregorPushState.find(i => i.item.category === 'whatsNewLastSeenVersion') + useWhatsNewState.getState().dispatch.updateLastSeen(lastSeenItem) + } + + if (s.active !== old.active) { + const cs = storeRegistry.getConvoState(getSelectedConversation()) + cs.dispatch.markThreadAsRead() + } + }) + + useDaemonState.subscribe((s, old) => { + if (s.handshakeVersion !== old.handshakeVersion) { + useDarkModeState.getState().dispatch.loadDarkPrefs() + storeRegistry.getState('chat').dispatch.loadStaticConfig() + const configState = useConfigState.getState() + s.dispatch.loadDaemonAccounts( + configState.configuredAccounts.length, + configState.loggedIn, + useConfigState.getState().dispatch.refreshAccounts + ) + } + + if (s.bootstrapStatus !== old.bootstrapStatus) { + const bootstrap = s.bootstrapStatus + if (bootstrap) { + const {deviceID, deviceName, loggedIn, uid, username, userReacjis} = bootstrap + useCurrentUserState.getState().dispatch.setBootstrap({deviceID, deviceName, uid, username}) + + const configDispatch = useConfigState.getState().dispatch + if (username) { + configDispatch.setDefaultUsername(username) + } + if (loggedIn) { + configDispatch.setUserSwitching(false) + } + configDispatch.setLoggedIn(loggedIn, false) + + if (bootstrap.httpSrvInfo) { + configDispatch.setHTTPSrvInfo(bootstrap.httpSrvInfo.address, bootstrap.httpSrvInfo.token) + } + + storeRegistry.getState('chat').dispatch.updateUserReacjis(userReacjis) + } + } + + if (s.handshakeState !== old.handshakeState) { + if (s.handshakeState === 'done') { + if (!_emitStartupOnLoadDaemonConnectedOnce) { + _emitStartupOnLoadDaemonConnectedOnce = true + useConfigState.getState().dispatch.loadOnStart('connectedToDaemonForFirstTime') + } + } + } + }) + + useProvisionState.subscribe((s, old) => { + if (s.startProvisionTrigger !== old.startProvisionTrigger) { + useConfigState.getState().dispatch.setLoginError() + useConfigState.getState().dispatch.resetRevokedSelf() + const f = async () => { + // If we're logged in, we're coming from the user switcher; log out first to prevent the service from getting out of sync with the GUI about our logged-in-ness + if (useConfigState.getState().loggedIn) { + await T.RPCGen.loginLogoutRpcPromise({force: false, keepSecrets: true}, 'config:loginAsOther') + } + } + ignorePromise(f()) + } + }) + + useRouterState.subscribe((s, old) => { + const next = s.navState as Util.NavState + const prev = old.navState as Util.NavState + if (prev === next) return + + const namespaces = ['chat2', 'crypto', 'teams', 'people'] as const + const namespaceToRoute = new Map([ + ['chat2', 'chatNewChat'], + ['crypto', 'cryptoTeamBuilder'], + ['teams', 'teamsTeamBuilder'], + ['people', 'peopleTeamBuilder'], + ]) + for (const namespace of namespaces) { + const wasTeamBuilding = namespaceToRoute.get(namespace) === Util.getVisibleScreen(prev)?.name + if (wasTeamBuilding) { + // team building or modal on top of that still + const isTeamBuilding = namespaceToRoute.get(namespace) === Util.getVisibleScreen(next)?.name + if (!isTeamBuilding) { + getTBStore(namespace).dispatch.cancelTeamBuilding() + } + } + } + + // Clear critical update when we nav away from tab + if ( + prev && + Util.getTab(prev) === Tabs.fsTab && + next && + Util.getTab(next) !== Tabs.fsTab && + storeRegistry.getState('fs').criticalUpdate + ) { + const {dispatch} = storeRegistry.getState('fs') + dispatch.setCriticalUpdate(false) + } + const fsRrouteNames = ['fsRoot', 'barePreview'] + const wasScreen = fsRrouteNames.includes(Util.getVisibleScreen(prev)?.name ?? '') + const isScreen = fsRrouteNames.includes(Util.getVisibleScreen(next)?.name ?? '') + if (wasScreen !== isScreen) { + const {dispatch} = storeRegistry.getState('fs') + if (wasScreen) { + dispatch.userOut() + } else { + dispatch.userIn() + } + } + + // Clear "just signed up email" when you leave the people tab after signup + if ( + prev && + Util.getTab(prev) === Tabs.peopleTab && + next && + Util.getTab(next) !== Tabs.peopleTab && + storeRegistry.getState('signup').justSignedUpEmail + ) { + storeRegistry.getState('signup').dispatch.clearJustSignedUpEmail() + } + + if (prev && Util.getTab(prev) === Tabs.peopleTab && next && Util.getTab(next) !== Tabs.peopleTab) { + storeRegistry.getState('people').dispatch.markViewed() + } + + if (prev && Util.getTab(prev) === Tabs.teamsTab && next && Util.getTab(next) !== Tabs.teamsTab) { + storeRegistry.getState('teams').dispatch.clearNavBadges() + } + + // Clear "check your inbox" in settings when you leave the settings tab + if ( + prev && + Util.getTab(prev) === Tabs.settingsTab && + next && + Util.getTab(next) !== Tabs.settingsTab && + storeRegistry.getState('settings-email').addedEmail + ) { + storeRegistry.getState('settings-email').dispatch.resetAddedEmail() + } + + storeRegistry.getState('chat').dispatch.onRouteChanged(prev, next) + }) + + initAutoResetCallbacks() + initChat2Callbacks() + initTeamBuildingCallbacks() + initTeamsCallbacks() + initFSCallbacks() + initNotificationsCallbacks() + initProfileCallbacks() + initPushCallbacks() + initRecoverPasswordCallbacks() + initSettingsCallbacks() + initSignupCallbacks() + initTracker2Callbacks() +} + +// This is to defer loading stores we don't need immediately. +export const _onEngineIncoming = (action: EngineGen.Actions) => { + switch (action.type) { + case EngineGen.keybase1NotifySimpleFSSimpleFSArchiveStatusChanged: + case EngineGen.chat1NotifyChatChatArchiveComplete: + case EngineGen.chat1NotifyChatChatArchiveProgress: + { + const {useArchiveState} = require('@/stores/archive') as typeof UseArchiveStateType + useArchiveState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1NotifyBadgesBadgeState: + { + const {useAutoResetState} = require('@/stores/autoreset') as typeof UseAutoResetStateType + useAutoResetState.getState().dispatch.onEngineIncomingImpl(action) + + const {badgeState} = action.payload.params + const {newDevices, revokedDevices} = badgeState + const hasValue = (newDevices?.length ?? 0) + (revokedDevices?.length ?? 0) > 0 + if (_devicesLoaded || hasValue) { + _devicesLoaded = true + const {useDevicesState} = require('@/stores/devices') as typeof UseDevicesStateType + useDevicesState.getState().dispatch.onEngineIncomingImpl(action) + } + + const badges = new Set(badgeState.newGitRepoGlobalUniqueIDs) + if (_gitLoaded || badges.size) { + _gitLoaded = true + const {useGitState} = require('@/stores/git') as typeof UseGitStateType + useGitState.getState().dispatch.onEngineIncomingImpl(action) + } + + const {useNotifState} = require('@/stores/notifications') as typeof UseNotificationsStateType + useNotifState.getState().dispatch.onEngineIncomingImpl(action) + + const {useTeamsState} = require('@/stores/teams') as typeof UseTeamsStateType + useTeamsState.getState().dispatch.onEngineIncomingImpl(action) + + const {useChatState} = require('@/stores/chat2') as typeof UseChatStateType + useChatState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.chat1ChatUiChatShowManageChannels: + case EngineGen.keybase1NotifyTeamTeamMetadataUpdate: + case EngineGen.chat1NotifyChatChatWelcomeMessageLoaded: + case EngineGen.keybase1NotifyTeamTeamTreeMembershipsPartial: + case EngineGen.keybase1NotifyTeamTeamTreeMembershipsDone: + case EngineGen.keybase1NotifyTeamTeamRoleMapChanged: + case EngineGen.keybase1NotifyTeamTeamChangedByID: + case EngineGen.keybase1NotifyTeamTeamDeleted: + case EngineGen.keybase1NotifyTeamTeamExit: + case EngineGen.keybase1GregorUIPushState: + { + const {useTeamsState} = require('@/stores/teams') as typeof UseTeamsStateType + useTeamsState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1NotifyFeaturedBotsFeaturedBotsUpdate: + { + const {useBotsState} = require('@/stores/bots') as typeof UseBotsStateType + useBotsState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1NotifyFSFSOverallSyncStatusChanged: + case EngineGen.keybase1NotifyFSFSSubscriptionNotifyPath: + case EngineGen.keybase1NotifyFSFSSubscriptionNotify: + { + const {useFSState} = require('@/stores/fs') as typeof UseFSStateType + useFSState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1NotifyAuditRootAuditError: + case EngineGen.keybase1NotifyAuditBoxAuditError: + { + const {useNotifState} = require('@/stores/notifications') as typeof UseNotificationsStateType + useNotifState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1HomeUIHomeUIRefresh: + case EngineGen.keybase1NotifyEmailAddressEmailAddressVerified: + { + const {usePeopleState} = require('@/stores/people') as typeof UsePeopleStateType + usePeopleState.getState().dispatch.onEngineIncomingImpl(action) + const emailAddress = action.payload.params?.emailAddress + if (emailAddress) { + storeRegistry.getState('settings-email').dispatch.notifyEmailVerified(emailAddress) + } + const {useSignupState} = require('@/stores/signup') as typeof UseSignupStateType + useSignupState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1SecretUiGetPassphrase: + { + const {usePinentryState} = require('@/stores/pinentry') as typeof UsePinentryStateType + usePinentryState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1NotifyUsersPasswordChanged: + { + const randomPW = action.payload.params.state === T.RPCGen.PassphraseState.random + const {usePWState} = require('@/stores/settings-password') as typeof UseSettingsPasswordStateType + usePWState.getState().dispatch.notifyUsersPasswordChanged(randomPW) + } + break + case EngineGen.keybase1NotifyPhoneNumberPhoneNumbersChanged: { + const {list} = action.payload.params + storeRegistry + .getState('settings-phone') + .dispatch.notifyPhoneNumberPhoneNumbersChanged(list ?? undefined) + break + } + case EngineGen.keybase1NotifyEmailAddressEmailsChanged: { + const list = action.payload.params.list ?? [] + storeRegistry.getState('settings-email').dispatch.notifyEmailAddressEmailsChanged(list) + break + } + case EngineGen.chat1ChatUiChatInboxFailed: + case EngineGen.chat1NotifyChatChatSetConvSettings: + case EngineGen.chat1NotifyChatChatAttachmentUploadStart: + case EngineGen.chat1NotifyChatChatPromptUnfurl: + case EngineGen.chat1NotifyChatChatPaymentInfo: + case EngineGen.chat1NotifyChatChatRequestInfo: + case EngineGen.chat1NotifyChatChatAttachmentDownloadProgress: + case EngineGen.chat1NotifyChatChatAttachmentDownloadComplete: + case EngineGen.chat1NotifyChatChatAttachmentUploadProgress: + case EngineGen.chat1ChatUiChatCommandMarkdown: + case EngineGen.chat1ChatUiChatGiphyToggleResultWindow: + case EngineGen.chat1ChatUiChatCommandStatus: + case EngineGen.chat1ChatUiChatBotCommandsUpdateStatus: + case EngineGen.chat1ChatUiChatGiphySearchResults: + case EngineGen.chat1NotifyChatChatParticipantsInfo: + case EngineGen.chat1ChatUiChatMaybeMentionUpdate: + case EngineGen.chat1NotifyChatChatConvUpdate: + case EngineGen.chat1ChatUiChatCoinFlipStatus: + case EngineGen.chat1NotifyChatChatThreadsStale: + case EngineGen.chat1NotifyChatChatSubteamRename: + case EngineGen.chat1NotifyChatChatTLFFinalize: + case EngineGen.chat1NotifyChatChatIdentifyUpdate: + case EngineGen.chat1ChatUiChatInboxUnverified: + case EngineGen.chat1NotifyChatChatInboxSyncStarted: + case EngineGen.chat1NotifyChatChatInboxSynced: + case EngineGen.chat1ChatUiChatInboxLayout: + case EngineGen.chat1NotifyChatChatInboxStale: + case EngineGen.chat1ChatUiChatInboxConversation: + case EngineGen.chat1NotifyChatNewChatActivity: + case EngineGen.chat1NotifyChatChatTypingUpdate: + case EngineGen.chat1NotifyChatChatSetConvRetention: + case EngineGen.chat1NotifyChatChatSetTeamRetention: + { + const {useChatState} = require('@/stores/chat2') as typeof UseChatStateType + useChatState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1NotifyServiceHandleKeybaseLink: + { + const {link, deferred} = action.payload.params + if (deferred && !link.startsWith('keybase://team-invite-link/')) { + return + } + handleKeybaseLink(link) + } + break + case EngineGen.keybase1NotifyTeamAvatarUpdated: { + const {name} = action.payload.params + useAvatarState.getState().dispatch.updated(name) + break + } + case EngineGen.keybase1NotifyTrackingTrackingChanged: { + const {isTracking, username} = action.payload.params + useFollowerState.getState().dispatch.updateFollowing(username, isTracking) + const {useTrackerState} = require('@/stores/tracker2') as typeof UseTracker2StateType + useTrackerState.getState().dispatch.onEngineIncomingImpl(action) + break + } + case EngineGen.keybase1NotifyTrackingTrackingInfo: { + const {uid, followers: _newFollowers, followees: _newFollowing} = action.payload.params + if (useCurrentUserState.getState().uid !== uid) { + break + } + const newFollowers = new Set(_newFollowers) + const newFollowing = new Set(_newFollowing) + const {following: oldFollowing, followers: oldFollowers, dispatch} = useFollowerState.getState() + const following = isEqual(newFollowing, oldFollowing) ? oldFollowing : newFollowing + const followers = isEqual(newFollowers, oldFollowers) ? oldFollowers : newFollowers + dispatch.replace(followers, following) + break + } + case EngineGen.keybase1Identify3UiIdentify3Result: + case EngineGen.keybase1Identify3UiIdentify3ShowTracker: + case EngineGen.keybase1NotifyUsersUserChanged: + case EngineGen.keybase1NotifyTrackingNotifyUserBlocked: + case EngineGen.keybase1Identify3UiIdentify3UpdateRow: + case EngineGen.keybase1Identify3UiIdentify3UserReset: + case EngineGen.keybase1Identify3UiIdentify3UpdateUserCard: + case EngineGen.keybase1Identify3UiIdentify3Summary: + { + const {useTrackerState} = require('@/stores/tracker2') as typeof UseTracker2StateType + useTrackerState.getState().dispatch.onEngineIncomingImpl(action) + } + { + const {useUsersState} = require('@/stores/users') as typeof UseUsersStateType + useUsersState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1NotifyUsersIdentifyUpdate: + { + const {useUsersState} = require('@/stores/users') as typeof UseUsersStateType + useUsersState.getState().dispatch.onEngineIncomingImpl(action) + } + break + case EngineGen.keybase1RekeyUIRefresh: + case EngineGen.keybase1RekeyUIDelegateRekeyUI: + { + const {useUnlockFoldersState} = require('@/stores/unlock-folders') as typeof UseUnlockFoldersStateType + useUnlockFoldersState.getState().dispatch.onEngineIncomingImpl(action) + } + break + default: + } + useConfigState.getState().dispatch.onEngineIncoming(action) +} diff --git a/shared/constants/notifications/util.tsx b/shared/constants/notifications/util.tsx deleted file mode 100644 index acc95a39dbf0..000000000000 --- a/shared/constants/notifications/util.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' -import {isMobile} from '../platform' -import logger from '@/logger' - -export const onEngineConnected = () => { - const f = async () => { - try { - await T.RPCGen.notifyCtlSetNotificationsRpcPromise({ - channels: { - allowChatNotifySkips: true, - app: true, - audit: true, - badges: true, - chat: true, - chatarchive: true, - chatattachments: true, - chatdev: false, - chatemoji: false, - chatemojicross: false, - chatkbfsedits: false, - deviceclone: false, - ephemeral: false, - favorites: false, - featuredBots: true, - kbfs: true, - kbfsdesktop: !isMobile, - kbfslegacy: false, - kbfsrequest: false, - kbfssubscription: true, - keyfamily: false, - notifysimplefs: true, - paperkeys: false, - pgp: true, - reachability: true, - runtimestats: true, - saltpack: true, - service: true, - session: true, - team: true, - teambot: false, - tracking: true, - users: true, - wallet: false, - }, - }) - } catch (error) { - if (error) { - logger.warn('error in toggling notifications: ', error) - } - } - } - ignorePromise(f()) -} - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyAuditRootAuditError: - case EngineGen.keybase1NotifyAuditBoxAuditError: - case EngineGen.keybase1NotifyBadgesBadgeState: - { - storeRegistry.getState('notifications').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/people/util.tsx b/shared/constants/people/util.tsx deleted file mode 100644 index 8deb5593c959..000000000000 --- a/shared/constants/people/util.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -export const onEngineConnected = () => { - const f = async () => { - try { - await T.RPCGen.delegateUiCtlRegisterHomeUIRpcPromise() - console.log('Registered home UI') - } catch (error) { - console.warn('Error in registering home UI:', error) - } - } - ignorePromise(f()) -} - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1HomeUIHomeUIRefresh: - case EngineGen.keybase1NotifyEmailAddressEmailAddressVerified: - { - storeRegistry.getState('people').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/pinentry/util.tsx b/shared/constants/pinentry/util.tsx deleted file mode 100644 index 5ec547f0d1cf..000000000000 --- a/shared/constants/pinentry/util.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' -import logger from '@/logger' - -export const onEngineConnected = () => { - const f = async () => { - try { - await T.RPCGen.delegateUiCtlRegisterSecretUIRpcPromise() - logger.info('Registered secret ui') - } catch (error) { - logger.warn('error in registering secret ui: ', error) - } - } - ignorePromise(f()) -} - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1SecretUiGetPassphrase: - { - storeRegistry.getState('pinentry').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/platform-specific/index.desktop.tsx b/shared/constants/platform-specific/index.desktop.tsx deleted file mode 100644 index b9f8f70f7ade..000000000000 --- a/shared/constants/platform-specific/index.desktop.tsx +++ /dev/null @@ -1,290 +0,0 @@ -import * as Chat from '../chat2' -import {ignorePromise} from '../utils' -import {storeRegistry} from '../store-registry' -import * as ConfigConstants from '../config' -import * as EngineGen from '@/actions/engine-gen-gen' -import * as T from '../types' -import InputMonitor from './input-monitor.desktop' -import KB2 from '@/util/electron.desktop' -import logger from '@/logger' -import type {RPCError} from '@/util/errors' -import {getEngine} from '@/engine' -import {isLinux, isWindows} from '../platform.desktop' -import {kbfsNotification} from './kbfs-notifications' -import {skipAppFocusActions} from '@/local-debug.desktop' -import NotifyPopup from '@/util/notify-popup' -import {noKBFSFailReason} from '@/constants/config/util' -import {wrapErrors} from '@/util/debug' - -const {showMainWindow, activeChanged, requestWindowsStartService, dumpNodeLogger} = KB2.functions -const {quitApp, exitApp, setOpenAtLogin, ctlQuit, copyToClipboard} = KB2.functions - -export const requestPermissionsToWrite = async () => { - return Promise.resolve(true) -} - -export function showShareActionSheet() { - throw new Error('Show Share Action - unsupported on this platform') -} -export async function saveAttachmentToCameraRoll() { - return Promise.reject(new Error('Save Attachment to camera roll - unsupported on this platform')) -} - -export const requestLocationPermission = async () => Promise.resolve() -export const watchPositionForMap = async () => Promise.resolve(() => {}) - -const maybePauseVideos = () => { - const {appFocused} = storeRegistry.getState('config') - const videos = document.querySelectorAll('video') - const allVideos = Array.from(videos) - - allVideos.forEach(v => { - if (appFocused) { - if (v.hasAttribute('data-focus-paused')) { - if (v.paused) { - v.play() - .then(() => {}) - .catch(() => {}) - } - } - } else { - // only pause looping videos - if (!v.paused && v.hasAttribute('loop') && v.hasAttribute('autoplay')) { - v.setAttribute('data-focus-paused', 'true') - v.pause() - } - } - }) -} - -export const dumpLogs = async (reason?: string) => { - await logger.dump() - await (dumpNodeLogger?.() ?? Promise.resolve([])) - // quit as soon as possible - if (reason === 'quitting through menu') { - ctlQuit?.() - } -} - -export const initPlatformListener = () => { - storeRegistry.getStore('config').setState(s => { - s.dispatch.dynamic.dumpLogsNative = dumpLogs - s.dispatch.dynamic.showMainNative = wrapErrors(() => showMainWindow?.()) - s.dispatch.dynamic.copyToClipboard = wrapErrors((s: string) => copyToClipboard?.(s)) - s.dispatch.dynamic.onEngineConnectedDesktop = wrapErrors(() => { - // Introduce ourselves to the service - const f = async () => { - await T.RPCGen.configHelloIAmRpcPromise({details: KB2.constants.helloDetails}) - } - ignorePromise(f()) - }) - - s.dispatch.dynamic.onEngineIncomingDesktop = wrapErrors((action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1LogsendPrepareLogsend: { - const f = async () => { - const response = action.payload.response - try { - await dumpLogs() - } finally { - response.result() - } - } - ignorePromise(f()) - break - } - case EngineGen.keybase1NotifyAppExit: - console.log('App exit requested') - exitApp?.(0) - break - case EngineGen.keybase1NotifyFSFSActivity: - kbfsNotification(action.payload.params.notification, NotifyPopup) - break - case EngineGen.keybase1NotifyPGPPgpKeyInSecretStoreFile: { - const f = async () => { - try { - await T.RPCGen.pgpPgpStorageDismissRpcPromise() - } catch (err) { - console.warn('Error in sending pgpPgpStorageDismissRpc:', err) - } - } - ignorePromise(f()) - break - } - case EngineGen.keybase1NotifyServiceShutdown: { - const {code} = action.payload.params - if (isWindows && code !== (T.RPCGen.ExitCode.restart as number)) { - console.log('Quitting due to service shutdown with code: ', code) - // Quit just the app, not the service - quitApp?.() - } - break - } - - case EngineGen.keybase1LogUiLog: { - const {params} = action.payload - const {level, text} = params - logger.info('keybase.1.logUi.log:', params.text.data) - if (level >= T.RPCGen.LogLevel.error) { - NotifyPopup(text.data) - } - break - } - - case EngineGen.keybase1NotifySessionClientOutOfDate: { - const {upgradeTo, upgradeURI, upgradeMsg} = action.payload.params - const body = upgradeMsg || `Please update to ${upgradeTo} by going to ${upgradeURI}` - NotifyPopup('Client out of date!', {body}, 60 * 60) - // This is from the API server. Consider notifications from server always critical. - storeRegistry - .getState('config') - .dispatch.setOutOfDate({critical: true, message: upgradeMsg, outOfDate: true, updating: false}) - break - } - default: - } - }) - }) - - storeRegistry.getStore('config').subscribe((s, old) => { - if (s.loggedIn === old.loggedIn) return - storeRegistry.getState('config').dispatch.osNetworkStatusChanged(navigator.onLine, 'notavailable', true) - }) - - storeRegistry.getStore('config').subscribe((s, prev) => { - if (s.appFocused !== prev.appFocused) { - maybePauseVideos() - } - }) - - storeRegistry.getStore('daemon').subscribe((s, old) => { - if (s.handshakeVersion === old.handshakeVersion) return - if (!isWindows) return - - const f = async () => { - const waitKey = 'pipeCheckFail' - const version = s.handshakeVersion - const {wait} = storeRegistry.getState('daemon').dispatch - wait(waitKey, version, true) - try { - logger.info('Checking RPC ownership') - if (KB2.functions.winCheckRPCOwnership) { - await KB2.functions.winCheckRPCOwnership() - } - wait(waitKey, version, false) - } catch (error_) { - // error will be logged in bootstrap check - getEngine().reset() - const error = error_ as RPCError - wait(waitKey, version, false, error.message || 'windows pipe owner fail', true) - } - } - ignorePromise(f()) - }) - - const handleWindowFocusEvents = () => { - const handle = (appFocused: boolean) => { - if (skipAppFocusActions) { - console.log('Skipping app focus actions!') - } else { - storeRegistry.getState('config').dispatch.changedFocus(appFocused) - } - } - window.addEventListener('focus', () => handle(true)) - window.addEventListener('blur', () => handle(false)) - } - handleWindowFocusEvents() - - const setupReachabilityWatcher = () => { - window.addEventListener('online', () => - storeRegistry.getState('config').dispatch.osNetworkStatusChanged(true, 'notavailable') - ) - window.addEventListener('offline', () => - storeRegistry.getState('config').dispatch.osNetworkStatusChanged(false, 'notavailable') - ) - } - setupReachabilityWatcher() - - storeRegistry.getStore('config').subscribe((s, old) => { - if (s.openAtLogin === old.openAtLogin) return - const {openAtLogin} = s - const f = async () => { - if (__DEV__) { - console.log('onSetOpenAtLogin disabled for dev mode') - return - } else { - await T.RPCGen.configGuiSetValueRpcPromise({ - path: ConfigConstants.openAtLoginKey, - value: {b: openAtLogin, isNull: false}, - }) - } - if (isLinux || isWindows) { - const enabled = - (await T.RPCGen.ctlGetOnLoginStartupRpcPromise()) === T.RPCGen.OnLoginStartupStatus.enabled - if (enabled !== openAtLogin) { - try { - await T.RPCGen.ctlSetOnLoginStartupRpcPromise({enabled: openAtLogin}) - } catch (error_) { - const error = error_ as RPCError - logger.warn(`Error in sending ctlSetOnLoginStartup: ${error.message}`) - } - } - } else { - logger.info(`Login item settings changed! now ${openAtLogin ? 'on' : 'off'}`) - await setOpenAtLogin?.(openAtLogin) - } - } - ignorePromise(f()) - }) - - storeRegistry.getStore('daemon').subscribe((s, old) => { - if (s.handshakeState === old.handshakeState || s.handshakeState !== 'done') return - storeRegistry.getState('config').dispatch.setStartupDetails({ - conversation: Chat.noConversationIDKey, - followUser: '', - link: '', - tab: undefined, - }) - }) - - if (isLinux) { - storeRegistry.getState('config').dispatch.initUseNativeFrame() - } - storeRegistry.getState('config').dispatch.initNotifySound() - storeRegistry.getState('config').dispatch.initForceSmallNav() - storeRegistry.getState('config').dispatch.initOpenAtLogin() - storeRegistry.getState('config').dispatch.initAppUpdateLoop() - - storeRegistry.getStore('profile').setState(s => { - s.dispatch.editAvatar = () => { - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {image: undefined}, selected: 'profileEditAvatar'}) - } - }) - - const initializeInputMonitor = () => { - const inputMonitor = new InputMonitor() - inputMonitor.notifyActive = (userActive: boolean) => { - if (skipAppFocusActions) { - console.log('Skipping app focus actions!') - } else { - storeRegistry.getState('active').dispatch.setActive(userActive) - // let node thread save file - activeChanged?.(Date.now(), userActive) - } - } - } - initializeInputMonitor() - - storeRegistry.getStore('daemon').setState(s => { - s.dispatch.onRestartHandshakeNative = () => { - const {handshakeFailedReason} = storeRegistry.getState('daemon') - if (isWindows && handshakeFailedReason === noKBFSFailReason) { - requestWindowsStartService?.() - } - } - }) - - ignorePromise(storeRegistry.getState('fs').dispatch.setupSubscriptions()) -} diff --git a/shared/constants/recover-password/utils.tsx b/shared/constants/recover-password/utils.tsx deleted file mode 100644 index 8b137891791f..000000000000 --- a/shared/constants/recover-password/utils.tsx +++ /dev/null @@ -1 +0,0 @@ - diff --git a/shared/constants/router2/util.tsx b/shared/constants/router2.tsx similarity index 98% rename from shared/constants/router2/util.tsx rename to shared/constants/router2.tsx index f47b6a479a5e..5da352fedcb7 100644 --- a/shared/constants/router2/util.tsx +++ b/shared/constants/router2.tsx @@ -1,6 +1,6 @@ import type * as React from 'react' -import type * as T from '../types' -import * as Tabs from '../tabs' +import type * as T from './types' +import * as Tabs from './tabs' import { StackActions, CommonActions, @@ -10,11 +10,11 @@ import { type NavigationState, } from '@react-navigation/core' import type {NavigateAppendType, RouteKeys, RootParamList as KBRootParamList} from '@/router-v2/route-params' -import type {GetOptionsRet} from '../types/router2' +import type {GetOptionsRet} from './types/router2' import {produce} from 'immer' import isEqual from 'lodash/isEqual' -import {isMobile, isTablet} from '../platform' -import {shallowEqual, type ViewPropsToPageProps} from '../utils' +import {isMobile, isTablet} from './platform' +import {shallowEqual, type ViewPropsToPageProps} from './utils' import {registerDebugClear} from '@/util/debug' export const navigationRef = createNavigationContainerRef() @@ -440,4 +440,3 @@ export const appendEncryptRecipientsBuilder = () => { selected: 'cryptoTeamBuilder', }) } - diff --git a/shared/constants/router2/index.tsx b/shared/constants/router2/index.tsx deleted file mode 100644 index 24c72281cad0..000000000000 --- a/shared/constants/router2/index.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import type * as T from '../types' -import * as Z from '@/util/zustand' -import * as Tabs from '../tabs' -import type {RouteKeys} from '@/router-v2/route-params' -import {storeRegistry} from '../store-registry' -import * as Util from './util' - -export { - type NavState, - getTab, - navigationRef, - getRootState, - _getNavigator, - logState, - getVisiblePath, - getModalStack, - getVisibleScreen, - navToProfile, - navToThread, - getRouteTab, - getRouteLoggedIn, - useSafeFocusEffect, - makeScreen, -} from './util' -export type {PathParam, Navigator} from './util' - -type Store = T.Immutable<{ - navState?: unknown -}> - -const initialStore: Store = { - navState: undefined, -} - -export interface State extends Store { - dispatch: { - clearModals: () => void - dynamic: { - tabLongPress?: (tab: string) => void - } - navigateAppend: (path: Util.PathParam, replace?: boolean) => void - navigateUp: () => void - navUpToScreen: (name: RouteKeys) => void - popStack: () => void - resetState: () => void - setNavState: (ns: Util.NavState) => void - switchTab: (tab: Tabs.AppTab) => void - } - appendEncryptRecipientsBuilder: () => void - appendNewChatBuilder: () => void - appendNewTeamBuilder: (teamID: T.Teams.TeamID) => void - appendPeopleBuilder: () => void -} - -export const useRouterState = Z.createZustand((set, get) => { - const dispatch: State['dispatch'] = { - clearModals: Util.clearModals, - dynamic: { - tabLongPress: undefined, - }, - navUpToScreen: Util.navUpToScreen, - navigateAppend: Util.navigateAppend, - navigateUp: Util.navigateUp, - popStack: Util.popStack, - resetState: () => { - set(s => ({ - ...s, - dispatch: s.dispatch, - })) - }, - setNavState: next => { - const DEBUG_NAV = __DEV__ && (false as boolean) - DEBUG_NAV && console.log('[Nav] setNavState') - const prev = get().navState as Util.NavState - if (prev === next) return - set(s => { - s.navState = next - }) - - const updateTeamBuilding = () => { - const namespaces = ['chat2', 'crypto', 'teams', 'people'] as const - const namespaceToRoute = new Map([ - ['chat2', 'chatNewChat'], - ['crypto', 'cryptoTeamBuilder'], - ['teams', 'teamsTeamBuilder'], - ['people', 'peopleTeamBuilder'], - ]) - for (const namespace of namespaces) { - const wasTeamBuilding = namespaceToRoute.get(namespace) === Util.getVisibleScreen(prev)?.name - if (wasTeamBuilding) { - // team building or modal on top of that still - const isTeamBuilding = namespaceToRoute.get(namespace) === Util.getVisibleScreen(next)?.name - if (!isTeamBuilding) { - storeRegistry.getTBStore(namespace).dispatch.cancelTeamBuilding() - } - } - } - } - updateTeamBuilding() - - const updateFS = () => { - // Clear critical update when we nav away from tab - if ( - prev && - Util.getTab(prev) === Tabs.fsTab && - next && - Util.getTab(next) !== Tabs.fsTab && - storeRegistry.getState('fs').criticalUpdate - ) { - const {dispatch} = storeRegistry.getState('fs') - dispatch.setCriticalUpdate(false) - } - const fsRrouteNames = ['fsRoot', 'barePreview'] - const wasScreen = fsRrouteNames.includes(Util.getVisibleScreen(prev)?.name ?? '') - const isScreen = fsRrouteNames.includes(Util.getVisibleScreen(next)?.name ?? '') - if (wasScreen !== isScreen) { - const {dispatch} = storeRegistry.getState('fs') - if (wasScreen) { - dispatch.userOut() - } else { - dispatch.userIn() - } - } - } - updateFS() - - const updateSignup = () => { - // Clear "just signed up email" when you leave the people tab after signup - if ( - prev && - Util.getTab(prev) === Tabs.peopleTab && - next && - Util.getTab(next) !== Tabs.peopleTab && - storeRegistry.getState('signup').justSignedUpEmail - ) { - storeRegistry.getState('signup').dispatch.clearJustSignedUpEmail() - } - } - updateSignup() - - const updatePeople = () => { - if (prev && Util.getTab(prev) === Tabs.peopleTab && next && Util.getTab(next) !== Tabs.peopleTab) { - storeRegistry.getState('people').dispatch.markViewed() - } - } - updatePeople() - - const updateTeams = () => { - if (prev && Util.getTab(prev) === Tabs.teamsTab && next && Util.getTab(next) !== Tabs.teamsTab) { - storeRegistry.getState('teams').dispatch.clearNavBadges() - } - } - updateTeams() - - const updateSettings = () => { - // Clear "check your inbox" in settings when you leave the settings tab - if ( - prev && - Util.getTab(prev) === Tabs.settingsTab && - next && - Util.getTab(next) !== Tabs.settingsTab && - storeRegistry.getState('settings-email').addedEmail - ) { - storeRegistry.getState('settings-email').dispatch.resetAddedEmail() - } - } - updateSettings() - - storeRegistry.getState('chat').dispatch.onRouteChanged(prev, next) - }, - switchTab: Util.switchTab, - } - - return { - ...initialStore, - appendEncryptRecipientsBuilder: Util.appendEncryptRecipientsBuilder, - appendNewChatBuilder: Util.appendNewChatBuilder, - appendNewTeamBuilder: Util.appendNewTeamBuilder, - appendPeopleBuilder: Util.appendPeopleBuilder, - dispatch, - } -}) diff --git a/shared/constants/rpc-utils.tsx b/shared/constants/rpc-utils.tsx index 5969509c5d03..8c5993554391 100644 --- a/shared/constants/rpc-utils.tsx +++ b/shared/constants/rpc-utils.tsx @@ -1,6 +1,12 @@ import * as T from './types' import {uint8ArrayToString} from 'uint8array-extras' -import {type Device} from './provision' + +type Device = { + deviceNumberOfType: number + id: T.Devices.DeviceID + name: string + type: T.Devices.DeviceType +} export const bodyToJSON = (body?: Uint8Array): unknown => { if (!body) return undefined @@ -27,4 +33,3 @@ export const rpcDeviceToDevice = (d: T.RPCGen.Device): Device => { throw new Error('Invalid device type detected: ' + type) } } - diff --git a/shared/constants/settings/util.tsx b/shared/constants/settings.tsx similarity index 78% rename from shared/constants/settings/util.tsx rename to shared/constants/settings.tsx index 281dc0582fd5..b54c44d6f26e 100644 --- a/shared/constants/settings/util.tsx +++ b/shared/constants/settings.tsx @@ -1,6 +1,3 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - export const traceInProgressKey = 'settings:traceInProgress' export const processorProfileInProgressKey = 'settings:processorProfileInProgress' @@ -47,17 +44,3 @@ export type SettingsTab = | typeof settingsCryptoTab | typeof settingsContactsTab | typeof settingsWhatsNewTab - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyEmailAddressEmailAddressVerified: - case EngineGen.keybase1NotifyUsersPasswordChanged: - case EngineGen.keybase1NotifyPhoneNumberPhoneNumbersChanged: - case EngineGen.keybase1NotifyEmailAddressEmailsChanged: - { - storeRegistry.getState('settings').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/signup/util.tsx b/shared/constants/signup/util.tsx deleted file mode 100644 index 7c58483bdb13..000000000000 --- a/shared/constants/signup/util.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyEmailAddressEmailAddressVerified: - { - storeRegistry.getState('signup').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/store-registry.tsx b/shared/constants/store-registry.tsx deleted file mode 100644 index f62f57f25e1f..000000000000 --- a/shared/constants/store-registry.tsx +++ /dev/null @@ -1,353 +0,0 @@ -// used to allow non-circular cross-calls between stores -// ONLY for zustand stores -import type * as T from './types' -import type * as TBType from './team-building' -import type * as ConvoStateType from './chat2/convostate' -import type {ConvoState} from './chat2/convostate' -import type {State as ActiveState, useActiveState} from './active' -import type {State as ArchiveState, useArchiveState} from './archive' -import type {State as AutoResetState, useAutoResetState} from './autoreset' -import type {State as AvatarState, useAvatarState} from '@/common-adapters/avatar/store' -import type {State as BotsState, useBotsState} from './bots' -import type {State as ChatState, useChatState} from './chat2' -import type {State as ConfigState, useConfigState} from './config' -import type {State as CryptoState, useCryptoState} from './crypto' -import type {State as CurrentUserState, useCurrentUserState} from './current-user' -import type {State as DaemonState, useDaemonState} from './daemon' -import type {State as DarkModeState, useDarkModeState} from './darkmode' -import type {State as DeepLinksState, useDeepLinksState} from './deeplinks' -import type {State as DevicesState, useDevicesState} from './devices' -import type {State as EngineState, useEngineState} from './engine' -import type {State as FollowersState, useFollowerState} from './followers' -import type {State as FSState, useFSState} from './fs' -import type {State as GitState, useGitState} from './git' -import type {State as LogoutState, useLogoutState} from './logout' -import type {State as NotificationsState, useNotifState} from './notifications' -import type {State as PeopleState, usePeopleState} from './people' -import type {State as PinentryState, usePinentryState} from './pinentry' -import type {State as ProfileState, useProfileState} from './profile' -import type {State as ProvisionState, useProvisionState} from './provision' -import type {State as PushState, usePushState} from './push' -import type {State as RecoverPasswordState, useState as useRecoverPasswordState} from './recover-password' -import type {State as RouterState, useRouterState} from './router2' -import type {State as SettingsState, useSettingsState} from './settings' -import type {State as SettingsChatState, useSettingsChatState} from './settings-chat' -import type {State as SettingsContactsState, useSettingsContactsState} from './settings-contacts' -import type {State as SettingsEmailState, useSettingsEmailState} from './settings-email' -import type {State as SettingsPasswordState, usePWState} from './settings-password' -import type {State as SettingsPhoneState, useSettingsPhoneState} from './settings-phone' -import type {State as SignupState, useSignupState} from './signup' -import type {State as TeamsState, useTeamsState} from './teams' -import type {State as Tracker2State, useTrackerState} from './tracker2' -import type {State as UnlockFoldersState, useUnlockFoldersState} from './unlock-folders' -import type {State as UsersState, useUsersState} from './users' -import type {State as WaitingState, useWaitingState} from './waiting' -import type {State as WhatsNewState, useWhatsNewState} from './whats-new' - -type StoreName = - | 'active' - | 'archive' - | 'autoreset' - | 'avatar' - | 'bots' - | 'chat' - | 'config' - | 'crypto' - | 'current-user' - | 'daemon' - | 'dark-mode' - | 'deeplinks' - | 'devices' - | 'engine' - | 'followers' - | 'fs' - | 'git' - | 'logout' - | 'notifications' - | 'people' - | 'pinentry' - | 'profile' - | 'provision' - | 'push' - | 'recover-password' - | 'router' - | 'settings' - | 'settings-chat' - | 'settings-contacts' - | 'settings-email' - | 'settings-password' - | 'settings-phone' - | 'signup' - | 'teams' - | 'tracker2' - | 'unlock-folders' - | 'users' - | 'waiting' - | 'whats-new' - -type StoreStates = { - active: ActiveState - archive: ArchiveState - autoreset: AutoResetState - avatar: AvatarState - bots: BotsState - chat: ChatState - config: ConfigState - crypto: CryptoState - 'current-user': CurrentUserState - daemon: DaemonState - 'dark-mode': DarkModeState - deeplinks: DeepLinksState - devices: DevicesState - engine: EngineState - followers: FollowersState - fs: FSState - git: GitState - logout: LogoutState - notifications: NotificationsState - people: PeopleState - pinentry: PinentryState - profile: ProfileState - provision: ProvisionState - push: PushState - 'recover-password': RecoverPasswordState - router: RouterState - settings: SettingsState - 'settings-chat': SettingsChatState - 'settings-contacts': SettingsContactsState - 'settings-email': SettingsEmailState - 'settings-password': SettingsPasswordState - 'settings-phone': SettingsPhoneState - signup: SignupState - teams: TeamsState - tracker2: Tracker2State - 'unlock-folders': UnlockFoldersState - users: UsersState - waiting: WaitingState - 'whats-new': WhatsNewState -} - -type StoreHooks = { - active: typeof useActiveState - archive: typeof useArchiveState - autoreset: typeof useAutoResetState - avatar: typeof useAvatarState - bots: typeof useBotsState - chat: typeof useChatState - config: typeof useConfigState - crypto: typeof useCryptoState - 'current-user': typeof useCurrentUserState - daemon: typeof useDaemonState - 'dark-mode': typeof useDarkModeState - deeplinks: typeof useDeepLinksState - devices: typeof useDevicesState - engine: typeof useEngineState - followers: typeof useFollowerState - fs: typeof useFSState - git: typeof useGitState - logout: typeof useLogoutState - notifications: typeof useNotifState - people: typeof usePeopleState - pinentry: typeof usePinentryState - profile: typeof useProfileState - provision: typeof useProvisionState - push: typeof usePushState - 'recover-password': typeof useRecoverPasswordState - router: typeof useRouterState - settings: typeof useSettingsState - 'settings-chat': typeof useSettingsChatState - 'settings-contacts': typeof useSettingsContactsState - 'settings-email': typeof useSettingsEmailState - 'settings-password': typeof usePWState - 'settings-phone': typeof useSettingsPhoneState - signup: typeof useSignupState - teams: typeof useTeamsState - tracker2: typeof useTrackerState - 'unlock-folders': typeof useUnlockFoldersState - users: typeof useUsersState - waiting: typeof useWaitingState - 'whats-new': typeof useWhatsNewState -} - -class StoreRegistry { - getStore(storeName: T): StoreHooks[T] { - /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return */ - switch (storeName) { - case 'active': { - const {useActiveState} = require('./active') - return useActiveState - } - case 'archive': { - const {useArchiveState} = require('./archive') - return useArchiveState - } - case 'autoreset': { - const {useAutoResetState} = require('./autoreset') - return useAutoResetState - } - case 'avatar': { - const {useAvatarState} = require('@/common-adapters/avatar/store') - return useAvatarState - } - case 'bots': { - const {useBotsState} = require('./bots') - return useBotsState - } - case 'chat': { - const {useChatState} = require('./chat2') - return useChatState - } - case 'config': { - const {useConfigState} = require('./config') - return useConfigState - } - case 'current-user': { - const {useCurrentUserState} = require('./current-user') - return useCurrentUserState - } - case 'crypto': { - const {useCryptoState} = require('./crypto') - return useCryptoState - } - case 'daemon': { - const {useDaemonState} = require('./daemon') - return useDaemonState - } - case 'dark-mode': { - const {useDarkModeState} = require('./darkmode') - return useDarkModeState - } - case 'deeplinks': { - const {useDeepLinksState} = require('./deeplinks') - return useDeepLinksState - } - case 'devices': { - const {useDevicesState} = require('./devices') - return useDevicesState - } - case 'engine': { - const {useEngineState} = require('./engine') - return useEngineState - } - case 'followers': { - const {useFollowerState} = require('./followers') - return useFollowerState - } - case 'fs': { - const {useFSState} = require('./fs') - return useFSState - } - case 'git': { - const {useGitState} = require('./git') - return useGitState - } - case 'logout': { - const {useLogoutState} = require('./logout') - return useLogoutState - } - case 'notifications': { - const {useNotifState} = require('./notifications') - return useNotifState - } - case 'people': { - const {usePeopleState} = require('./people') - return usePeopleState - } - case 'pinentry': { - const {usePinentryState} = require('./pinentry') - return usePinentryState - } - case 'profile': { - const {useProfileState} = require('./profile') - return useProfileState - } - case 'provision': { - const {useProvisionState} = require('./provision') - return useProvisionState - } - case 'push': { - const {usePushState} = require('./push') - return usePushState - } - case 'recover-password': { - const {useState} = require('./recover-password') - return useState - } - case 'router': { - const {useRouterState} = require('./router2') - return useRouterState - } - case 'settings': { - const {useSettingsState} = require('./settings') - return useSettingsState - } - case 'settings-chat': { - const {useSettingsChatState} = require('./settings-chat') - return useSettingsChatState - } - case 'settings-contacts': { - const {useSettingsContactsState} = require('./settings-contacts') - return useSettingsContactsState - } - case 'settings-email': { - const {useSettingsEmailState} = require('./settings-email') - return useSettingsEmailState - } - case 'settings-password': { - const {usePWState} = require('./settings-password') - return usePWState - } - case 'settings-phone': { - const {useSettingsPhoneState} = require('./settings-phone') - return useSettingsPhoneState - } - case 'signup': { - const {useSignupState} = require('./signup') - return useSignupState - } - case 'teams': { - const {useTeamsState} = require('./teams') - return useTeamsState - } - case 'tracker2': { - const {useTrackerState} = require('./tracker2') - return useTrackerState - } - case 'unlock-folders': { - const {useUnlockFoldersState} = require('./unlock-folders') - return useUnlockFoldersState - } - case 'users': { - const {useUsersState} = require('./users') - return useUsersState - } - case 'waiting': { - const {useWaitingState} = require('./waiting') - return useWaitingState - } - case 'whats-new': { - const {useWhatsNewState} = require('./whats-new') - return useWhatsNewState - } - default: - throw new Error(`Unknown store: ${storeName}`) - } - } - - getState(storeName: T): StoreStates[T] { - return this.getStore(storeName).getState() as StoreStates[T] - } - - getTBStore(name: T.TB.AllowedNamespace): TBType.State { - const {createTBStore} = require('./team-building') as typeof TBType - const store = createTBStore(name) - return store.getState() - } - - getConvoState(id: T.Chat.ConversationIDKey): ConvoState { - const {getConvoState} = require('./chat2/convostate') as typeof ConvoStateType - return getConvoState(id) - } -} - -export const storeRegistry = new StoreRegistry() diff --git a/shared/constants/strings.tsx b/shared/constants/strings.tsx index 5fe715a7795c..cceff363d6c6 100644 --- a/shared/constants/strings.tsx +++ b/shared/constants/strings.tsx @@ -44,6 +44,8 @@ export const waitingKeyRecoverPassword = 'recover-password:waiting' export const waitingKeyCrypto = 'cryptoWaiting' +export const searchWaitingKey = 'teamBuilding:search' + export const waitingKeyTeamsLoaded = 'teams:loaded' export const waitingKeyTeamsJoinTeam = 'teams:joinTeam' export const waitingKeyTeamsTeam = (teamID: T.Teams.TeamID) => `team:${teamID}` @@ -96,6 +98,13 @@ export const waitingKeyFSStat = 'fs:stat' export const waitingKeyFSCommitEdit = 'fs:commitEditWaitingKey' export const waitingKeyFSSetSyncOnCellular = 'fs:setSyncOnCellular' +export const loadAccountsWaitingKey = 'wallets:loadAccounts' + +export const currentVersion: string = '5.5.0' +export const lastVersion: string = '5.4.0' +export const lastLastVersion: string = '5.3.0' +export const keybaseFM = 'Keybase FM 87.7' + export const waitingKeyGitLoading = 'git:loading' export const waitingKeyUsersGetUserBlocks = 'users:getUserBlocks' diff --git a/shared/constants/team-building/utils.tsx b/shared/constants/team-building.tsx similarity index 82% rename from shared/constants/team-building/utils.tsx rename to shared/constants/team-building.tsx index e277db5d587f..24e3c9d457e7 100644 --- a/shared/constants/team-building/utils.tsx +++ b/shared/constants/team-building.tsx @@ -1,4 +1,4 @@ -import type * as T from '../types' +import type * as T from './types' const searchServices: Array = ['keybase', 'twitter', 'github', 'reddit', 'hackernews'] @@ -16,5 +16,3 @@ export const selfToUser = (you: string): T.TB.User => ({ serviceMap: {}, username: you, }) - -export const searchWaitingKey = 'teamBuilding:search' diff --git a/shared/constants/teams/util.tsx b/shared/constants/teams.tsx similarity index 82% rename from shared/constants/teams/util.tsx rename to shared/constants/teams.tsx index 48167c472d34..5eb4fbea86ff 100644 --- a/shared/constants/teams/util.tsx +++ b/shared/constants/teams.tsx @@ -1,29 +1,6 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import * as T from '../types' -import {storeRegistry} from '../store-registry' +import * as T from './types' import invert from 'lodash/invert' -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.chat1ChatUiChatShowManageChannels: - case EngineGen.keybase1NotifyTeamTeamMetadataUpdate: - case EngineGen.chat1NotifyChatChatWelcomeMessageLoaded: - case EngineGen.keybase1NotifyTeamTeamTreeMembershipsPartial: - case EngineGen.keybase1NotifyTeamTeamTreeMembershipsDone: - case EngineGen.keybase1NotifyTeamTeamRoleMapChanged: - case EngineGen.keybase1NotifyTeamTeamChangedByID: - case EngineGen.keybase1NotifyTeamTeamDeleted: - case EngineGen.keybase1NotifyTeamTeamExit: - case EngineGen.keybase1NotifyBadgesBadgeState: - case EngineGen.keybase1GregorUIPushState: - { - storeRegistry.getState('teams').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} - export const makeRetentionPolicy = ( r?: Partial ): T.Retention.RetentionPolicy => ({ diff --git a/shared/constants/tracker2/util.tsx b/shared/constants/tracker2/util.tsx deleted file mode 100644 index e6adb67ec4c6..000000000000 --- a/shared/constants/tracker2/util.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' -import logger from '@/logger' - -export const onEngineConnected = () => { - const f = async () => { - try { - await T.RPCGen.delegateUiCtlRegisterIdentify3UIRpcPromise() - logger.info('Registered identify ui') - } catch (error) { - logger.warn('error in registering identify ui: ', error) - } - } - ignorePromise(f()) -} - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyTrackingTrackingChanged: - case EngineGen.keybase1Identify3UiIdentify3Result: - case EngineGen.keybase1Identify3UiIdentify3ShowTracker: - case EngineGen.keybase1NotifyUsersUserChanged: - case EngineGen.keybase1NotifyTrackingNotifyUserBlocked: - case EngineGen.keybase1Identify3UiIdentify3UpdateRow: - case EngineGen.keybase1Identify3UiIdentify3UserReset: - case EngineGen.keybase1Identify3UiIdentify3UpdateUserCard: - case EngineGen.keybase1Identify3UiIdentify3Summary: - { - storeRegistry.getState('tracker2').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/types/chat2/index.tsx b/shared/constants/types/chat2/index.tsx index 212ae677e11b..9742dd2548ca 100644 --- a/shared/constants/types/chat2/index.tsx +++ b/shared/constants/types/chat2/index.tsx @@ -5,11 +5,6 @@ import * as _Message from './message' import type * as Meta from './meta' import {uint8ArrayToHex, hexToUint8Array} from 'uint8array-extras' -export type PaymentConfirmInfo = { - error?: RPCTypes.Status - summary?: T.RPCChat.UIChatPaymentSummary -} - // Static config data we use for various things export type StaticConfig = { deletableByDeleteHistory: Set<_Message.MessageType> @@ -86,11 +81,6 @@ export type AttachmentViewInfo = { last: boolean } -export type AttachmentFullscreenSelection = { - autoPlay: boolean - message: _Message.Message -} - export type CommandStatusInfo = { displayText: string displayType: T.RPCChat.UICommandStatusDisplayTyp @@ -208,4 +198,3 @@ export const outboxIDToRpcOutboxID = (outboxID: _Message.OutboxID): T.RPCChat.Ou export * from './message' export * from './common' export type * from './meta' -export type * from './rowitem' diff --git a/shared/constants/types/chat2/message.tsx b/shared/constants/types/chat2/message.tsx index f7b7d1332f81..16a90713c8bb 100644 --- a/shared/constants/types/chat2/message.tsx +++ b/shared/constants/types/chat2/message.tsx @@ -51,11 +51,6 @@ export const outboxIDToString = (o: OutboxID): string => o export type MentionsAt = ReadonlySet export type MentionsChannel = 'none' | 'all' | 'here' -export interface MessageExplodeDescription { - text: string - seconds: number -} - export interface PathAndOutboxID { path: string outboxID?: T.RPCChat.OutboxID diff --git a/shared/constants/types/config.tsx b/shared/constants/types/config.tsx index e001a72201e9..837ec64ebb5e 100644 --- a/shared/constants/types/config.tsx +++ b/shared/constants/types/config.tsx @@ -1,5 +1,3 @@ -import type * as NetInfo from '@react-native-community/netinfo' - export type OutOfDate = { critical: boolean message: string @@ -8,8 +6,7 @@ export type OutOfDate = { } export type DaemonHandshakeState = 'starting' | 'waitingForWaiters' | 'done' export type ConfiguredAccount = { + fullname?: string hasStoredSecret: boolean username: string } -// 'notavailable' is the desktop default -export type ConnectionType = NetInfo.NetInfoStateType | 'notavailable' diff --git a/shared/constants/types/crypto.tsx b/shared/constants/types/crypto.tsx index db2f4c451ffb..ad568add5e73 100644 --- a/shared/constants/types/crypto.tsx +++ b/shared/constants/types/crypto.tsx @@ -1,4 +1,2 @@ -export type TextType = 'cipher' | 'plain' export type Operations = 'encrypt' | 'decrypt' | 'sign' | 'verify' export type InputTypes = 'text' | 'file' -export type OutputType = 'text' | 'file' diff --git a/shared/constants/types/git.tsx b/shared/constants/types/git.tsx index 3a0e54f885d1..c960905a7cdf 100644 --- a/shared/constants/types/git.tsx +++ b/shared/constants/types/git.tsx @@ -1,4 +1,3 @@ -import type * as T from '@/constants/types' export type GitInfo = { canDelete: boolean channelName?: string @@ -12,9 +11,3 @@ export type GitInfo = { teamname?: string url: string } - -export type State = T.Immutable<{ - readonly error?: Error - readonly idToInfo: Map - readonly isNew?: Set -}> diff --git a/shared/constants/types/push.tsx b/shared/constants/types/push.tsx index 8b35307b1543..ea0e33b9a4cc 100644 --- a/shared/constants/types/push.tsx +++ b/shared/constants/types/push.tsx @@ -1,8 +1,6 @@ import type * as ChatTypes from './chat2' import type * as RPCChatTypes from './rpc-chat-gen' -export type TokenType = 'apple' | 'appledev' | 'androidplay' - export type PushNotification = | { badges: number diff --git a/shared/constants/types/wallets.tsx b/shared/constants/types/wallets.tsx index 0be9a9ab413d..4c4b5488b782 100644 --- a/shared/constants/types/wallets.tsx +++ b/shared/constants/types/wallets.tsx @@ -1,10 +1,5 @@ import type * as StellarRPCTypes from './rpc-stellar-gen' -export type Reserve = { - amount: string - description: string // e.g. 'account' or 'KEYZ/keybase.io trust line' -} - export type AccountID = string export const noAccountID = 'NOACCOUNTID' export type PaymentID = StellarRPCTypes.PaymentID diff --git a/shared/constants/unlock-folders/util.tsx b/shared/constants/unlock-folders/util.tsx deleted file mode 100644 index 89d12305fd44..000000000000 --- a/shared/constants/unlock-folders/util.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' -import logger from '@/logger' - -export const onEngineConnected = () => { - const f = async () => { - try { - await T.RPCGen.delegateUiCtlRegisterRekeyUIRpcPromise() - logger.info('Registered rekey ui') - } catch (error) { - logger.warn('error in registering rekey ui: ') - logger.debug('error in registering rekey ui: ', error) - } - } - ignorePromise(f()) -} - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1RekeyUIRefresh: - case EngineGen.keybase1RekeyUIDelegateRekeyUI: - { - storeRegistry.getState('unlock-folders').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/users/util.tsx b/shared/constants/users/util.tsx deleted file mode 100644 index cd70c647ccb5..000000000000 --- a/shared/constants/users/util.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import * as EngineGen from '@/actions/engine-gen-gen' -import {storeRegistry} from '../store-registry' - -export const onEngineIncoming = (action: EngineGen.Actions) => { - switch (action.type) { - case EngineGen.keybase1NotifyUsersIdentifyUpdate: - case EngineGen.keybase1NotifyTrackingNotifyUserBlocked: - { - storeRegistry.getState('users').dispatch.onEngineIncomingImpl(action) - } - break - default: - } -} diff --git a/shared/constants/values.tsx b/shared/constants/values.tsx index ddf79f1e2170..31ec39ad5cf5 100644 --- a/shared/constants/values.tsx +++ b/shared/constants/values.tsx @@ -1,2 +1,11 @@ export const maxHandshakeTries = 3 export const maxUsernameLength = 16 + +// Exit Codes +export const ExitCodeFuseKextError = 4 +export const ExitCodeFuseKextPermissionError = 5 +export const ExitCodeAuthCanceledError = 6 +// See Installer.m: KBExitFuseCriticalUpdate +export const ExitFuseCriticalUpdate = 8 +// See install_darwin.go: exitCodeFuseCriticalUpdateFailed +export const ExitFuseCriticalUpdateFailed = 300 diff --git a/shared/constants/wallets/utils.tsx b/shared/constants/wallets/utils.tsx deleted file mode 100644 index 6754f164b02d..000000000000 --- a/shared/constants/wallets/utils.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export const loadAccountsWaitingKey = 'wallets:loadAccounts' - diff --git a/shared/constants/whats-new/index.tsx b/shared/constants/whats-new/index.tsx deleted file mode 100644 index 526d9e4473d7..000000000000 --- a/shared/constants/whats-new/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import type * as T from '../types' -import * as Z from '@/util/zustand' -import {uint8ArrayToString} from 'uint8array-extras' -import {noVersion, getSeenVersions} from './utils' - -export {currentVersion, lastVersion, lastLastVersion, keybaseFM} from './utils' - -type SeenVersionsMap = {[key in string]: boolean} - -type Store = T.Immutable<{ - lastSeenVersion: string - seenVersions: SeenVersionsMap -}> -const initialStore: Store = { - lastSeenVersion: '', - seenVersions: getSeenVersions(''), -} -export interface State extends Store { - dispatch: { - resetState: 'default' - updateLastSeen: (lastSeenItem?: {md: T.RPCGen.Gregor1.Metadata; item: T.RPCGen.Gregor1.Item}) => void - } - anyVersionsUnseen: () => boolean -} -export const useWhatsNewState = Z.createZustand((set, get) => { - const dispatch: State['dispatch'] = { - resetState: 'default', - updateLastSeen: lastSeenItem => { - if (lastSeenItem) { - const {body} = lastSeenItem.item - const pushStateLastSeenVersion = uint8ArrayToString(body) - const lastSeenVersion = pushStateLastSeenVersion || noVersion - // Default to 0.0.0 (noVersion) if user has never marked a version as seen - set(s => { - s.lastSeenVersion = lastSeenVersion - s.seenVersions = getSeenVersions(lastSeenVersion) - }) - } else { - set(s => { - s.lastSeenVersion = noVersion - s.seenVersions = getSeenVersions(noVersion) - }) - } - }, - } - return { - ...initialStore, - anyVersionsUnseen: () => { - const {lastSeenVersion: ver} = get() - // On first load of what's new, lastSeenVersion == noVersion so everything is unseen - return ver !== '' && ver === noVersion ? true : Object.values(getSeenVersions(ver)).some(seen => !seen) - }, - dispatch, - } -}) diff --git a/shared/constants/whats-new/utils.tsx b/shared/constants/whats-new/utils.tsx deleted file mode 100644 index bc3a3e93f98e..000000000000 --- a/shared/constants/whats-new/utils.tsx +++ /dev/null @@ -1,69 +0,0 @@ -const semver = { - gte: (a: string, b: string) => { - const arra = a.split('.').map(i => parseInt(i)) - const [a1, a2, a3] = arra - const arrb = b.split('.').map(i => parseInt(i)) - const [b1, b2, b3] = arrb - if (arra.length === 3 && arrb.length === 3) { - return a1! >= b1! && a2! >= b2! && a3! >= b3! - } else { - return false - } - }, - valid: (v: string) => - v.split('.').reduce((cnt, i) => { - if (parseInt(i) >= 0) { - return cnt + 1 - } - return cnt - }, 0) === 3, -} - -const noVersion: string = '0.0.0' -export const currentVersion: string = '5.5.0' -export const lastVersion: string = '5.4.0' -export const lastLastVersion: string = '5.3.0' -const versions = [currentVersion, lastVersion, lastLastVersion, noVersion] as const -export const keybaseFM = 'Keybase FM 87.7' - -type SeenVersionsMap = {[key in string]: boolean} - -const isVersionValid = (version: string) => { - return version ? semver.valid(version) : false -} - -export const getSeenVersions = (lastSeenVersion: string): SeenVersionsMap => { - const initialMap: SeenVersionsMap = { - [currentVersion]: true, - [lastLastVersion]: true, - [lastVersion]: true, - [noVersion]: true, - } - - if (!lastSeenVersion || !semver.valid(lastSeenVersion)) { - return initialMap - } - if (lastSeenVersion === noVersion) { - return { - [currentVersion]: false, - [lastLastVersion]: false, - [lastVersion]: false, - [noVersion]: false, - } - } - - const validVersions = versions.filter(isVersionValid) - - const seenVersions = validVersions.reduce( - (acc, version) => ({ - ...acc, - [version]: version === noVersion ? true : semver.gte(lastSeenVersion, version), - }), - initialMap - ) - - return seenVersions -} - -export {noVersion} - diff --git a/shared/crypto/input.tsx b/shared/crypto/input.tsx index 575de4122740..273288164961 100644 --- a/shared/crypto/input.tsx +++ b/shared/crypto/input.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import * as React from 'react' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' diff --git a/shared/crypto/operations/decrypt.tsx b/shared/crypto/operations/decrypt.tsx index 4f553f9e7b06..61060317259c 100644 --- a/shared/crypto/operations/decrypt.tsx +++ b/shared/crypto/operations/decrypt.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import * as Kb from '@/common-adapters' import * as React from 'react' import {Input, DragAndDrop, InputActionsBar, OperationBanner} from '../input' diff --git a/shared/crypto/operations/encrypt.tsx b/shared/crypto/operations/encrypt.tsx index 856c4b40cb0c..7be3f776cec8 100644 --- a/shared/crypto/operations/encrypt.tsx +++ b/shared/crypto/operations/encrypt.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import * as Kb from '@/common-adapters' import * as React from 'react' import Recipients from '../recipients' diff --git a/shared/crypto/operations/sign.tsx b/shared/crypto/operations/sign.tsx index 5ec68a46802e..7b0267c7f2a5 100644 --- a/shared/crypto/operations/sign.tsx +++ b/shared/crypto/operations/sign.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import * as React from 'react' import * as Kb from '@/common-adapters' import openURL from '@/util/open-url' diff --git a/shared/crypto/operations/verify.tsx b/shared/crypto/operations/verify.tsx index 3b64988b45a3..56decc162d9d 100644 --- a/shared/crypto/operations/verify.tsx +++ b/shared/crypto/operations/verify.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import * as Kb from '@/common-adapters' import * as React from 'react' import {Input, InputActionsBar, DragAndDrop, OperationBanner} from '../input' diff --git a/shared/crypto/output.tsx b/shared/crypto/output.tsx index d349ae39efe5..6510f136f84b 100644 --- a/shared/crypto/output.tsx +++ b/shared/crypto/output.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Crypto from '@/constants/crypto' +import * as Chat from '@/stores/chat2' +import * as Crypto from '@/stores/crypto' import * as Kb from '@/common-adapters' import * as Path from '@/util/path' import * as React from 'react' @@ -8,9 +8,9 @@ import capitalize from 'lodash/capitalize' import type * as T from '@/constants/types' import {pickFiles} from '@/util/pick-files' import type HiddenString from '@/util/hidden-string' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' import * as FS from '@/constants/fs' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' type OutputProps = {operation: T.Crypto.Operations} type OutputActionsBarProps = {operation: T.Crypto.Operations} @@ -181,7 +181,7 @@ export const OutputActionsBar = (props: OutputActionsBarProps) => { const actionsDisabled = waiting || !outputValid const openLocalPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop + s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const onShowInFinder = () => { openLocalPathInSystemFileManagerDesktop?.(output.stringValue()) @@ -194,7 +194,7 @@ export const OutputActionsBar = (props: OutputActionsBarProps) => { previewConversation({participants: [username.stringValue()], reason: 'search'}) } - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) const onCopyOutput = () => { copyToClipboard(output.stringValue()) } @@ -369,7 +369,7 @@ export const OperationOutput = (props: OutputProps) => { const output = _output.stringValue() const openLocalPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop + s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const onShowInFinder = () => { if (!output) return diff --git a/shared/crypto/recipients.tsx b/shared/crypto/recipients.tsx index a4ad222b2a2d..a7ac327df09f 100644 --- a/shared/crypto/recipients.tsx +++ b/shared/crypto/recipients.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import * as Kb from '@/common-adapters' const placeholder = 'Search people' diff --git a/shared/crypto/routes.tsx b/shared/crypto/routes.tsx index da93e894f5bb..14200ecfdaf5 100644 --- a/shared/crypto/routes.tsx +++ b/shared/crypto/routes.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as C from '@/constants' -import * as Crypto from '@/constants/crypto/util' +import * as Crypto from '@/constants/crypto' import {HeaderLeftCancel2, type HeaderBackButtonProps} from '@/common-adapters/header-hoc' import cryptoTeamBuilder from '../team-building/page' diff --git a/shared/crypto/sub-nav/index.desktop.tsx b/shared/crypto/sub-nav/index.desktop.tsx index 7cd6cdfdd883..47e4b97a6486 100644 --- a/shared/crypto/sub-nav/index.desktop.tsx +++ b/shared/crypto/sub-nav/index.desktop.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as Kb from '@/common-adapters' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import * as Common from '@/router-v2/common.desktop' import LeftNav from './left-nav.desktop' import { diff --git a/shared/crypto/sub-nav/index.native.tsx b/shared/crypto/sub-nav/index.native.tsx index c41261148096..f2ee824bec3b 100644 --- a/shared/crypto/sub-nav/index.native.tsx +++ b/shared/crypto/sub-nav/index.native.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import * as Kb from '@/common-adapters' import NavRow from './nav-row' diff --git a/shared/crypto/sub-nav/left-nav.desktop.tsx b/shared/crypto/sub-nav/left-nav.desktop.tsx index e44a26459acd..5dc1b78e412a 100644 --- a/shared/crypto/sub-nav/left-nav.desktop.tsx +++ b/shared/crypto/sub-nav/left-nav.desktop.tsx @@ -1,6 +1,6 @@ import type * as React from 'react' import * as Kb from '@/common-adapters' -import * as Crypto from '@/constants/crypto' +import * as Crypto from '@/stores/crypto' import NavRow from './nav-row' type Row = (typeof Crypto.Tabs)[number] & { diff --git a/shared/deeplinks/error.tsx b/shared/deeplinks/error.tsx index ed8a9cda94c5..6450b1b88b1f 100644 --- a/shared/deeplinks/error.tsx +++ b/shared/deeplinks/error.tsx @@ -1,6 +1,5 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import {useDeepLinksState} from '@/constants/deeplinks' type KeybaseLinkErrorBodyProps = { message: string @@ -21,9 +20,9 @@ export const KeybaseLinkErrorBody = (props: KeybaseLinkErrorBodyProps) => { ) } -const KeybaseLinkError = () => { - const deepError = useDeepLinksState(s => s.keybaseLinkError) - const message = deepError +const LinkError = (props: {error?: string}) => { + const error = props.error ?? 'Invalid page! (sorry)' + const message = error const isError = true const navigateUp = C.useRouterState(s => s.dispatch.navigateUp) const onClose = () => navigateUp() @@ -48,4 +47,6 @@ const styles = Kb.Styles.styleSheetCreate(() => ({ }), })) -export default KeybaseLinkError +type OwnProps = C.ViewPropsToPageProps +const Screen = (p: OwnProps) => +export default Screen diff --git a/shared/desktop/app/installer.desktop.tsx b/shared/desktop/app/installer.desktop.tsx index 3d6306fcbc41..3d4b50dd5e73 100644 --- a/shared/desktop/app/installer.desktop.tsx +++ b/shared/desktop/app/installer.desktop.tsx @@ -9,6 +9,13 @@ import {ctlQuit} from './ctl.desktop' import {isDarwin} from '@/constants/platform' import logger from '@/logger' import zlib from 'zlib' +import { + ExitCodeAuthCanceledError, + ExitCodeFuseKextError, + ExitCodeFuseKextPermissionError, + ExitFuseCriticalUpdate, + ExitFuseCriticalUpdateFailed, +} from '@/constants/values' const file = path.join(Electron.app.getPath('userData'), 'installer.json') @@ -56,18 +63,6 @@ type ResultType = | undefined const checkErrors = (result: ResultType, errors: Array, errorTypes: ErrorTypes) => { - // Copied from old constants/favorite.js - // See Installer.m: KBExitFuseKextError - const ExitCodeFuseKextError = 4 - // See Installer.m: KBExitFuseKextPermissionError - const ExitCodeFuseKextPermissionError = 5 - // See Installer.m: KBExitAuthCanceledError - const ExitCodeAuthCanceledError = 6 - // See Installer.m: KBExitFuseCriticalUpdate - const ExitFuseCriticalUpdate = 8 - // See install_darwin.go: exitCodeFuseCriticalUpdateFailed - const ExitFuseCriticalUpdateFailed = 300 - const results = result?.componentResults || [] results.forEach(cr => { if (cr.status?.code === 0) { diff --git a/shared/desktop/app/menu-bar.desktop.tsx b/shared/desktop/app/menu-bar.desktop.tsx index cbc76a7e902f..bca0da6b42de 100644 --- a/shared/desktop/app/menu-bar.desktop.tsx +++ b/shared/desktop/app/menu-bar.desktop.tsx @@ -8,7 +8,7 @@ import {menubar} from 'menubar' import {showDevTools, skipSecondaryDevtools} from '@/local-debug' import {getMainWindow} from './main-window.desktop' import {assetRoot, htmlPrefix} from './html-root.desktop' -import type {BadgeType} from '@/constants/notifications' +import type {BadgeType} from '@/stores/notifications' const getIcons = (iconType: BadgeType, badges: number) => { const size = isWindows ? 16 : 22 diff --git a/shared/desktop/app/node2.desktop.tsx b/shared/desktop/app/node2.desktop.tsx index 7d3b6c8db307..6b65e04f06ce 100644 --- a/shared/desktop/app/node2.desktop.tsx +++ b/shared/desktop/app/node2.desktop.tsx @@ -427,8 +427,7 @@ const plumbEvents = () => { () => {}, (c: boolean) => { R.remoteDispatch(RemoteGen.createEngineConnection({connected: c})) - }, - false + } ) const timeoutPromise = async (timeMs: number) => diff --git a/shared/desktop/electron-sums.tsx b/shared/desktop/electron-sums.tsx index 5e8e69930588..f02f8033b288 100644 --- a/shared/desktop/electron-sums.tsx +++ b/shared/desktop/electron-sums.tsx @@ -1,10 +1,10 @@ // Generated with: ./extract-electron-shasums.sh {ver} // prettier-ignore export const electronChecksums = { - 'electron-v39.2.7-darwin-arm64.zip': 'bda657a77c074ee0c6a0e5d5f6de17918d7cf959306b454f6fadb07a08588883', - 'electron-v39.2.7-darwin-x64.zip': 'd7535e64ad54efcf0fae84d7fea4c2ee4727eec99c78d2a5acc695285cb0a9f0', - 'electron-v39.2.7-linux-arm64.zip': '445465a43bd2ffaec09877f4ed46385065632a4683c2806cc6211cc73c110024', - 'electron-v39.2.7-linux-x64.zip': '2f5285ef563dca154aa247696dddef545d3d895dd9b227ed423ea0d43737c22c', - 'electron-v39.2.7-win32-x64.zip': '3464537fa4be6b7b073f1c9b694ac2eb1f632d6ec36f6eeac9e00d8a279f188c', - 'hunspell_dictionaries.zip': 'f1320ff95f2cce0f0f7225b45f2b9340aeb38b341b4090f0e58f58dc2da2f3a9', + 'electron-v40.4.0-darwin-arm64.zip': 'b35807b8a43f9f1b60eb5e99ccd4c5001f96579e1b061a2b96207ff88d339a91', + 'electron-v40.4.0-darwin-x64.zip': '33f6fb8e495d64235519de57ab178fc12611adbe2bd1bbb65e6f80c5b04e1978', + 'electron-v40.4.0-linux-arm64.zip': 'a0e38a4657c69a4ff488990b8f3575e1422a4f6aec6f0f67608c567aab77d112', + 'electron-v40.4.0-linux-x64.zip': '351e0b5df9bedf3ee30f32e4186f059699d3b60515398925213a61496fafd3c5', + 'electron-v40.4.0-win32-x64.zip': 'd3f15c97ba68dad570d10b139f4bf11b5b58efbc8409f2ffda310990a5a329a6', + 'hunspell_dictionaries.zip': '6ab010b97b1e024234032cad5ce28e8a161b257c96e99231957c5d213421fca4', } diff --git a/shared/desktop/package.desktop.tsx b/shared/desktop/package.desktop.tsx index 718fb3d37e5f..96a27cca95f3 100644 --- a/shared/desktop/package.desktop.tsx +++ b/shared/desktop/package.desktop.tsx @@ -1,7 +1,7 @@ import {rimrafSync} from 'rimraf' import fs from 'fs-extra' import os from 'os' -import packager, {type Options} from '@electron/packager' +import {packager, type Options} from '@electron/packager' import path from 'path' import webpack from 'webpack' import rootConfig from './webpack.config.babel' @@ -237,14 +237,14 @@ async function pack(plat: string, arch: string) { if (packageOutDir === '') packageOutDir = desktopPath(`release/${plat}-${arch}`) console.log('Packaging to', packageOutDir) - const opts = { + const opts: Options = { ...packagerOpts, - arch, + arch: arch as Options['arch'], out: packageOutDir, - platform: plat, + platform: plat as Options['platform'], ...(plat === 'win32' ? { - 'version-string': { + win32metadata: { CompanyName: companyName, FileDescription: appName, OriginalFilename: appName + '.exe', @@ -256,8 +256,7 @@ async function pack(plat: string, arch: string) { console.log('Building using options', opts) const ret = await packager(opts) - // sometimes returns bools, unclear why - return ret.filter(o => typeof o === 'string') + return ret } function postPack(appPaths: Array, plat: string, arch: string) { diff --git a/shared/desktop/remote/use-serialize-props.desktop.tsx b/shared/desktop/remote/use-serialize-props.desktop.tsx index 4bd25aacfdf1..3327ea8f6428 100644 --- a/shared/desktop/remote/use-serialize-props.desktop.tsx +++ b/shared/desktop/remote/use-serialize-props.desktop.tsx @@ -5,7 +5,7 @@ import * as React from 'react' import * as C from '@/constants' import KB2 from '@/util/electron.desktop' import isEqual from 'lodash/isEqual' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const {rendererNewProps} = KB2.functions diff --git a/shared/desktop/renderer/main.desktop.tsx b/shared/desktop/renderer/main.desktop.tsx index 640693e7a1bd..f216bfdfa816 100644 --- a/shared/desktop/renderer/main.desktop.tsx +++ b/shared/desktop/renderer/main.desktop.tsx @@ -3,7 +3,7 @@ import './globals.desktop' import {isDarwin, isWindows} from '@/constants/platform' import '@/util/why-did-you-render' import KB2, {waitOnKB2Loaded} from '@/util/electron.desktop' -import * as DarkMode from '@/constants/darkmode' +import * as DarkMode from '@/stores/darkmode' waitOnKB2Loaded(() => { const {setSystemSupported, setSystemDarkMode} = DarkMode.useDarkModeState.getState().dispatch diff --git a/shared/desktop/renderer/main2.desktop.tsx b/shared/desktop/renderer/main2.desktop.tsx index 969a0917143b..e2d29cd73822 100644 --- a/shared/desktop/renderer/main2.desktop.tsx +++ b/shared/desktop/renderer/main2.desktop.tsx @@ -4,24 +4,229 @@ import Main from '@/app/main.desktop' import * as C from '@/constants' import * as React from 'react' import * as ReactDOM from 'react-dom/client' -import type * as RemoteGen from '@/actions/remote-gen' +import * as RemoteGen from '@/actions/remote-gen' import Root from './container.desktop' import {makeEngine} from '@/engine' import {disableDragDrop} from '@/util/drag-drop.desktop' -import {dumpLogs} from '@/constants/platform-specific/index.desktop' import {initDesktopStyles} from '@/styles/index.desktop' import {isWindows} from '@/constants/platform' import KB2 from '@/util/electron.desktop' +import {ignorePromise} from '@/constants/utils' +import {useConfigState} from '@/stores/config' +import {usePinentryState} from '@/stores/pinentry' +import * as T from '@/constants/types' +import {RPCError} from '@/util/errors' +import {switchTab} from '@/constants/router2' +import {storeRegistry} from '@/stores/store-registry' +import {onEngineConnected, onEngineDisconnected} from '@/constants/init/index.desktop' +import {handleAppLink} from '@/constants/deeplinks' +import * as Crypto from '@/constants/crypto' +import * as Tabs from '@/constants/tabs' +import {isPathSaltpackEncrypted, isPathSaltpackSigned} from '@/util/path' +import type HiddenString from '@/util/hidden-string' +import {useCryptoState} from '@/stores/crypto' +import logger from '@/logger' import {debugWarning} from '@/util/debug-warning' -import {useConfigState} from '@/constants/config' import type {default as NewMainType} from '../../app/main.desktop' import {setServiceDecoration} from '@/common-adapters/markdown/react' import ServiceDecoration from '@/common-adapters/markdown/service-decoration' -import {useDarkModeState} from '@/constants/darkmode' -import {initPlatformListener} from '@/constants/platform-specific' +import {useDarkModeState} from '@/stores/darkmode' +import {initPlatformListener, onEngineIncoming} from '@/constants/init/index.desktop' setServiceDecoration(ServiceDecoration) -const {ipcRendererOn, requestWindowsStartService, appStartedUp} = KB2.functions +const {ipcRendererOn, requestWindowsStartService, appStartedUp, ctlQuit, dumpNodeLogger} = KB2.functions + +const handleSaltPackOpen = (_path: string | HiddenString) => { + const path = typeof _path === 'string' ? _path : _path.stringValue() + + if (!useConfigState.getState().loggedIn) { + console.warn('Tried to open a saltpack file before being logged in') + return + } + let operation: T.Crypto.Operations | undefined + if (isPathSaltpackEncrypted(path)) { + operation = Crypto.Operations.Decrypt + } else if (isPathSaltpackSigned(path)) { + operation = Crypto.Operations.Verify + } else { + logger.warn( + 'Deeplink received saltpack file path not ending in ".encrypted.saltpack" or ".signed.saltpack"' + ) + return + } + useCryptoState.getState().dispatch.onSaltpackOpenFile(operation, path) + switchTab(Tabs.cryptoTab) +} + +const dumpLogs = async (reason?: string) => { + await logger.dump() + await (dumpNodeLogger?.() ?? Promise.resolve([])) + // quit as soon as possible + if (reason === 'quitting through menu') { + ctlQuit?.() + } +} + +const updateApp = () => { + const f = async () => { + await T.RPCGen.configStartUpdateIfNeededRpcPromise() + } + ignorePromise(f()) + // * If user choose to update: + // We'd get killed and it doesn't matter what happens here. + // * If user hits "Ignore": + // Note that we ignore the snooze here, so the state shouldn't change, + // and we'd back to where we think we still need an update. So we could + // have just unset the "updating" flag.However, in case server has + // decided to pull out the update between last time we asked the updater + // and now, we'd be in a wrong state if we didn't check with the service. + // Since user has interacted with it, we still ask the service to make + // sure. + + useConfigState.getState().dispatch.setUpdating() +} + +const eventFromRemoteWindows = (action: RemoteGen.Actions) => { + switch (action.type) { + case RemoteGen.resetStore: + break + case RemoteGen.openChatFromWidget: { + useConfigState.getState().dispatch.showMain() + storeRegistry.getConvoState(action.payload.conversationIDKey).dispatch.navigateToThread('inboxSmall') + break + } + case RemoteGen.inboxRefresh: { + storeRegistry.getState('chat').dispatch.inboxRefresh('widgetRefresh') + break + } + case RemoteGen.engineConnection: { + if (action.payload.connected) { + onEngineConnected() + } else { + onEngineDisconnected() + } + break + } + case RemoteGen.switchTab: { + switchTab(action.payload.tab) + break + } + case RemoteGen.setCriticalUpdate: { + storeRegistry.getState('fs').dispatch.setCriticalUpdate(action.payload.critical) + break + } + case RemoteGen.userFileEditsLoad: { + storeRegistry.getState('fs').dispatch.userFileEditsLoad() + break + } + case RemoteGen.openFilesFromWidget: { + storeRegistry.getState('fs').dispatch.defer.openFilesFromWidgetDesktop?.(action.payload.path) + break + } + case RemoteGen.saltpackFileOpen: { + handleSaltPackOpen(action.payload.path) + break + } + case RemoteGen.pinentryOnCancel: { + usePinentryState.getState().dispatch.dynamic.onCancel?.() + break + } + case RemoteGen.pinentryOnSubmit: { + usePinentryState.getState().dispatch.dynamic.onSubmit?.(action.payload.password) + break + } + case RemoteGen.openPathInSystemFileManager: { + storeRegistry.getState('fs').dispatch.defer.openPathInSystemFileManagerDesktop?.(action.payload.path) + break + } + case RemoteGen.unlockFoldersSubmitPaperKey: { + T.RPCGen.loginPaperKeySubmitRpcPromise({paperPhrase: action.payload.paperKey}, 'unlock-folders:waiting') + .then(() => { + useConfigState.getState().dispatch.openUnlockFolders([]) + }) + .catch((e: unknown) => { + if (!(e instanceof RPCError)) return + useConfigState.setState(s => { + s.unlockFoldersError = e.desc + }) + }) + break + } + case RemoteGen.closeUnlockFolders: { + T.RPCGen.rekeyRekeyStatusFinishRpcPromise() + .then(() => {}) + .catch(() => {}) + useConfigState.getState().dispatch.openUnlockFolders([]) + break + } + case RemoteGen.stop: { + storeRegistry.getState('settings').dispatch.stop(action.payload.exitCode) + break + } + case RemoteGen.trackerChangeFollow: { + storeRegistry.getState('tracker2').dispatch.changeFollow(action.payload.guiID, action.payload.follow) + break + } + case RemoteGen.trackerIgnore: { + storeRegistry.getState('tracker2').dispatch.ignore(action.payload.guiID) + break + } + case RemoteGen.trackerCloseTracker: { + storeRegistry.getState('tracker2').dispatch.closeTracker(action.payload.guiID) + break + } + case RemoteGen.trackerLoad: { + storeRegistry.getState('tracker2').dispatch.load(action.payload) + break + } + case RemoteGen.link: + { + const {link} = action.payload + handleAppLink(link) + } + break + case RemoteGen.installerRan: + useConfigState.getState().dispatch.installerRan() + break + case RemoteGen.updateNow: + updateApp() + break + case RemoteGen.powerMonitorEvent: + useConfigState.getState().dispatch.powerMonitorEvent(action.payload.event) + break + case RemoteGen.showMain: + useConfigState.getState().dispatch.showMain() + break + case RemoteGen.dumpLogs: + ignorePromise(useConfigState.getState().dispatch.dumpLogs(action.payload.reason)) + break + case RemoteGen.remoteWindowWantsProps: + useConfigState + .getState() + .dispatch.remoteWindowNeedsProps(action.payload.component, action.payload.param) + break + case RemoteGen.updateWindowMaxState: + useConfigState.setState(s => { + s.windowState.isMaximized = action.payload.max + }) + break + case RemoteGen.updateWindowState: + useConfigState.getState().dispatch.updateWindowState(action.payload.windowState) + break + case RemoteGen.updateWindowShown: { + const win = action.payload.component + useConfigState.setState(s => { + s.windowShownCount.set(win, (s.windowShownCount.get(win) ?? 0) + 1) + }) + break + } + case RemoteGen.previewConversation: + storeRegistry + .getState('chat') + .dispatch.previewConversation({participants: [action.payload.participant], reason: 'tracker'}) + break + } +} // node side plumbs through initial pref so we avoid flashes const darkModeFromNode = window.location.search.match(/darkMode=(light|dark)/) @@ -49,16 +254,20 @@ const setupApp = () => { disableDragDrop() const {batch} = C.useWaitingState.getState().dispatch - const eng = makeEngine(batch, () => { - // do nothing we wait for the remote version from node - }) + const eng = makeEngine( + batch, + () => { + // do nothing we wait for the remote version from node + }, + onEngineIncoming + ) initPlatformListener() eng.listenersAreReady() ipcRendererOn?.('KBdispatchAction', (_: unknown, action: unknown) => { setTimeout(() => { try { - useConfigState.getState().dispatch.eventFromRemoteWindows(action as RemoteGen.Actions) + eventFromRemoteWindows(action as RemoteGen.Actions) } catch {} }, 0) }) diff --git a/shared/desktop/webpack.config.babel.js b/shared/desktop/webpack.config.babel.js index 061ead3748fc..b14d20a5fbf0 100644 --- a/shared/desktop/webpack.config.babel.js +++ b/shared/desktop/webpack.config.babel.js @@ -8,22 +8,19 @@ import path from 'path' import webpack from 'webpack' import HtmlWebpackPlugin from 'html-webpack-plugin' import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin' -import CircularDependencyPlugin from 'circular-dependency-plugin' const ignoredModules = require('../ignored-modules') const enableWDYR = require('../util/why-did-you-render-enabled') const elecVersion = require('../package.json').devDependencies.electron // true if you want to debug unused code. This makes single chunks so you can grep for 'unused harmony' in the output in desktop/dist const debugUnusedChunks = false -const enableCircularDepCheck = false const evalDevtools = false -if (enableWDYR || debugUnusedChunks || enableCircularDepCheck || evalDevtools) { +if (enableWDYR || debugUnusedChunks || evalDevtools) { for (let i = 0; i < 10; ++i) { console.error('Webpack debugging on!!!', { enableWDYR, debugUnusedChunks, - enableCircularDepCheck, evalDevtools, }) } @@ -188,23 +185,6 @@ const config = (_, {mode}) => { new webpack.DefinePlugin(defines), // Inject some defines new webpack.IgnorePlugin({resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/}), // Skip a bunch of crap moment pulls in ...(enableWDYR ? [] : [new webpack.IgnorePlugin({resourceRegExp: /^lodash$/})]), // Disallow entire lodash, but needed by why did - ...(isDev && enableCircularDepCheck - ? [ - new CircularDependencyPlugin({ - // exclude detection of files based on a RegExp - exclude: /node_modules/, - // include specific files based on a RegExp - // include: /dir/, - // add errors to webpack instead of warnings - failOnError: true, - // allow import cycles that include an asyncronous import, - // e.g. via import(/* webpackMode: "weak" */ './file.js') - allowAsyncCycles: false, - // set the current working directory for displaying module paths - cwd: process.cwd(), - }), - ] - : []), ], resolve: { alias, diff --git a/shared/devices/add-device.tsx b/shared/devices/add-device.tsx index 3b5726f11013..e27c6efffcc5 100644 --- a/shared/devices/add-device.tsx +++ b/shared/devices/add-device.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' -import * as Devices from '@/constants/devices' +import * as Devices from '@/stores/devices' import * as React from 'react' import * as Kb from '@/common-adapters' -import {useProvisionState} from '@/constants/provision' +import {useProvisionState} from '@/stores/provision' type OwnProps = { highlight?: Array<'computer' | 'phone' | 'paper key'> diff --git a/shared/devices/device-icon.tsx b/shared/devices/device-icon.tsx index 0121619b3fd5..cc698e7fe61d 100644 --- a/shared/devices/device-icon.tsx +++ b/shared/devices/device-icon.tsx @@ -1,5 +1,5 @@ -import type * as Provision from '@/constants/provision' -import * as Devices from '@/constants/devices' +import type * as Provision from '@/stores/provision' +import * as Devices from '@/stores/devices' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import type {IconStyle} from '@/common-adapters/icon' diff --git a/shared/devices/device-page.tsx b/shared/devices/device-page.tsx index dff22308c890..f270c62a1b27 100644 --- a/shared/devices/device-page.tsx +++ b/shared/devices/device-page.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Devices from '@/constants/devices' +import * as Devices from '@/stores/devices' import * as Kb from '@/common-adapters' import * as React from 'react' import type * as T from '@/constants/types' diff --git a/shared/devices/device-revoke.tsx b/shared/devices/device-revoke.tsx index 374bc6d13354..90e0850a35fe 100644 --- a/shared/devices/device-revoke.tsx +++ b/shared/devices/device-revoke.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import {useConfigState} from '@/constants/config' -import * as Devices from '@/constants/devices' +import {useConfigState} from '@/stores/config' +import * as Devices from '@/stores/devices' import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' -import {settingsDevicesTab} from '@/constants/settings' -import {useCurrentUserState} from '@/constants/current-user' +import {settingsDevicesTab} from '@/stores/settings' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {deviceID: string} @@ -97,7 +97,7 @@ const useRevoke = (deviceID = '') => { try { await T.RPCGen.loginDeprovisionRpcPromise({doRevoke: true, username}, C.waitingKeyDevices) load() - useConfigState.getState().dispatch.revoke(deviceName) + useConfigState.getState().dispatch.revoke(deviceName, wasCurrentDevice) } catch {} } else { try { @@ -106,7 +106,7 @@ const useRevoke = (deviceID = '') => { C.waitingKeyDevices ) load() - useConfigState.getState().dispatch.revoke(deviceName) + useConfigState.getState().dispatch.revoke(deviceName, wasCurrentDevice) navUpToScreen( C.isMobile ? (C.isTablet ? C.Tabs.settingsTab : settingsDevicesTab) : C.Tabs.devicesTab ) diff --git a/shared/devices/index.tsx b/shared/devices/index.tsx index ebef2b008e50..4df196248347 100644 --- a/shared/devices/index.tsx +++ b/shared/devices/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Devices from '@/constants/devices' +import * as Devices from '@/stores/devices' import * as Kb from '@/common-adapters' import * as React from 'react' import DeviceRow, {NewContext} from './row' diff --git a/shared/devices/nav-header.tsx b/shared/devices/nav-header.tsx index 2176976bb9c3..66506b11fbe8 100644 --- a/shared/devices/nav-header.tsx +++ b/shared/devices/nav-header.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' -import type * as DevicesType from '@/constants/devices' +import type * as DevicesType from '@/stores/devices' import * as Kb from '@/common-adapters' import * as React from 'react' export const HeaderTitle = () => { - const Devices = require('@/constants/devices') as typeof DevicesType + const Devices = require('@/stores/devices') as typeof DevicesType const numActive = Devices.useActiveDeviceCounts() const numRevoked = Devices.useRevokedDeviceCounts() return ( diff --git a/shared/devices/row.tsx b/shared/devices/row.tsx index 5a384bbb9d81..3817ce3ef759 100644 --- a/shared/devices/row.tsx +++ b/shared/devices/row.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Devices from '@/constants/devices' +import * as Devices from '@/stores/devices' import * as Kb from '@/common-adapters' import * as React from 'react' import DeviceIcon from './device-icon' diff --git a/shared/engine/index-impl.tsx b/shared/engine/index-impl.tsx index 14f389095d1d..14513d9c5fa5 100644 --- a/shared/engine/index-impl.tsx +++ b/shared/engine/index-impl.tsx @@ -12,7 +12,6 @@ import {printOutstandingRPCs} from '@/local-debug' import {resetClient, createClient, rpcLog, type CreateClientType, type PayloadType} from './index.platform' import {type RPCError, convertToError} from '@/util/errors' import type * as EngineGen from '../actions/engine-gen-gen' -import type * as EngineConst from '@/constants/engine' // delay incoming to stop react from queueing too many setState calls and stopping rendering // only while debugging for now @@ -47,6 +46,8 @@ class Engine { _listenersAreReady: boolean = false _emitWaiting: (changes: BatchParams) => void + _incomingTimeout: NodeJS.Timeout | undefined + _onEngineIncoming?: (action: EngineGen.Actions) => void _queuedChanges: Array<{error?: RPCError; increment: boolean; key: WaitingKey}> = [] dispatchWaitingAction = (key: WaitingKey, waiting: boolean, error?: RPCError) => { @@ -63,13 +64,18 @@ class Engine { constructor( emitWaiting: (changes: BatchParams) => void, onConnected: (c: boolean) => void, - allowIncomingCalls = true + onEngineIncoming?: (action: EngineGen.Actions) => void ) { this._onConnectedCB = onConnected + this._onEngineIncoming = onEngineIncoming // the node engine doesn't do this and we don't want to pull in any reqs - if (allowIncomingCalls) { - const {useEngineState} = require('@/constants/engine') as typeof EngineConst - this._engineConstantsIncomingCall = useEngineState.getState().dispatch.onEngineIncoming + if (onEngineIncoming) { + this._engineConstantsIncomingCall = (action: EngineGen.Actions) => { + // defer a frame so its more like before + this._incomingTimeout = setTimeout(() => { + this._onEngineIncoming?.(action) + }, 0) + } } this._emitWaiting = emitWaiting this._rpcClient = createClient( @@ -282,14 +288,14 @@ if (__DEV__) { const makeEngine = ( emitWaiting: (b: BatchParams) => void, onConnected: (c: boolean) => void, - allowIncomingCalls = true + onEngineIncoming?: (action: EngineGen.Actions) => void ) => { if (__DEV__ && engine) { logger.warn('makeEngine called multiple times') } if (!engine) { - engine = new Engine(emitWaiting, onConnected, allowIncomingCalls) + engine = new Engine(emitWaiting, onConnected, onEngineIncoming) initEngine(engine) initEngineListener(engineListener) } diff --git a/shared/engine/index.d.ts b/shared/engine/index.d.ts index cd74ba25e572..e352925abb33 100644 --- a/shared/engine/index.d.ts +++ b/shared/engine/index.d.ts @@ -29,7 +29,7 @@ export declare function getEngine(): Engine export declare function makeEngine( emitWaiting: (b: BatchParams) => void, onConnected: (c: boolean) => void, - allowIncomingCalls?: boolean + onEngineIncoming?: (action: EngineGen.Actions) => void ): Engine export default getEngine export type {IncomingCallMapType, CustomResponseIncomingCallMapType} diff --git a/shared/fs/banner/conflict-banner.tsx b/shared/fs/banner/conflict-banner.tsx index 09898a2b91e6..c43e0333121f 100644 --- a/shared/fs/banner/conflict-banner.tsx +++ b/shared/fs/banner/conflict-banner.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import openUrl from '@/util/open-url' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type OwnProps = { path: T.FS.Path @@ -33,7 +33,7 @@ const ConnectedBanner = (ownProps: OwnProps) => { }, [startManualConflictResolution, path]) const openPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openPathInSystemFileManagerDesktop + s => s.dispatch.defer.openPathInSystemFileManagerDesktop ) const openInSystemFileManager = React.useCallback( diff --git a/shared/fs/banner/public-reminder.tsx b/shared/fs/banner/public-reminder.tsx index 16783c602a96..717e78175cf4 100644 --- a/shared/fs/banner/public-reminder.tsx +++ b/shared/fs/banner/public-reminder.tsx @@ -1,8 +1,8 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type Props = { path: T.FS.Path diff --git a/shared/fs/banner/reset-banner.tsx b/shared/fs/banner/reset-banner.tsx index 91e09bfd1b13..3606d16b8f2c 100644 --- a/shared/fs/banner/reset-banner.tsx +++ b/shared/fs/banner/reset-banner.tsx @@ -4,10 +4,10 @@ import * as T from '@/constants/types' import {folderNameWithoutUsers} from '@/util/kbfs' import * as Kb from '@/common-adapters' import * as RowTypes from '@/fs/browser/rows/types' -import {useTrackerState} from '@/constants/tracker2' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' -import {useProfileState} from '@/constants/profile' +import {useTrackerState} from '@/stores/tracker2' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' +import {useProfileState} from '@/stores/profile' type OwnProps = {path: T.FS.Path} @@ -21,7 +21,7 @@ const ConnectedBanner = (ownProps: OwnProps) => { if (pathElems.length < 3) return const filteredPathName = folderNameWithoutUsers(pathElems[2] ?? '', users) const filteredPath = T.FS.stringToPath(['', pathElems[0], pathElems[1], filteredPathName].join('/')) - FS.makeActionForOpenPathInFilesTab(filteredPath) + FS.navToPath(filteredPath) }, [] ) diff --git a/shared/fs/banner/system-file-manager-integration-banner/container.tsx b/shared/fs/banner/system-file-manager-integration-banner/container.tsx index 02877de920b0..232e32b74c5d 100644 --- a/shared/fs/banner/system-file-manager-integration-banner/container.tsx +++ b/shared/fs/banner/system-file-manager-integration-banner/container.tsx @@ -3,8 +3,8 @@ import * as C from '@/constants' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import * as Kbfs from '@/fs/common' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type OwnProps = {alwaysShow?: boolean} @@ -14,7 +14,7 @@ const SFMIContainer = (op: OwnProps) => { driverDisable: s.dispatch.driverDisable, driverEnable: s.dispatch.driverEnable, driverStatus: s.sfmi.driverStatus, - setSfmiBannerDismissedDesktop: s.dispatch.dynamic.setSfmiBannerDismissedDesktop, + setSfmiBannerDismissedDesktop: s.dispatch.defer.setSfmiBannerDismissedDesktop, settings: s.settings, })) ) @@ -218,7 +218,7 @@ const JustEnabled = ({onDismiss}: JustEnabledProps) => { const {displayingMountDir, openLocalPathInSystemFileManagerDesktop} = useFSState( C.useShallow(s => ({ displayingMountDir: s.sfmi.preferredMountDirs[0] ?? '', - openLocalPathInSystemFileManagerDesktop: s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop, + openLocalPathInSystemFileManagerDesktop: s.dispatch.defer.openLocalPathInSystemFileManagerDesktop, })) ) const open = displayingMountDir diff --git a/shared/fs/banner/system-file-manager-integration-banner/kext-permission-popup.tsx b/shared/fs/banner/system-file-manager-integration-banner/kext-permission-popup.tsx index 759aa1d2fe21..c294c7b776ea 100644 --- a/shared/fs/banner/system-file-manager-integration-banner/kext-permission-popup.tsx +++ b/shared/fs/banner/system-file-manager-integration-banner/kext-permission-popup.tsx @@ -2,11 +2,11 @@ import * as React from 'react' import * as C from '@/constants' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const InstallSecurityPrefs = () => { const driverStatus = useFSState(s => s.sfmi.driverStatus) - const openSecurityPreferencesDesktop = useFSState(s => s.dispatch.dynamic.openSecurityPreferencesDesktop) + const openSecurityPreferencesDesktop = useFSState(s => s.dispatch.defer.openSecurityPreferencesDesktop) const onCancel = C.useRouterState(s => s.dispatch.navigateUp) const openSecurityPrefs = React.useCallback( () => openSecurityPreferencesDesktop?.(), diff --git a/shared/fs/browser/destination-picker.tsx b/shared/fs/browser/destination-picker.tsx index 9a407bb950b8..b00bdd56608b 100644 --- a/shared/fs/browser/destination-picker.tsx +++ b/shared/fs/browser/destination-picker.tsx @@ -8,8 +8,8 @@ import NavHeaderTitle from '@/fs/nav-header/title' import Root from './root' import Rows from './rows/rows-container' import {OriginalOrCompressedButton} from '@/incoming-share' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type OwnProps = {index: number} diff --git a/shared/fs/browser/index.tsx b/shared/fs/browser/index.tsx index c4e8df63334d..29410ae9c1bc 100644 --- a/shared/fs/browser/index.tsx +++ b/shared/fs/browser/index.tsx @@ -10,8 +10,8 @@ import PublicReminder from '../banner/public-reminder' import Root from './root' import Rows from './rows/rows-container' import {asRows as resetBannerAsRows} from '../banner/reset-banner' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type OwnProps = {path: T.FS.Path} @@ -68,7 +68,7 @@ const DragAndDrop = React.memo(function DragAndDrop(p: { rejectReason?: string }) { const {children, path, rejectReason} = p - const uploadFromDragAndDrop = useFSState(s => s.dispatch.dynamic.uploadFromDragAndDropDesktop) + const uploadFromDragAndDrop = useFSState(s => s.dispatch.defer.uploadFromDragAndDropDesktop) const onAttach = React.useCallback( (localPaths: Array) => uploadFromDragAndDrop?.(path, localPaths), [path, uploadFromDragAndDrop] diff --git a/shared/fs/browser/offline.tsx b/shared/fs/browser/offline.tsx index 51f045b4fdba..3a3273e64ffe 100644 --- a/shared/fs/browser/offline.tsx +++ b/shared/fs/browser/offline.tsx @@ -1,8 +1,8 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import TopBar from '../top-bar' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type Props = { path: T.FS.Path diff --git a/shared/fs/browser/root.tsx b/shared/fs/browser/root.tsx index af39fbc45ea1..1a1cef90548c 100644 --- a/shared/fs/browser/root.tsx +++ b/shared/fs/browser/root.tsx @@ -5,9 +5,9 @@ import TlfType from './rows/tlf-type' import Tlf from './rows/tlf' import SfmiBanner from '../banner/system-file-manager-integration-banner/container' import {WrapRow} from './rows/rows' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' +import {useCurrentUserState} from '@/stores/current-user' type Props = { destinationPickerIndex?: number diff --git a/shared/fs/browser/rows/editing.tsx b/shared/fs/browser/rows/editing.tsx index e6e19567d756..99ac037113a7 100644 --- a/shared/fs/browser/rows/editing.tsx +++ b/shared/fs/browser/rows/editing.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {rowStyles} from './common' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type Props = { editID: T.FS.EditID diff --git a/shared/fs/browser/rows/rows-container.tsx b/shared/fs/browser/rows/rows-container.tsx index 9c3852859b96..37019066d45d 100644 --- a/shared/fs/browser/rows/rows-container.tsx +++ b/shared/fs/browser/rows/rows-container.tsx @@ -4,9 +4,9 @@ import * as RowTypes from './types' import {sortRowItems, type SortableRowItem} from './sort' import Rows, {type Props} from './rows' import {asRows as topBarAsRow} from '../../top-bar' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = { path: T.FS.Path // path to the parent folder containering the rows, diff --git a/shared/fs/browser/rows/still.tsx b/shared/fs/browser/rows/still.tsx index c5b1655ff3b4..f0a2ad9abbef 100644 --- a/shared/fs/browser/rows/still.tsx +++ b/shared/fs/browser/rows/still.tsx @@ -4,8 +4,8 @@ import {useOpen} from '@/fs/common/use-open' import {rowStyles, StillCommon} from './common' import * as Kb from '@/common-adapters' import {LastModifiedLine, Filename} from '@/fs/common' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type OwnProps = { destinationPickerIndex?: number diff --git a/shared/fs/browser/rows/tlf-type.tsx b/shared/fs/browser/rows/tlf-type.tsx index 908334f54352..e60d181d6f39 100644 --- a/shared/fs/browser/rows/tlf-type.tsx +++ b/shared/fs/browser/rows/tlf-type.tsx @@ -1,6 +1,6 @@ import * as T from '@/constants/types' import {useOpen} from '@/fs/common/use-open' -import * as FS from '@/constants/fs' +import * as FS from '@/stores/fs' import {rowStyles, StillCommon} from './common' import * as Kb from '@/common-adapters' diff --git a/shared/fs/browser/rows/tlf.tsx b/shared/fs/browser/rows/tlf.tsx index 001ba2b64cdd..1092deddbaff 100644 --- a/shared/fs/browser/rows/tlf.tsx +++ b/shared/fs/browser/rows/tlf.tsx @@ -3,9 +3,9 @@ import {useOpen} from '@/fs/common/use-open' import {rowStyles, StillCommon} from './common' import * as Kb from '@/common-adapters' import {useFsPathMetadata, TlfInfoLine, Filename} from '@/fs/common' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' +import {useCurrentUserState} from '@/stores/current-user' export type OwnProps = { destinationPickerIndex?: number diff --git a/shared/fs/common/errs-container.tsx b/shared/fs/common/errs-container.tsx index 7e86e484dd46..ae768869412a 100644 --- a/shared/fs/common/errs-container.tsx +++ b/shared/fs/common/errs-container.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as C from '@/constants' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const ErrsContainer = () => { const {_errors, _dismiss} = useFSState( diff --git a/shared/fs/common/folder-view-filter-icon.tsx b/shared/fs/common/folder-view-filter-icon.tsx index a843e380bf81..135aff8989f2 100644 --- a/shared/fs/common/folder-view-filter-icon.tsx +++ b/shared/fs/common/folder-view-filter-icon.tsx @@ -1,8 +1,8 @@ import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import type * as Styles from '@/styles' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type Props = { onClick: () => void diff --git a/shared/fs/common/folder-view-filter.tsx b/shared/fs/common/folder-view-filter.tsx index 7cf7dc787a71..45e1c6d835da 100644 --- a/shared/fs/common/folder-view-filter.tsx +++ b/shared/fs/common/folder-view-filter.tsx @@ -3,8 +3,8 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as React from 'react' import debounce from 'lodash/debounce' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type Props = { onCancel?: () => void diff --git a/shared/fs/common/hooks.tsx b/shared/fs/common/hooks.tsx index be2ac0bcaa39..bb30566beb4f 100644 --- a/shared/fs/common/hooks.tsx +++ b/shared/fs/common/hooks.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import logger from '@/logger' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' const isPathItem = (path: T.FS.Path) => T.FS.getPathLevel(path) > 2 || FS.hasSpecialFileElement(path) @@ -185,8 +185,8 @@ export const useFsWatchDownloadForMobile = C.isMobile const {dlState, finishedDownloadWithIntentMobile, finishedRegularDownloadMobile} = useFSState( C.useShallow(s => ({ dlState: s.downloads.state.get(downloadID) || FS.emptyDownloadState, - finishedDownloadWithIntentMobile: s.dispatch.dynamic.finishedDownloadWithIntentMobile, - finishedRegularDownloadMobile: s.dispatch.dynamic.finishedRegularDownloadMobile, + finishedDownloadWithIntentMobile: s.dispatch.defer.finishedDownloadWithIntentMobile, + finishedRegularDownloadMobile: s.dispatch.defer.finishedRegularDownloadMobile, })) ) const finished = dlState !== FS.emptyDownloadState && !FS.downloadIsOngoing(dlState) diff --git a/shared/fs/common/item-icon.tsx b/shared/fs/common/item-icon.tsx index 303b8e533dc2..8b0d42789caf 100644 --- a/shared/fs/common/item-icon.tsx +++ b/shared/fs/common/item-icon.tsx @@ -1,8 +1,8 @@ import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import type {IconType} from '@/common-adapters/icon' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' export type Size = 96 | 48 | 32 | 16 type SizeString = '96' | '48' | '32' | '16' diff --git a/shared/fs/common/kbfs-path.tsx b/shared/fs/common/kbfs-path.tsx index bc000fb40d44..c49cd7cd96e7 100644 --- a/shared/fs/common/kbfs-path.tsx +++ b/shared/fs/common/kbfs-path.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' -import * as FS from '@/constants/fs' +import * as FS from '@/stores/fs' import PathInfo from './path-info' import PathItemInfo from './path-item-info' @@ -18,7 +18,7 @@ type PopupProps = Props & { } const useOpenInFilesTab = (path: T.FS.Path) => { - return React.useCallback(() => FS.makeActionForOpenPathInFilesTab(path), [path]) + return React.useCallback(() => FS.navToPath(path), [path]) } const KbfsPathPopup = (props: PopupProps) => { diff --git a/shared/fs/common/last-modified-line.tsx b/shared/fs/common/last-modified-line.tsx index 7a8d7893f508..a11c6be3625f 100644 --- a/shared/fs/common/last-modified-line.tsx +++ b/shared/fs/common/last-modified-line.tsx @@ -1,8 +1,8 @@ import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import {formatTimeForFS} from '@/util/timestamp' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' export type OwnProps = { path: T.FS.Path diff --git a/shared/fs/common/open-in-system-file-manager.tsx b/shared/fs/common/open-in-system-file-manager.tsx index 42758a5a7420..44daf606fd2e 100644 --- a/shared/fs/common/open-in-system-file-manager.tsx +++ b/shared/fs/common/open-in-system-file-manager.tsx @@ -3,13 +3,13 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as C from '@/constants' import SystemFileManagerIntegrationPopup from './sfmi-popup' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' type Props = {path: T.FS.Path} const OpenInSystemFileManager = React.memo(function OpenInSystemFileManager({path}: Props) { const openPathInSystemFileManagerDesktop = useFSState( - s => s.dispatch.dynamic.openPathInSystemFileManagerDesktop + s => s.dispatch.defer.openPathInSystemFileManagerDesktop ) const openInSystemFileManager = React.useCallback( () => openPathInSystemFileManagerDesktop?.(path), diff --git a/shared/fs/common/path-info.tsx b/shared/fs/common/path-info.tsx index 16e0f1ef9d51..25182ab0625a 100644 --- a/shared/fs/common/path-info.tsx +++ b/shared/fs/common/path-info.tsx @@ -1,8 +1,8 @@ import * as T from '@/constants/types' import {useFsPathInfo} from './hooks' import * as Kb from '@/common-adapters' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type PathInfoProps = { containerStyle?: Kb.Styles.StylesCrossPlatform diff --git a/shared/fs/common/path-item-action/choose-view.tsx b/shared/fs/common/path-item-action/choose-view.tsx index 7029ddf85d26..23421a3c10cd 100644 --- a/shared/fs/common/path-item-action/choose-view.tsx +++ b/shared/fs/common/path-item-action/choose-view.tsx @@ -2,7 +2,7 @@ import * as T from '@/constants/types' import type {FloatingMenuProps} from './types' import Menu from './menu-container' import Confirm from './confirm' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' type OwnProps = { floatingMenuProps: FloatingMenuProps diff --git a/shared/fs/common/path-item-action/confirm-delete.tsx b/shared/fs/common/path-item-action/confirm-delete.tsx index fbc3d3afe1aa..d47af5f53184 100644 --- a/shared/fs/common/path-item-action/confirm-delete.tsx +++ b/shared/fs/common/path-item-action/confirm-delete.tsx @@ -2,8 +2,8 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as C from '@/constants' import * as React from 'react' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' export type Props = { onBack: () => void diff --git a/shared/fs/common/path-item-action/confirm.tsx b/shared/fs/common/path-item-action/confirm.tsx index 73e19306d67c..c3fb1a363ea6 100644 --- a/shared/fs/common/path-item-action/confirm.tsx +++ b/shared/fs/common/path-item-action/confirm.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import * as T from '@/constants/types' import type {FloatingMenuProps} from './types' import * as Kb from '@/common-adapters' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type OwnProps = { floatingMenuProps: FloatingMenuProps diff --git a/shared/fs/common/path-item-action/index.tsx b/shared/fs/common/path-item-action/index.tsx index 46087ca280e2..00c24879ddbb 100644 --- a/shared/fs/common/path-item-action/index.tsx +++ b/shared/fs/common/path-item-action/index.tsx @@ -4,8 +4,8 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import ChooseView from './choose-view' import type {SizeType} from '@/common-adapters/icon' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' export type ClickableProps = { onClick: () => void diff --git a/shared/fs/common/path-item-action/layout.tsx b/shared/fs/common/path-item-action/layout.tsx index ec63a3a52b64..89a91060c446 100644 --- a/shared/fs/common/path-item-action/layout.tsx +++ b/shared/fs/common/path-item-action/layout.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as T from '@/constants/types' -import * as FS from '@/constants/fs' +import * as FS from '@/stores/fs' export type Layout = { archive: boolean diff --git a/shared/fs/common/path-item-action/menu-container.tsx b/shared/fs/common/path-item-action/menu-container.tsx index 996f366ea73c..74d8f8c7180b 100644 --- a/shared/fs/common/path-item-action/menu-container.tsx +++ b/shared/fs/common/path-item-action/menu-container.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as Kbfs from '@/fs/common/hooks' import * as React from 'react' @@ -8,9 +8,9 @@ import * as Util from '@/util/kbfs' import Header from './header' import type {FloatingMenuProps} from './types' import {getRootLayout, getShareLayout} from './layout' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = { floatingMenuProps: FloatingMenuProps @@ -32,7 +32,7 @@ const Container = (op: OwnProps) => { const fileContext = s.fileContext.get(path) || FS.emptyFileContext const {cancelDownload, setPathItemActionMenuView, download, newFolderRow} = s.dispatch const {favoriteIgnore, startRename, dismissDownload} = s.dispatch - const {openPathInSystemFileManagerDesktop} = s.dispatch.dynamic + const {openPathInSystemFileManagerDesktop} = s.dispatch.defer const sfmiEnabled = s.sfmi.driverStatus.type === T.FS.DriverStatusType.Enabled return { cancelDownload, diff --git a/shared/fs/common/path-item-info.tsx b/shared/fs/common/path-item-info.tsx index 75d4bc69fb06..bd9d22d388f1 100644 --- a/shared/fs/common/path-item-info.tsx +++ b/shared/fs/common/path-item-info.tsx @@ -6,8 +6,8 @@ import ItemIcon from './item-icon' import CommaSeparatedName from './comma-separated-name' import {pluralize} from '@/util/string' import {useFsChildren, useFsPathMetadata, useFsOnlineStatus, useFsSoftError} from './hooks' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type Props = { containerStyle?: Kb.Styles.StylesCrossPlatform diff --git a/shared/fs/common/path-status-icon-container.tsx b/shared/fs/common/path-status-icon-container.tsx index 9a1440a507e2..0e6cb624b358 100644 --- a/shared/fs/common/path-status-icon-container.tsx +++ b/shared/fs/common/path-status-icon-container.tsx @@ -1,8 +1,8 @@ import * as T from '@/constants/types' import * as C from '@/constants' import PathStatusIcon from './path-status-icon' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type OwnPropsPathItem = { path: T.FS.Path diff --git a/shared/fs/common/refresh-driver-status-on-mount.tsx b/shared/fs/common/refresh-driver-status-on-mount.tsx index 9ce3cc4d12d5..a710da6894c8 100644 --- a/shared/fs/common/refresh-driver-status-on-mount.tsx +++ b/shared/fs/common/refresh-driver-status-on-mount.tsx @@ -1,8 +1,8 @@ import * as React from 'react' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const RefreshDriverStatusOnMount = () => { - const refreshDriverStatusDesktop = useFSState(s => s.dispatch.dynamic.refreshDriverStatusDesktop) + const refreshDriverStatusDesktop = useFSState(s => s.dispatch.defer.refreshDriverStatusDesktop) const refresh = React.useCallback(() => refreshDriverStatusDesktop?.(), [refreshDriverStatusDesktop]) React.useEffect(() => { diff --git a/shared/fs/common/sfmi-popup.tsx b/shared/fs/common/sfmi-popup.tsx index 9fbf8764643b..33aa3bbe952b 100644 --- a/shared/fs/common/sfmi-popup.tsx +++ b/shared/fs/common/sfmi-popup.tsx @@ -3,7 +3,7 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import {useFuseClosedSourceConsent} from './hooks' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' type Props = { mode: 'Icon' | 'Button' diff --git a/shared/fs/common/tlf-info-line-container.tsx b/shared/fs/common/tlf-info-line-container.tsx index 8b84d8962edd..09803b935e53 100644 --- a/shared/fs/common/tlf-info-line-container.tsx +++ b/shared/fs/common/tlf-info-line-container.tsx @@ -1,8 +1,8 @@ import * as T from '@/constants/types' import TlfInfoLine from './tlf-info-line' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' +import {useCurrentUserState} from '@/stores/current-user' export type OwnProps = { path: T.FS.Path diff --git a/shared/fs/common/upload-button.tsx b/shared/fs/common/upload-button.tsx index 593688df6905..3021fbfbfa05 100644 --- a/shared/fs/common/upload-button.tsx +++ b/shared/fs/common/upload-button.tsx @@ -3,8 +3,8 @@ import * as T from '@/constants/types' import * as C from '@/constants' import * as Kb from '@/common-adapters' import type * as Styles from '@/styles' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type OwnProps = { path: T.FS.Path @@ -73,8 +73,8 @@ const UploadButton = (props: UploadButtonProps) => { const Container = (ownProps: OwnProps) => { const _pathItem = useFSState(s => FS.getPathItem(s.pathItems, ownProps.path)) - const openAndUploadDesktop = useFSState(s => s.dispatch.dynamic.openAndUploadDesktop) - const pickAndUploadMobile = useFSState(s => s.dispatch.dynamic.pickAndUploadMobile) + const openAndUploadDesktop = useFSState(s => s.dispatch.defer.openAndUploadDesktop) + const pickAndUploadMobile = useFSState(s => s.dispatch.defer.pickAndUploadMobile) const _openAndUploadBoth = () => { openAndUploadDesktop?.(T.FS.OpenDialogType.Both, ownProps.path) } diff --git a/shared/fs/common/use-open.tsx b/shared/fs/common/use-open.tsx index 0e15b1719686..74a9ea2be194 100644 --- a/shared/fs/common/use-open.tsx +++ b/shared/fs/common/use-open.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' import * as T from '@/constants/types' import {useSafeNavigation} from '@/util/safe-navigation' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type Props = { path: T.FS.Path diff --git a/shared/fs/filepreview/bare-preview.tsx b/shared/fs/filepreview/bare-preview.tsx index bcbe04f750c7..f9d95b402178 100644 --- a/shared/fs/filepreview/bare-preview.tsx +++ b/shared/fs/filepreview/bare-preview.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' -import * as FS from '@/constants/fs' +import * as FS from '@/stores/fs' import Footer from '../footer/footer' import View from './view' import * as Kbfs from '../common' diff --git a/shared/fs/filepreview/default-view.tsx b/shared/fs/filepreview/default-view.tsx index 41d252be7d54..3fa036521c01 100644 --- a/shared/fs/filepreview/default-view.tsx +++ b/shared/fs/filepreview/default-view.tsx @@ -3,8 +3,8 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import {PathItemAction, LastModifiedLine, ItemIcon, type ClickableProps} from '../common' import {hasShare} from '../common/path-item-action/layout' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' type OwnProps = {path: T.FS.Path} @@ -19,7 +19,7 @@ const Container = (ownProps: OwnProps) => { C.useShallow(s => ({ _download: s.dispatch.download, fileContext: s.fileContext.get(path) || FS.emptyFileContext, - openPathInSystemFileManagerDesktop: s.dispatch.dynamic.openPathInSystemFileManagerDesktop, + openPathInSystemFileManagerDesktop: s.dispatch.defer.openPathInSystemFileManagerDesktop, pathItem: FS.getPathItem(s.pathItems, path), sfmiEnabled: s.sfmi.driverStatus.type === T.FS.DriverStatusType.Enabled, })) diff --git a/shared/fs/filepreview/view.tsx b/shared/fs/filepreview/view.tsx index 2765510266e7..aad5f4757683 100644 --- a/shared/fs/filepreview/view.tsx +++ b/shared/fs/filepreview/view.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as T from '@/constants/types' import DefaultView from './default-view' @@ -7,8 +7,8 @@ import TextView from './text-view' import AVView from './av-view' import PdfView from './pdf-view' import * as Kb from '@/common-adapters' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' type Props = { path: T.FS.Path diff --git a/shared/fs/footer/download.tsx b/shared/fs/footer/download.tsx index 3d2177cbd799..9cf91a45cefb 100644 --- a/shared/fs/footer/download.tsx +++ b/shared/fs/footer/download.tsx @@ -4,8 +4,8 @@ import * as C from '@/constants' import * as T from '@/constants/types' import DownloadWrapper from './download-wrapper' import {formatDurationFromNowTo} from '@/util/timestamp' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' export type Props = { downloadID: string @@ -37,7 +37,7 @@ const Download = (props: Props) => { cancelDownload: s.dispatch.cancelDownload, dismissDownload: s.dispatch.dismissDownload, dlState: s.downloads.state.get(props.downloadID) || FS.emptyDownloadState, - openLocalPathInSystemFileManagerDesktop: s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop, + openLocalPathInSystemFileManagerDesktop: s.dispatch.defer.openLocalPathInSystemFileManagerDesktop, })) ) const open = dlState.localPath diff --git a/shared/fs/footer/downloads.tsx b/shared/fs/footer/downloads.tsx index 7983b96c0020..f00942abd8b0 100644 --- a/shared/fs/footer/downloads.tsx +++ b/shared/fs/footer/downloads.tsx @@ -2,7 +2,7 @@ import * as Kb from '@/common-adapters' import * as C from '@/constants' import * as Kbfs from '../common' import Download from './download' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const Mobile = () => { Kbfs.useFsDownloadStatus() @@ -33,7 +33,7 @@ const Desktop = () => { const {downloadIDs, openLocalPathInSystemFileManagerDesktop} = useFSState( C.useShallow(s => ({ downloadIDs: s.downloads.regularDownloads, - openLocalPathInSystemFileManagerDesktop: s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop, + openLocalPathInSystemFileManagerDesktop: s.dispatch.defer.openLocalPathInSystemFileManagerDesktop, })) ) const openDownloadFolder = () => openLocalPathInSystemFileManagerDesktop?.(C.downloadFolder) diff --git a/shared/fs/footer/proof-broken.tsx b/shared/fs/footer/proof-broken.tsx index 186b5106bb28..852d5b5f925d 100644 --- a/shared/fs/footer/proof-broken.tsx +++ b/shared/fs/footer/proof-broken.tsx @@ -1,7 +1,7 @@ import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' -import * as FS from '@/constants/fs' -import {useUsersState} from '@/constants/users' +import * as FS from '@/stores/fs' +import {useUsersState} from '@/stores/users' type Props = {path: T.FS.Path} diff --git a/shared/fs/footer/upload-container.tsx b/shared/fs/footer/upload-container.tsx index 73bfd3b2d3ef..31193f51a235 100644 --- a/shared/fs/footer/upload-container.tsx +++ b/shared/fs/footer/upload-container.tsx @@ -2,8 +2,8 @@ import * as T from '@/constants/types' import Upload from './upload' import {useUploadCountdown} from './use-upload-countdown' import * as C from '@/constants' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' // NOTE flip this to show a button to debug the upload banner animations. const enableDebugUploadBanner = false as boolean diff --git a/shared/fs/index.tsx b/shared/fs/index.tsx index 304ce8342ced..78fa22597830 100644 --- a/shared/fs/index.tsx +++ b/shared/fs/index.tsx @@ -5,8 +5,8 @@ import Browser from './browser' import {NormalPreview} from './filepreview' import * as Kbfs from './common' import * as SimpleScreens from './simple-screens' -import {useFSState} from '@/constants/fs' -import * as FS from '@/constants/fs' +import {useFSState} from '@/stores/fs' +import * as FS from '@/stores/fs' type ChooseComponentProps = { emitBarePreview: () => void diff --git a/shared/fs/nav-header/actions.tsx b/shared/fs/nav-header/actions.tsx index 497c67488b7f..014b0c7e1331 100644 --- a/shared/fs/nav-header/actions.tsx +++ b/shared/fs/nav-header/actions.tsx @@ -3,8 +3,8 @@ import * as T from '@/constants/types' import * as React from 'react' import * as Kb from '@/common-adapters' import * as Kbfs from '../common' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' type Props = { onTriggerFilterMobile: () => void diff --git a/shared/fs/nav-header/main-banner.tsx b/shared/fs/nav-header/main-banner.tsx index 73150635e474..4c24a1390ca4 100644 --- a/shared/fs/nav-header/main-banner.tsx +++ b/shared/fs/nav-header/main-banner.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' +import {useCurrentUserState} from '@/stores/current-user' type Props = { onRetry: () => void diff --git a/shared/fs/nav-header/mobile-header.tsx b/shared/fs/nav-header/mobile-header.tsx index 1364d912bc1c..3868d0e25ba9 100644 --- a/shared/fs/nav-header/mobile-header.tsx +++ b/shared/fs/nav-header/mobile-header.tsx @@ -5,8 +5,8 @@ import * as Kbfs from '../common' import type * as T from '@/constants/types' import Actions from './actions' import MainBanner from './main-banner' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' /* * diff --git a/shared/fs/nav-header/title.tsx b/shared/fs/nav-header/title.tsx index a1419632345f..1bcb3e156a77 100644 --- a/shared/fs/nav-header/title.tsx +++ b/shared/fs/nav-header/title.tsx @@ -3,7 +3,7 @@ import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import * as Kbfs from '../common' import {useSafeNavigation} from '@/util/safe-navigation' -import * as FS from '@/constants/fs' +import * as FS from '@/stores/fs' type Props = { path: T.FS.Path diff --git a/shared/fs/routes.tsx b/shared/fs/routes.tsx index f96c5d4f17c3..f9d6796ef092 100644 --- a/shared/fs/routes.tsx +++ b/shared/fs/routes.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as T from '@/constants/types' import * as C from '@/constants' -import * as FS from '@/constants/fs' +import * as FS from '@/stores/fs' import {Actions, MainBanner, MobileHeader, Title} from './nav-header' const FsRoot = React.lazy(async () => import('.')) diff --git a/shared/fs/top-bar/loading.tsx b/shared/fs/top-bar/loading.tsx index 1aee292386c8..214ecb0a9539 100644 --- a/shared/fs/top-bar/loading.tsx +++ b/shared/fs/top-bar/loading.tsx @@ -1,8 +1,8 @@ import * as T from '@/constants/types' import * as C from '@/constants' import * as Kb from '@/common-adapters' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' // The behavior is to only show spinner when user first time lands on a screen // and when don't have the data that drives it yet. Since RPCs happen diff --git a/shared/fs/top-bar/sort.tsx b/shared/fs/top-bar/sort.tsx index f2057895d10f..30db41b3f6be 100644 --- a/shared/fs/top-bar/sort.tsx +++ b/shared/fs/top-bar/sort.tsx @@ -2,8 +2,8 @@ import * as C from '@/constants' import * as T from '@/constants/types' import * as React from 'react' import * as Kb from '@/common-adapters' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' type OwnProps = { path: T.FS.Path diff --git a/shared/fs/top-bar/sync-toggle.tsx b/shared/fs/top-bar/sync-toggle.tsx index 437300313c8d..cdc1e573a46d 100644 --- a/shared/fs/top-bar/sync-toggle.tsx +++ b/shared/fs/top-bar/sync-toggle.tsx @@ -2,8 +2,8 @@ import * as C from '@/constants' import * as T from '@/constants/types' import * as React from 'react' import * as Kb from '@/common-adapters' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' type OwnProps = { tlfPath: T.FS.Path diff --git a/shared/git/delete-repo.tsx b/shared/git/delete-repo.tsx index 88b1804a1f83..1be08287171f 100644 --- a/shared/git/delete-repo.tsx +++ b/shared/git/delete-repo.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as React from 'react' -import * as Git from '@/constants/git' +import * as Git from '@/stores/git' type OwnProps = {id: string} diff --git a/shared/git/index.tsx b/shared/git/index.tsx index 168a9715ed72..aaf29607752f 100644 --- a/shared/git/index.tsx +++ b/shared/git/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Git from '@/constants/git' +import * as Git from '@/stores/git' import * as Kb from '@/common-adapters' import * as React from 'react' import Row, {NewContext} from './row' diff --git a/shared/git/nav-header.tsx b/shared/git/nav-header.tsx index 5dafe65dd6ca..26d239bb94c9 100644 --- a/shared/git/nav-header.tsx +++ b/shared/git/nav-header.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Git from '@/constants/git' +import * as Git from '@/stores/git' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/git/new-repo.tsx b/shared/git/new-repo.tsx index fd7ded7e79f5..2672731dc6a7 100644 --- a/shared/git/new-repo.tsx +++ b/shared/git/new-repo.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' -import * as Git from '@/constants/git' -import * as Teams from '@/constants/teams' +import * as Git from '@/stores/git' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' diff --git a/shared/git/row.tsx b/shared/git/row.tsx index 4402b545eb79..af0167090a53 100644 --- a/shared/git/row.tsx +++ b/shared/git/row.tsx @@ -1,13 +1,13 @@ import * as C from '@/constants' -import * as Git from '@/constants/git' -import * as Teams from '@/constants/teams' +import * as Git from '@/stores/git' +import * as Teams from '@/stores/teams' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import * as React from 'react' import openURL from '@/util/open-url' -import {useTrackerState} from '@/constants/tracker2' -import * as FS from '@/constants/fs' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import * as FS from '@/stores/fs' +import {useCurrentUserState} from '@/stores/current-user' export const NewContext = React.createContext>(new Set()) @@ -28,7 +28,7 @@ const ConnectedRow = React.memo(function ConnectedRow(ownProps: OwnProps) { const isNew = React.useContext(NewContext).has(id) const you = useCurrentUserState(s => s.username) const setTeamRepoSettings = Git.useGitState(s => s.dispatch.setTeamRepoSettings) - const _onBrowseGitRepo = FS.makeActionForOpenPathInFilesTab + const _onBrowseGitRepo = FS.navToPath const navigateAppend = C.useRouterState(s => s.dispatch.navigateAppend) const {url: gitURL, repoID, channelName, teamname, chatDisabled} = git diff --git a/shared/git/select-channel.tsx b/shared/git/select-channel.tsx index a2cee7e5f3cb..a4b40c3af1af 100644 --- a/shared/git/select-channel.tsx +++ b/shared/git/select-channel.tsx @@ -1,5 +1,5 @@ -import * as Git from '@/constants/git' -import * as Teams from '@/constants/teams' +import * as Git from '@/stores/git' +import * as Teams from '@/stores/teams' import {useSafeNavigation} from '@/util/safe-navigation' import * as Kb from '@/common-adapters' import * as React from 'react' diff --git a/shared/incoming-share/index.tsx b/shared/incoming-share/index.tsx index 44c54b0619ab..6a6786f8bdf8 100644 --- a/shared/incoming-share/index.tsx +++ b/shared/incoming-share/index.tsx @@ -1,15 +1,15 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as FsCommon from '@/fs/common' import {MobileSendToChat} from '../chat/send-to-chat' -import {navigateAppend} from '@/constants/router2/util' -import {settingsFeedbackTab} from '@/constants/settings' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' -import {useConfigState} from '@/constants/config' +import {settingsFeedbackTab} from '@/stores/settings' +import * as FS from '@/stores/fs' +import {useConfigState} from '@/stores/config' +import {useFSState} from '@/stores/fs' +import {useRouterState} from '@/stores/router2' export const OriginalOrCompressedButton = ({incomingShareItems}: IncomingShareProps) => { const originalTotalSize = incomingShareItems.reduce((bytes, item) => bytes + (item.originalSize ?? 0), 0) @@ -204,6 +204,7 @@ type IncomingShareWithSelectionProps = IncomingShareProps & { } const IncomingShare = (props: IncomingShareWithSelectionProps) => { + const navigateAppend = useRouterState(s => s.dispatch.navigateAppend) const useOriginalValue = useConfigState(s => s.incomingShareUseOriginal) const {sendPaths, text} = props.incomingShareItems.reduce( ({sendPaths, text}, item) => { @@ -223,7 +224,7 @@ const IncomingShare = (props: IncomingShareWithSelectionProps) => { // Pre-selected conv: navToThread + attachments directly (skip MobileSendToChat) const selectedConversationIDKey = props.selectedConversationIDKey - const canDirectNav = selectedConversationIDKey && Chat.isValidConversationIDKey(selectedConversationIDKey) + const canDirectNav = selectedConversationIDKey && T.Chat.isValidConversationIDKey(selectedConversationIDKey) const hasNavigatedRef = React.useRef(false) React.useEffect(() => { if (!canDirectNav || hasNavigatedRef.current) return @@ -246,7 +247,7 @@ const IncomingShare = (props: IncomingShareWithSelectionProps) => { selected: 'chatAttachmentGetTitles', }) } - }, [canDirectNav, selectedConversationIDKey, sendPaths, text]) + }, [canDirectNav, selectedConversationIDKey, sendPaths, text, navigateAppend]) const header = useHeader(props.incomingShareItems) const footer = useFooter(props.incomingShareItems) diff --git a/shared/ios/Keybase/Info.plist b/shared/ios/Keybase/Info.plist index fb65c31b600d..fc0bc7c9f8f2 100644 --- a/shared/ios/Keybase/Info.plist +++ b/shared/ios/Keybase/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 6.6.0 + 6.7.0 CFBundleSignature ???? CFBundleURLTypes diff --git a/shared/ios/KeybaseShare/Info.plist b/shared/ios/KeybaseShare/Info.plist index d7a771229ac6..b699e9a1d1b1 100644 --- a/shared/ios/KeybaseShare/Info.plist +++ b/shared/ios/KeybaseShare/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 6.6.0 + 6.7.0 CFBundleVersion 200 NSExtension diff --git a/shared/ios/Podfile.lock b/shared/ios/Podfile.lock index 6c16b2c5df90..db6ab05d2fa1 100644 --- a/shared/ios/Podfile.lock +++ b/shared/ios/Podfile.lock @@ -9,7 +9,7 @@ PODS: - EXImageLoader (6.0.0): - ExpoModulesCore - React-Core - - Expo (54.0.31): + - Expo (54.0.33): - boost - DoubleConversion - ExpoModulesCore @@ -52,7 +52,7 @@ PODS: - ExpoModulesCore - ExpoFileSystem (19.0.21): - ExpoModulesCore - - ExpoFont (14.0.10): + - ExpoFont (14.0.11): - ExpoModulesCore - ExpoHaptics (15.0.8): - ExpoModulesCore @@ -116,8 +116,8 @@ PODS: - hermes-engine/Pre-built (= 0.81.5) - hermes-engine/Pre-built (0.81.5) - KBCommon (1.0.0) - - libavif/core (0.11.1) - - libavif/libdav1d (0.11.1): + - libavif/core (1.0.0) + - libavif/libdav1d (1.0.0): - libavif/core - libdav1d (>= 0.6.0) - libdav1d (1.2.0) @@ -133,15 +133,15 @@ PODS: - libwebp/sharpyuv (1.5.0) - libwebp/webp (1.5.0): - libwebp/sharpyuv - - lottie-ios (4.5.0) - - lottie-react-native (7.3.5): + - lottie-ios (4.6.0) + - lottie-react-native (7.3.6): - boost - DoubleConversion - fast_float - fmt - glog - hermes-engine - - lottie-ios (= 4.5.0) + - lottie-ios (= 4.6.0) - RCT-Folly - RCT-Folly/Fabric - RCTRequired @@ -1932,7 +1932,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-keyboard-controller (1.20.6): + - react-native-keyboard-controller (1.20.7): - boost - DoubleConversion - fast_float @@ -1950,7 +1950,7 @@ PODS: - React-graphics - React-ImageManager - React-jsi - - react-native-keyboard-controller/common (= 1.20.6) + - react-native-keyboard-controller/common (= 1.20.7) - React-NativeModulesApple - React-RCTFabric - React-renderercss @@ -1961,7 +1961,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-keyboard-controller/common (1.20.6): + - react-native-keyboard-controller/common (1.20.7): - boost - DoubleConversion - fast_float @@ -2814,7 +2814,7 @@ PODS: - RNWorklets - SocketRocket - Yoga - - RNScreens (4.18.0): + - RNScreens (4.23.0): - boost - DoubleConversion - fast_float @@ -2841,10 +2841,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNScreens/common (= 4.18.0) + - RNScreens/common (= 4.23.0) - SocketRocket - Yoga - - RNScreens/common (4.18.0): + - RNScreens/common (4.23.0): - boost - DoubleConversion - fast_float @@ -2873,7 +2873,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNWorklets (0.7.1): + - RNWorklets (0.7.3): - boost - DoubleConversion - fast_float @@ -2900,10 +2900,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNWorklets/worklets (= 0.7.1) + - RNWorklets/worklets (= 0.7.3) - SocketRocket - Yoga - - RNWorklets/worklets (0.7.1): + - RNWorklets/worklets (0.7.3): - boost - DoubleConversion - fast_float @@ -2930,10 +2930,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNWorklets/worklets/apple (= 0.7.1) + - RNWorklets/worklets/apple (= 0.7.3) - SocketRocket - Yoga - - RNWorklets/worklets/apple (0.7.1): + - RNWorklets/worklets/apple (0.7.3): - boost - DoubleConversion - fast_float @@ -2962,9 +2962,9 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - SDWebImage (5.21.5): - - SDWebImage/Core (= 5.21.5) - - SDWebImage/Core (5.21.5) + - SDWebImage (5.21.6): + - SDWebImage/Core (= 5.21.6) + - SDWebImage/Core (5.21.6) - SDWebImageAVIFCoder (0.11.1): - libavif/core (>= 0.11.0) - SDWebImage (~> 5.10) @@ -3323,13 +3323,13 @@ SPEC CHECKSUMS: EXAV: b60fcf142fae6684d295bc28cd7cfcb3335570ea EXConstants: fce59a631a06c4151602843667f7cfe35f81e271 EXImageLoader: 189e3476581efe3ad4d1d3fb4735b7179eb26f05 - Expo: 5fe0862b0c3de267afbba769891aa65f64c2800c + Expo: ec20d60a9ba93352323b8f773c0e59bc7aa1c492 ExpoAsset: f867e55ceb428aab99e1e8c082b5aee7c159ea18 ExpoCamera: 6a326deb45ba840749652e4c15198317aa78497e ExpoClipboard: b36b287d8356887844bb08ed5c84b5979bb4dd1e ExpoContacts: 1c976e6c0b9be9b256ea038eba5c86f5707a73ce ExpoFileSystem: 858a44267a3e6e9057e0888ad7c7cfbf55d52063 - ExpoFont: 35ac6191ed86bbf56b3ebd2d9154eda9fad5b509 + ExpoFont: f543ce20a228dd702813668b1a07b46f51878d47 ExpoHaptics: d3a6375d8dcc3a1083d003bc2298ff654fafb536 ExpoImage: 686f972bff29525733aa13357f6691dc90aa03d8 ExpoImagePicker: 1af3e4e31512d2f34c95c2a3018a3edc40aee748 @@ -3346,11 +3346,11 @@ SPEC CHECKSUMS: glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 hermes-engine: 9f4dfe93326146a1c99eb535b1cb0b857a3cd172 KBCommon: bd1f35bb07924f4cc57417b00dab6734747b6423 - libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7 + libavif: 5f8e715bea24debec477006f21ef9e95432e254d libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 - lottie-ios: a881093fab623c467d3bce374367755c272bdd59 - lottie-react-native: b01c4b468aed88931afefbde03d790fc4f8b010a + lottie-ios: 8f959969761e9c45d70353667d00af0e5b9cadb3 + lottie-react-native: e542fe4b6e8eddcf893d5a8cef9f8b5c6d2f7331 RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 RCTDeprecation: 5eb1d2eeff5fb91151e8a8eef45b6c7658b6c897 RCTRequired: cebcf9442fc296c9b89ac791dfd463021d9f6f23 @@ -3385,7 +3385,7 @@ SPEC CHECKSUMS: React-Mapbuffer: 017336879e2e0fb7537bbc08c24f34e2384c9260 React-microtasksnativemodule: 63ee6730cec233feab9cdcc0c100dc28a12e4165 react-native-kb: 078843e8c52d210aff0c50cbb4c4abe888c28ee2 - react-native-keyboard-controller: 40b005f1202d68566a2d058ddc148a3ea8d73288 + react-native-keyboard-controller: a9e423beaa20d00a4b9664b0fa37f1cdf3a59975 react-native-netinfo: 64f05e94821ee3f3adcd9b67b35c1480e5564915 react-native-safe-area-context: 0a3b034bb63a5b684dd2f5fffd3c90ef6ed41ee8 react-native-webview: cdce419e8022d0ef6f07db21890631258e7a9e6e @@ -3423,9 +3423,9 @@ SPEC CHECKSUMS: RNCPicker: 35fc66f352403cdfe99d53b541f5180482ca2bc5 RNGestureHandler: 043d32e7e7ae6bdcca06264682d5a078712903a1 RNReanimated: e79d7f42b76ba026e7dc5fb3e3f81991c590d3af - RNScreens: 98771ad898d1c0528fc8139606bbacf5a2e9d237 - RNWorklets: 416ef974c176d76634e34c0aeda83f6b67084a88 - SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838 + RNScreens: 199799bdab32fa1e17ebf938b06fec52033e81e5 + RNWorklets: ff9b9ff38df9accccbbbad692b6e435fec64e24e + SDWebImage: 1bb6a1b84b6fe87b972a102bdc77dd589df33477 SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57 SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 diff --git a/shared/login/index.tsx b/shared/login/index.tsx index cd680fd69a09..1c052d3559d0 100644 --- a/shared/login/index.tsx +++ b/shared/login/index.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -import {useConfigState} from '@/constants/config' -import {useDaemonState} from '@/constants/daemon' +import {useConfigState} from '@/stores/config' +import {useDaemonState} from '@/stores/daemon' const Loading = React.lazy(async () => import('./loading')) const Relogin = React.lazy(async () => import('./relogin/container')) diff --git a/shared/login/join-or-login.tsx b/shared/login/join-or-login.tsx index 543fb0be1d2c..0e4b0c267713 100644 --- a/shared/login/join-or-login.tsx +++ b/shared/login/join-or-login.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' import * as React from 'react' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as Kb from '@/common-adapters' import {InfoIcon} from '@/signup/common' -import {useSignupState} from '@/constants/signup' -import {useProvisionState} from '@/constants/provision' +import {useSignupState} from '@/stores/signup' +import {useProvisionState} from '@/stores/provision' const Intro = () => { const justDeletedSelf = useConfigState(s => s.justDeletedSelf) diff --git a/shared/login/loading.tsx b/shared/login/loading.tsx index 7d48b3dec130..41237f127c88 100644 --- a/shared/login/loading.tsx +++ b/shared/login/loading.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' -import {useDaemonState} from '@/constants/daemon' +import {useDaemonState} from '@/stores/daemon' const SplashContainer = () => { const failedReason = useDaemonState(s => s.handshakeFailedReason) diff --git a/shared/login/recover-password/device-selector.tsx b/shared/login/recover-password/device-selector.tsx index 150fb2d21f5a..e73f81372b15 100644 --- a/shared/login/recover-password/device-selector.tsx +++ b/shared/login/recover-password/device-selector.tsx @@ -1,5 +1,5 @@ import SelectOtherDevice from '@/provision/select-other-device' -import {useState as useRecoverState} from '@/constants/recover-password' +import {useState as useRecoverState} from '@/stores/recover-password' const RecoverPasswordDeviceSelector = () => { const devices = useRecoverState(s => s.devices) diff --git a/shared/login/recover-password/error-modal.tsx b/shared/login/recover-password/error-modal.tsx index efe62179c6c4..14cb7f80d1e5 100644 --- a/shared/login/recover-password/error-modal.tsx +++ b/shared/login/recover-password/error-modal.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import {useState as useRecoverState} from '@/constants/recover-password' -import {useConfigState} from '@/constants/config' +import {useState as useRecoverState} from '@/stores/recover-password' +import {useConfigState} from '@/stores/config' const styles = Kb.Styles.styleSheetCreate(() => ({ padding: { diff --git a/shared/login/recover-password/error.tsx b/shared/login/recover-password/error.tsx index 789cbf323102..c38307524d2e 100644 --- a/shared/login/recover-password/error.tsx +++ b/shared/login/recover-password/error.tsx @@ -2,8 +2,8 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import type {ButtonType} from '@/common-adapters/button' import {SignupScreen} from '@/signup/common' -import {useState as useRecoverState} from '@/constants/recover-password' -import {useConfigState} from '@/constants/config' +import {useState as useRecoverState} from '@/stores/recover-password' +import {useConfigState} from '@/stores/config' const ConnectedError = () => { const loggedIn = useConfigState(s => s.loggedIn) diff --git a/shared/login/recover-password/explain-device.tsx b/shared/login/recover-password/explain-device.tsx index 06890c97dab8..f9bd16930337 100644 --- a/shared/login/recover-password/explain-device.tsx +++ b/shared/login/recover-password/explain-device.tsx @@ -3,7 +3,7 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import type {ButtonType} from '@/common-adapters/button' import {SignupScreen} from '@/signup/common' -import {useState as useRecoverState} from '@/constants/recover-password' +import {useState as useRecoverState} from '@/stores/recover-password' const ConnectedExplainDevice = () => { const ed = useRecoverState(s => s.explainedDevice) diff --git a/shared/login/recover-password/paper-key.tsx b/shared/login/recover-password/paper-key.tsx index 5e2a332af6d9..9d1983af7a1d 100644 --- a/shared/login/recover-password/paper-key.tsx +++ b/shared/login/recover-password/paper-key.tsx @@ -3,7 +3,7 @@ import * as Kb from '@/common-adapters' import * as React from 'react' import type {ButtonType} from '@/common-adapters/button' import {SignupScreen} from '@/signup/common' -import {useState as useRecoverState} from '@/constants/recover-password' +import {useState as useRecoverState} from '@/stores/recover-password' const PaperKey = () => { const error = useRecoverState(s => s.paperKeyError) diff --git a/shared/login/recover-password/password.tsx b/shared/login/recover-password/password.tsx index 452ca011543a..2e61426b7edc 100644 --- a/shared/login/recover-password/password.tsx +++ b/shared/login/recover-password/password.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import {UpdatePassword} from '@/settings/password' -import {useState as useRecoverState} from '@/constants/recover-password' +import {useState as useRecoverState} from '@/stores/recover-password' const Password = () => { const error = useRecoverState(s => s.passwordError) diff --git a/shared/login/recover-password/prompt-reset-shared.tsx b/shared/login/recover-password/prompt-reset-shared.tsx index da223aa06338..dff66e082417 100644 --- a/shared/login/recover-password/prompt-reset-shared.tsx +++ b/shared/login/recover-password/prompt-reset-shared.tsx @@ -1,12 +1,12 @@ import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' +import * as AutoReset from '@/stores/autoreset' import * as React from 'react' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' import * as T from '@/constants/types' import {SignupScreen} from '@/signup/common' import type {ButtonType} from '@/common-adapters/button' -import {useState as useRecoverState} from '@/constants/recover-password' +import {useState as useRecoverState} from '@/stores/recover-password' export type Props = { resetPassword?: boolean diff --git a/shared/login/relogin/container.tsx b/shared/login/relogin/container.tsx index 2e80b7856bc6..ee18a0195aa3 100644 --- a/shared/login/relogin/container.tsx +++ b/shared/login/relogin/container.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' import * as React from 'react' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import Login from '.' import sortBy from 'lodash/sortBy' -import {useState as useRecoverState} from '@/constants/recover-password' -import {useSignupState} from '@/constants/signup' -import {useProvisionState} from '@/constants/provision' +import {useState as useRecoverState} from '@/stores/recover-password' +import {useSignupState} from '@/stores/signup' +import {useProvisionState} from '@/stores/provision' const needPasswordError = 'passphrase cannot be empty' diff --git a/shared/login/reset/confirm.tsx b/shared/login/reset/confirm.tsx index 5ce7f9b7d53e..e8a22339520d 100644 --- a/shared/login/reset/confirm.tsx +++ b/shared/login/reset/confirm.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' +import * as AutoReset from '@/stores/autoreset' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import {useState as useRecoverState} from '@/constants/recover-password' +import {useState as useRecoverState} from '@/stores/recover-password' const ConfirmReset = () => { const hasWallet = AutoReset.useAutoResetState(s => s.hasWallet) diff --git a/shared/login/reset/modal.tsx b/shared/login/reset/modal.tsx index 3aa3e2b5ef66..9e3e48bd8db4 100644 --- a/shared/login/reset/modal.tsx +++ b/shared/login/reset/modal.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' +import * as AutoReset from '@/stores/autoreset' import * as React from 'react' import * as Kb from '@/common-adapters' import {formatDurationForAutoreset} from '@/util/timestamp' diff --git a/shared/login/reset/password-enter.tsx b/shared/login/reset/password-enter.tsx index c749c5f801e6..010a4cdaf290 100644 --- a/shared/login/reset/password-enter.tsx +++ b/shared/login/reset/password-enter.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' +import * as AutoReset from '@/stores/autoreset' import * as React from 'react' import * as Kb from '@/common-adapters' import {SignupScreen} from '@/signup/common' diff --git a/shared/login/reset/password-known.tsx b/shared/login/reset/password-known.tsx index c606206718b4..f955e6b7d183 100644 --- a/shared/login/reset/password-known.tsx +++ b/shared/login/reset/password-known.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' +import * as AutoReset from '@/stores/autoreset' import * as React from 'react' import * as Kb from '@/common-adapters' import {SignupScreen} from '@/signup/common' diff --git a/shared/login/reset/waiting.tsx b/shared/login/reset/waiting.tsx index 9dea7f219397..adf2b26d7ace 100644 --- a/shared/login/reset/waiting.tsx +++ b/shared/login/reset/waiting.tsx @@ -3,7 +3,7 @@ import * as Kb from '@/common-adapters' import {SignupScreen} from '@/signup/common' import {addTicker, removeTicker} from '@/util/second-timer' import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' +import * as AutoReset from '@/stores/autoreset' import {useSafeNavigation} from '@/util/safe-navigation' import {formatDurationForAutoreset as formatDuration} from '@/util/timestamp' diff --git a/shared/login/routes.tsx b/shared/login/routes.tsx index f6d31a7081eb..06d196706d66 100644 --- a/shared/login/routes.tsx +++ b/shared/login/routes.tsx @@ -5,7 +5,7 @@ import {InfoIcon} from '@/signup/common' import {newRoutes as provisionRoutes} from '../provision/routes-sub' import {sharedNewRoutes as settingsRoutes} from '../settings/routes' import {newRoutes as signupRoutes} from './signup/routes' -import {settingsFeedbackTab} from '@/constants/settings/util' +import {settingsFeedbackTab} from '@/constants/settings' const recoverPasswordStyles = Kb.Styles.styleSheetCreate(() => ({ questionBox: Kb.Styles.padding(Kb.Styles.globalMargins.tiny, Kb.Styles.globalMargins.tiny, 0), diff --git a/shared/login/signup/error.tsx b/shared/login/signup/error.tsx index f646b1337698..04ff47e90e7b 100644 --- a/shared/login/signup/error.tsx +++ b/shared/login/signup/error.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import {Wrapper, ContinueButton} from './common' -import {useSignupState} from '@/constants/signup' +import {useSignupState} from '@/stores/signup' const ConnectedSignupError = () => { const error = useSignupState(s => s.signupError) diff --git a/shared/menubar/chat-container.desktop.tsx b/shared/menubar/chat-container.desktop.tsx index e00fdc6bc561..6bcff1bb9438 100644 --- a/shared/menubar/chat-container.desktop.tsx +++ b/shared/menubar/chat-container.desktop.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as R from '@/constants/remote' import * as RemoteGen from '../actions/remote-gen' import * as Kb from '@/common-adapters' diff --git a/shared/menubar/files-container.desktop.tsx b/shared/menubar/files-container.desktop.tsx index 773a166b515b..aea7e75c0361 100644 --- a/shared/menubar/files-container.desktop.tsx +++ b/shared/menubar/files-container.desktop.tsx @@ -1,4 +1,4 @@ -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as R from '@/constants/remote' import * as T from '@/constants/types' import * as RemoteGen from '../actions/remote-gen' @@ -6,7 +6,7 @@ import * as FsUtil from '@/util/kbfs' import * as TimestampUtil from '@/util/timestamp' import {FilesPreview} from './files.desktop' import type {DeserializeProps} from './remote-serializer.desktop' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' const FilesContainer = (p: Pick) => { const {remoteTlfUpdates} = p diff --git a/shared/menubar/remote-container.desktop.tsx b/shared/menubar/remote-container.desktop.tsx index fa658dbb9793..ed5a78d8ee00 100644 --- a/shared/menubar/remote-container.desktop.tsx +++ b/shared/menubar/remote-container.desktop.tsx @@ -1,14 +1,14 @@ import * as React from 'react' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import Menubar from './index.desktop' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import type {DeserializeProps} from './remote-serializer.desktop' import {useAvatarState} from '@/common-adapters/avatar/store' -import {useUsersState} from '@/constants/users' -import {useFollowerState} from '@/constants/followers' -import {useCurrentUserState} from '@/constants/current-user' -import {useDaemonState} from '@/constants/daemon' -import {useDarkModeState} from '@/constants/darkmode' +import {useUsersState} from '@/stores/users' +import {useFollowerState} from '@/stores/followers' +import {useCurrentUserState} from '@/stores/current-user' +import {useDaemonState} from '@/stores/daemon' +import {useDarkModeState} from '@/stores/darkmode' const RemoteContainer = (d: DeserializeProps) => { const {avatarRefreshCounter, badgeMap, daemonHandshakeState, darkMode, diskSpaceStatus, endEstimate} = d diff --git a/shared/menubar/remote-proxy.desktop.tsx b/shared/menubar/remote-proxy.desktop.tsx index c32e0a8c65e7..4e79d95e0e4e 100644 --- a/shared/menubar/remote-proxy.desktop.tsx +++ b/shared/menubar/remote-proxy.desktop.tsx @@ -1,7 +1,7 @@ // A mirror of the remote menubar windows. import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useConfigState} from '@/constants/config' +import * as Chat from '@/stores/chat2' +import {useConfigState} from '@/stores/config' import * as T from '@/constants/types' import * as React from 'react' import KB2 from '@/util/electron.desktop' @@ -10,16 +10,16 @@ import {intersect} from '@/util/set' import {mapFilterByKey} from '@/util/map' import {serialize, type ProxyProps, type RemoteTlfUpdates} from './remote-serializer.desktop' import {useAvatarState} from '@/common-adapters/avatar/store' -import type * as NotifConstants from '@/constants/notifications' +import type * as NotifConstants from '@/stores/notifications' import {useColorScheme} from 'react-native' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' -import {useFollowerState} from '@/constants/followers' -import {useUsersState} from '@/constants/users' -import {useNotifState} from '@/constants/notifications' -import {useCurrentUserState} from '@/constants/current-user' -import {useDaemonState} from '@/constants/daemon' -import {useDarkModeState} from '@/constants/darkmode' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' +import {useFollowerState} from '@/stores/followers' +import {useUsersState} from '@/stores/users' +import {useNotifState} from '@/stores/notifications' +import {useCurrentUserState} from '@/stores/current-user' +import {useDaemonState} from '@/stores/daemon' +import {useDarkModeState} from '@/stores/darkmode' const {showTray} = KB2.functions diff --git a/shared/override-d.ts/misc/index.d.ts b/shared/override-d.ts/misc/index.d.ts index 07d59acc2e73..9b7bcf3372e9 100644 --- a/shared/override-d.ts/misc/index.d.ts +++ b/shared/override-d.ts/misc/index.d.ts @@ -1,16 +1,3 @@ -declare module 'qrcode-generator' { - const gen: ( - n: number, - s: string - ) => { - addData: (s: string) => void - make: () => void - getModuleCount: () => number - createDataURL: (n: number) => string - } - export default gen -} - declare module 'emoji-datasource-apple/img/apple/sheets/64.png' { var png: string export default png diff --git a/shared/package.json b/shared/package.json index 2b6cb61ae16f..688526747830 100644 --- a/shared/package.json +++ b/shared/package.json @@ -72,20 +72,20 @@ "dependencies": { "@gorhom/bottom-sheet": "5.2.8", "@gorhom/portal": "1.0.14", - "@khanacademy/simple-markdown": "2.1.4", - "@msgpack/msgpack": "3.1.2", + "@khanacademy/simple-markdown": "2.2.1", + "@msgpack/msgpack": "3.1.3", "@react-native-community/netinfo": "11.5.2", "@react-native-masked-view/masked-view": "0.3.2", "@react-native-picker/picker": "2.11.4", - "@react-navigation/bottom-tabs": "7.9.1", - "@react-navigation/core": "7.13.7", - "@react-navigation/native": "7.1.27", - "@react-navigation/native-stack": "7.9.1", + "@react-navigation/bottom-tabs": "7.13.0", + "@react-navigation/core": "7.14.0", + "@react-navigation/native": "7.1.28", + "@react-navigation/native-stack": "7.12.0", "classnames": "2.5.1", "date-fns": "4.1.0", "emoji-datasource-apple": "16.0.0", "emoji-regex": "10.6.0", - "expo": "54.0.31", + "expo": "54.0.33", "expo-av": "16.0.8", "expo-camera": "17.0.10", "expo-clipboard": "8.0.8", @@ -99,15 +99,14 @@ "expo-media-library": "18.2.1", "expo-sms": "14.0.8", "expo-task-manager": "14.0.9", - "framed-msgpack-rpc": "keybase/node-framed-msgpack-rpc#nojima/HOTPOT-use-buffer", - "google-libphonenumber": "3.2.43", + "framed-msgpack-rpc": "keybase/node-framed-msgpack-rpc#nojima/HOTPOT-use-buffer-iserror", + "google-libphonenumber": "3.2.44", "iced-runtime": "1.0.4", - "immer": "11.1.3", + "immer": "11.1.4", "lodash": "4.17.23", - "lottie-react-native": "7.3.5", + "lottie-react-native": "7.3.6", "lottie-web": "5.13.0", "menubar": "9.5.2", - "qrcode-generator": "1.4.4", "react": "19.1.0", "react-dom": "19.1.0", "react-error-boundary": "5.0.0", @@ -115,30 +114,30 @@ "react-list": "0.8.18", "react-native": "0.81.5", "react-native-gesture-handler": "2.30.0", - "react-native-keyboard-controller": "1.20.6", "react-native-kb": "file:../rnmodules/react-native-kb", + "react-native-keyboard-controller": "1.20.7", "react-native-reanimated": "4.2.1", - "react-native-worklets": "0.7.1", "react-native-safe-area-context": "5.6.2", - "react-native-screens": "4.18.0", + "react-native-screens": "4.23.0", "react-native-web": "0.21.2", "react-native-webview": "13.16.0", + "react-native-worklets": "0.7.3", "react-native-zoom-toolkit": "5.0.1", - "react-window": "2.2.5", + "react-window": "2.2.6", "shallowequal": "1.1.0", "uint8array-extras": "1.5.0", "url-parse": "1.5.10", "use-debounce": "10.1.0", "util": "0.12.5", - "zustand": "5.0.10" + "zustand": "5.0.11" }, "devDependencies": { - "@babel/core": "7.28.5", - "@babel/node": "7.28.0", - "@babel/preset-env": "7.28.5", + "@babel/core": "7.29.0", + "@babel/node": "7.29.0", + "@babel/preset-env": "7.29.0", "@babel/preset-react": "7.28.5", "@babel/preset-typescript": "7.28.5", - "@electron/packager": "18.4.4", + "@electron/packager": "19.0.5", "@pmmmwh/react-refresh-webpack-plugin": "0.6.2", "@react-native-community/cli": "20.1.1", "@react-native-community/cli-platform-android": "20.1.1", @@ -149,7 +148,7 @@ "@types/google-libphonenumber": "7.4.30", "@types/lodash": "4.17.23", "@types/lodash-es": "4.17.12", - "@types/react": "19.2.8", + "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/react-is": "19.2.0", "@types/react-list": "0.8.12", @@ -161,11 +160,10 @@ "babel-plugin-module-resolver": "5.0.2", "babel-plugin-react-compiler": "1.0.0", "babel-plugin-react-native-web": "0.21.2", - "babel-preset-expo": "54.0.9", - "circular-dependency-plugin": "5.2.2", - "cross-env": "7.0.3", - "css-loader": "7.1.2", - "electron": "39.2.7", + "babel-preset-expo": "54.0.10", + "cross-env": "10.1.0", + "css-loader": "7.1.3", + "electron": "40.4.0", "eslint": "9.39.2", "eslint-plugin-deprecation": "3.0.0", "eslint-plugin-promise": "7.2.1", @@ -173,11 +171,11 @@ "eslint-plugin-react-compiler": "19.1.0-rc.2", "eslint-plugin-react-hooks": "7.0.1", "fs-extra": "11.3.3", - "html-webpack-plugin": "5.6.5", + "html-webpack-plugin": "5.6.6", "json5": "2.2.3", "null-loader": "4.0.1", "patch-package": "8.0.1", - "prettier": "3.8.0", + "prettier": "3.8.1", "react-refresh": "0.18.0", "react-scan": "0.4.3", "react-test-renderer": "19.1.0", @@ -185,15 +183,15 @@ "style-loader": "4.0.0", "terser-webpack-plugin": "5.3.16", "typescript": "5.9.3", - "typescript-eslint": "8.53.0", - "webpack": "5.104.1", + "typescript-eslint": "8.55.0", + "webpack": "5.105.2", "webpack-cli": "6.0.1", "webpack-dev-server": "5.2.3", "webpack-merge": "6.0.1" }, "resolutions": { "**/purepack": "keybase/nullModule", - "**/@types/react": "19.2.8" + "**/@types/react": "19.2.14" }, "typecoverage": { "detail": true, diff --git a/shared/patches/qrcode-generator+1.4.4.patch b/shared/patches/qrcode-generator+1.4.4.patch deleted file mode 100644 index d112877b87a8..000000000000 --- a/shared/patches/qrcode-generator+1.4.4.patch +++ /dev/null @@ -1,29 +0,0 @@ -diff --git a/node_modules/qrcode-generator/qrcode.d.ts b/node_modules/qrcode-generator/qrcode.d.ts -index b34952e..e72e297 100644 ---- a/node_modules/qrcode-generator/qrcode.d.ts -+++ b/node_modules/qrcode-generator/qrcode.d.ts -@@ -53,5 +53,4 @@ interface QRCode { - declare var qrcode : QRCodeFactory; - - declare module 'qrcode-generator' { -- export = qrcode; - } -diff --git a/node_modules/qrcode-generator/qrcode.js b/node_modules/qrcode-generator/qrcode.js -index 76889b5..39c2a5d 100644 ---- a/node_modules/qrcode-generator/qrcode.js -+++ b/node_modules/qrcode-generator/qrcode.js -@@ -2041,10 +2041,10 @@ var qrcode = function() { - //--------------------------------- - // Global Color Map - -- // black -- out.writeByte(0x00); -- out.writeByte(0x00); -- out.writeByte(0x00); -+ // keybase blue -+ out.writeByte(0x4c); -+ out.writeByte(0x8e); -+ out.writeByte(0xff); - - // white - out.writeByte(0xff); diff --git a/shared/people/announcement.tsx b/shared/people/announcement.tsx index 7a6e55dd442c..d3807eff4157 100644 --- a/shared/people/announcement.tsx +++ b/shared/people/announcement.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' import openURL from '@/util/open-url' import * as Kb from '@/common-adapters' import PeopleItem from './item' -import * as Settings from '@/constants/settings/util' -import {usePeopleState} from '@/constants/people' +import * as Settings from '@/constants/settings' +import {usePeopleState} from '@/stores/people' type OwnProps = { appLink?: T.RPCGen.AppLinkType diff --git a/shared/people/container.tsx b/shared/people/container.tsx index 6a5182b99177..fba20b5002cb 100644 --- a/shared/people/container.tsx +++ b/shared/people/container.tsx @@ -2,10 +2,10 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import People from '.' -import {useSignupState} from '@/constants/signup' -import {useProfileState} from '@/constants/profile' -import {usePeopleState, getPeopleDataWaitingKey} from '@/constants/people' -import {useCurrentUserState} from '@/constants/current-user' +import {useSignupState} from '@/stores/signup' +import {useProfileState} from '@/stores/profile' +import {usePeopleState, getPeopleDataWaitingKey} from '@/stores/people' +import {useCurrentUserState} from '@/stores/current-user' const waitToRefresh = 1000 * 60 * 5 diff --git a/shared/people/index.shared.tsx b/shared/people/index.shared.tsx index 66edb1f66e6d..e51585aa6339 100644 --- a/shared/people/index.shared.tsx +++ b/shared/people/index.shared.tsx @@ -7,8 +7,8 @@ import FollowNotification from './follow-notification' import FollowSuggestions from './follow-suggestions' import type {Props} from '.' import Todo from './todo' -import {useSignupState} from '@/constants/signup' -import {usePeopleState} from '@/constants/people' +import {useSignupState} from '@/stores/signup' +import {usePeopleState} from '@/stores/people' // import WotTask from './wot-task' const itemToComponent: (item: T.Immutable, props: Props) => React.ReactNode = ( diff --git a/shared/people/routes.tsx b/shared/people/routes.tsx index 1a73b36b39b9..f6c42a6f7051 100644 --- a/shared/people/routes.tsx +++ b/shared/people/routes.tsx @@ -3,7 +3,7 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import peopleTeamBuilder from '../team-building/page' import ProfileSearch from '../profile/search' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' const HeaderAvatar = () => { const myUsername = useCurrentUserState(s => s.username) diff --git a/shared/people/todo.tsx b/shared/people/todo.tsx index 512f1545b35f..016bc55b3d41 100644 --- a/shared/people/todo.tsx +++ b/shared/people/todo.tsx @@ -1,18 +1,18 @@ import * as C from '@/constants' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as React from 'react' import openURL from '@/util/open-url' import type * as T from '@/constants/types' import type {IconType} from '@/common-adapters/icon.constants-gen' import PeopleItem, {type TaskButton} from './item' import * as Kb from '@/common-adapters' -import {useSettingsPhoneState} from '@/constants/settings-phone' -import {useSettingsEmailState} from '@/constants/settings-email' -import {settingsAccountTab, settingsGitTab} from '@/constants/settings/util' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' -import {usePeopleState, todoTypes} from '@/constants/people' -import {useCurrentUserState} from '@/constants/current-user' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {useSettingsEmailState} from '@/stores/settings-email' +import {settingsAccountTab, settingsGitTab} from '@/constants/settings' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' +import {usePeopleState, todoTypes} from '@/stores/people' +import {useCurrentUserState} from '@/stores/current-user' type TodoOwnProps = { badged: boolean diff --git a/shared/pinentry/remote-container.desktop.tsx b/shared/pinentry/remote-container.desktop.tsx index 97f0967a1e16..316f528a9e14 100644 --- a/shared/pinentry/remote-container.desktop.tsx +++ b/shared/pinentry/remote-container.desktop.tsx @@ -3,7 +3,7 @@ import * as RemoteGen from '../actions/remote-gen' import * as R from '@/constants/remote' import Pinentry from './index.desktop' import type {DeserializeProps} from './remote-serializer.desktop' -import {useDarkModeState} from '@/constants/darkmode' +import {useDarkModeState} from '@/stores/darkmode' const RemoteContainer = (d: DeserializeProps) => { const {darkMode, ...rest} = d diff --git a/shared/pinentry/remote-proxy.desktop.tsx b/shared/pinentry/remote-proxy.desktop.tsx index 587043c04c1d..ea897586694b 100644 --- a/shared/pinentry/remote-proxy.desktop.tsx +++ b/shared/pinentry/remote-proxy.desktop.tsx @@ -6,7 +6,7 @@ import useBrowserWindow from '../desktop/remote/use-browser-window.desktop' import useSerializeProps from '../desktop/remote/use-serialize-props.desktop' import {serialize, type ProxyProps} from './remote-serializer.desktop' import {useColorScheme} from 'react-native' -import {usePinentryState} from '@/constants/pinentry' +import {usePinentryState} from '@/stores/pinentry' const windowOpts = {height: 230, width: 440} diff --git a/shared/pinentry/remote-serializer.desktop.tsx b/shared/pinentry/remote-serializer.desktop.tsx index 96f859f93cce..ca291a3014af 100644 --- a/shared/pinentry/remote-serializer.desktop.tsx +++ b/shared/pinentry/remote-serializer.desktop.tsx @@ -1,5 +1,5 @@ import * as T from '@/constants/types' -import type * as Constants from '@/constants/pinentry' +import type * as Constants from '@/stores/pinentry' import {produce} from 'immer' export type ProxyProps = { diff --git a/shared/profile/add-to-team.tsx b/shared/profile/add-to-team.tsx index 0c0228be9967..a4581d07b18b 100644 --- a/shared/profile/add-to-team.tsx +++ b/shared/profile/add-to-team.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import {FloatingRolePicker, sendNotificationFooter} from '@/teams/role-picker' import * as Kb from '@/common-adapters' diff --git a/shared/profile/confirm-or-pending.tsx b/shared/profile/confirm-or-pending.tsx index e0a49954bb5a..f33f9325224e 100644 --- a/shared/profile/confirm-or-pending.tsx +++ b/shared/profile/confirm-or-pending.tsx @@ -1,4 +1,4 @@ -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {subtitle} from '@/util/platforms' diff --git a/shared/profile/edit-avatar/hooks.tsx b/shared/profile/edit-avatar/hooks.tsx index cc54782f2283..9e5778f69af5 100644 --- a/shared/profile/edit-avatar/hooks.tsx +++ b/shared/profile/edit-avatar/hooks.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' -import * as Teams from '@/constants/teams' +import {useProfileState} from '@/stores/profile' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import type {Props} from '.' diff --git a/shared/profile/edit-profile.tsx b/shared/profile/edit-profile.tsx index 61bb397bab2a..c153a4866cf4 100644 --- a/shared/profile/edit-profile.tsx +++ b/shared/profile/edit-profile.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as React from 'react' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' +import {useCurrentUserState} from '@/stores/current-user' const Container = () => { const username = useCurrentUserState(s => s.username) diff --git a/shared/profile/generic/enter-username.tsx b/shared/profile/generic/enter-username.tsx index 41fee450433c..550411e67338 100644 --- a/shared/profile/generic/enter-username.tsx +++ b/shared/profile/generic/enter-username.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import openURL from '@/util/open-url' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/profile/generic/proofs-list.tsx b/shared/profile/generic/proofs-list.tsx index 20c8dae21a8a..ffbfa6fe9f16 100644 --- a/shared/profile/generic/proofs-list.tsx +++ b/shared/profile/generic/proofs-list.tsx @@ -5,8 +5,8 @@ import type * as T from '@/constants/types' import {SiteIcon} from './shared' import {makeInsertMatcher} from '@/util/string' import {useColorScheme} from 'react-native' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' const Container = () => { const _proofSuggestions = useTrackerState(s => s.proofSuggestions) diff --git a/shared/profile/generic/result.tsx b/shared/profile/generic/result.tsx index db0e0b49a419..765700b8b568 100644 --- a/shared/profile/generic/result.tsx +++ b/shared/profile/generic/result.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' import {SiteIcon} from './shared' diff --git a/shared/profile/pgp/finished/index.desktop.tsx b/shared/profile/pgp/finished/index.desktop.tsx index 89e7f6c192cb..22e9341df3e8 100644 --- a/shared/profile/pgp/finished/index.desktop.tsx +++ b/shared/profile/pgp/finished/index.desktop.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as React from 'react' import * as Kb from '@/common-adapters' import Modal from '@/profile/modal' diff --git a/shared/profile/pgp/generate/index.desktop.tsx b/shared/profile/pgp/generate/index.desktop.tsx index ed1be8203b1a..b144be1e0b6b 100644 --- a/shared/profile/pgp/generate/index.desktop.tsx +++ b/shared/profile/pgp/generate/index.desktop.tsx @@ -1,6 +1,6 @@ import * as Kb from '@/common-adapters' import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import Modal from '@/profile/modal' export default function Generate() { diff --git a/shared/profile/pgp/info/index.desktop.tsx b/shared/profile/pgp/info/index.desktop.tsx index af9859070160..8da249f27ec5 100644 --- a/shared/profile/pgp/info/index.desktop.tsx +++ b/shared/profile/pgp/info/index.desktop.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' import Modal from '@/profile/modal' diff --git a/shared/profile/post-proof.tsx b/shared/profile/post-proof.tsx index ddcc5b7e3b1c..74f1cc9fd63c 100644 --- a/shared/profile/post-proof.tsx +++ b/shared/profile/post-proof.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as React from 'react' import * as Kb from '@/common-adapters' import {subtitle} from '@/util/platforms' import openUrl from '@/util/open-url' import Modal from './modal' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const Container = () => { const platform = useProfileState(s => s.platform) @@ -49,7 +49,7 @@ const Container = () => { break } const platformUserName = username - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) const clearModals = C.useRouterState(s => s.dispatch.clearModals) const onCancel = () => { clearModals() diff --git a/shared/profile/prove-enter-username.tsx b/shared/profile/prove-enter-username.tsx index f64207564d86..5bd8cc0d1398 100644 --- a/shared/profile/prove-enter-username.tsx +++ b/shared/profile/prove-enter-username.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as React from 'react' import * as Kb from '@/common-adapters' import Modal from './modal' diff --git a/shared/profile/prove-website-choice.tsx b/shared/profile/prove-website-choice.tsx index 7eabba0143a9..ff6e664c9f14 100644 --- a/shared/profile/prove-website-choice.tsx +++ b/shared/profile/prove-website-choice.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' import Modal from './modal' diff --git a/shared/profile/revoke.tsx b/shared/profile/revoke.tsx index 5fa5097c28f8..aa34864f41ae 100644 --- a/shared/profile/revoke.tsx +++ b/shared/profile/revoke.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' import capitalize from 'lodash/capitalize' import {subtitle as platformSubtitle} from '@/util/platforms' diff --git a/shared/profile/showcase-team-offer.tsx b/shared/profile/showcase-team-offer.tsx index 0041a0725477..12c43db82598 100644 --- a/shared/profile/showcase-team-offer.tsx +++ b/shared/profile/showcase-team-offer.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import {useTeamsSubscribe} from '@/teams/subscriber' -import {useTrackerState} from '@/constants/tracker2' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import {useCurrentUserState} from '@/stores/current-user' const Container = () => { const waiting = C.useWaitingState(s => s.counts) diff --git a/shared/profile/user/actions/index.tsx b/shared/profile/user/actions/index.tsx index 5695e6e9051c..2dcf058334ef 100644 --- a/shared/profile/user/actions/index.tsx +++ b/shared/profile/user/actions/index.tsx @@ -4,11 +4,11 @@ import * as Kb from '@/common-adapters' import * as React from 'react' import FollowButton from './follow-button' import ChatButton from '@/chat/chat-button' -import {useBotsState} from '@/constants/bots' -import {useTrackerState} from '@/constants/tracker2' -import * as FS from '@/constants/fs' -import {useFollowerState} from '@/constants/followers' -import {useCurrentUserState} from '@/constants/current-user' +import {useBotsState} from '@/stores/bots' +import {useTrackerState} from '@/stores/tracker2' +import * as FS from '@/stores/fs' +import {useFollowerState} from '@/stores/followers' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {username: string} @@ -28,7 +28,7 @@ const Container = (ownProps: OwnProps) => { const navigateAppend = C.useRouterState(s => s.dispatch.navigateAppend) const _onAddToTeam = (username: string) => navigateAppend({props: {username}, selected: 'profileAddToTeam'}) const _onBrowsePublicFolder = (username: string) => - FS.makeActionForOpenPathInFilesTab(T.FS.stringToPath(`/keybase/public/${username}`)) + FS.navToPath(T.FS.stringToPath(`/keybase/public/${username}`)) const _onEditProfile = () => navigateAppend('profileEdit') const changeFollow = useTrackerState(s => s.dispatch.changeFollow) @@ -39,7 +39,7 @@ const Container = (ownProps: OwnProps) => { const _onManageBlocking = (username: string) => navigateAppend({props: {username}, selected: 'chatBlockingModal'}) const _onOpenPrivateFolder = (myUsername: string, theirUsername: string) => - FS.makeActionForOpenPathInFilesTab(T.FS.stringToPath(`/keybase/private/${theirUsername},${myUsername}`)) + FS.navToPath(T.FS.stringToPath(`/keybase/private/${theirUsername},${myUsername}`)) const showUser = useTrackerState(s => s.dispatch.showUser) const _onReload = (username: string) => { showUser(username, false) diff --git a/shared/profile/user/friend.tsx b/shared/profile/user/friend.tsx index ef0815a24d86..462965aaa940 100644 --- a/shared/profile/user/friend.tsx +++ b/shared/profile/user/friend.tsx @@ -1,6 +1,6 @@ -import {useProfileState} from '@/constants/profile' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' -import {useUsersState} from '@/constants/users' +import {useUsersState} from '@/stores/users' type OwnProps = { username: string diff --git a/shared/profile/user/hooks.tsx b/shared/profile/user/hooks.tsx index 0a2a49b3017e..b3848ac98bbd 100644 --- a/shared/profile/user/hooks.tsx +++ b/shared/profile/user/hooks.tsx @@ -2,10 +2,10 @@ import * as C from '@/constants' import type * as T from '@/constants/types' import {type BackgroundColorType} from '.' import {useColorScheme} from 'react-native' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' -import {useFollowerState} from '@/constants/followers' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' +import {useFollowerState} from '@/stores/followers' +import {useCurrentUserState} from '@/stores/current-user' const headerBackgroundColorType = ( state: T.Tracker.DetailsState, diff --git a/shared/profile/user/teams/index.tsx b/shared/profile/user/teams/index.tsx index 7cbc8c427ee2..f78cfca43e24 100644 --- a/shared/profile/user/teams/index.tsx +++ b/shared/profile/user/teams/index.tsx @@ -1,12 +1,12 @@ import * as C from '@/constants' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import * as React from 'react' import OpenMeta from './openmeta' import {default as TeamInfo, type Props as TIProps} from './teaminfo' -import {useTrackerState} from '@/constants/tracker2' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = {username: string} diff --git a/shared/provision/code-page/container.tsx b/shared/provision/code-page/container.tsx index bc731a76d534..a33c2113e0da 100644 --- a/shared/provision/code-page/container.tsx +++ b/shared/provision/code-page/container.tsx @@ -1,13 +1,13 @@ import * as C from '@/constants' -import * as Devices from '@/constants/devices' +import * as Devices from '@/stores/devices' import * as React from 'react' import * as Kb from '@/common-adapters' import QRImage from './qr-image' import QRScan from './qr-scan' import Troubleshooting from '../troubleshooting' import type * as T from '@/constants/types' -import {useCurrentUserState} from '@/constants/current-user' -import {type Device, useProvisionState} from '@/constants/provision' +import {useCurrentUserState} from '@/stores/current-user' +import {type Device, useProvisionState} from '@/stores/provision' const CodePageContainer = () => { const {deviceID, storeDeviceName} = useCurrentUserState( diff --git a/shared/provision/code-page/qr-image.tsx b/shared/provision/code-page/qr-image.tsx index df69d5410cdb..b3afa5342e99 100644 --- a/shared/provision/code-page/qr-image.tsx +++ b/shared/provision/code-page/qr-image.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import {Image2} from '@/common-adapters' -import QRCodeGen from 'qrcode-generator' +import generateQRDataURL from '@/util/qr-code' const Kb = { Image2, @@ -13,11 +13,8 @@ type Props = { const QrImage = React.memo(function QrImage(p: Props) { const {code, cellSize = 8} = p - const qr = QRCodeGen(4, 'L') - qr.addData(code) - qr.make() - const size = qr.getModuleCount() * (cellSize / 2) // retina - const url = qr.createDataURL(cellSize) + const {url, moduleCount} = generateQRDataURL(code, cellSize) + const size = moduleCount * (cellSize / 2) // retina return }) diff --git a/shared/provision/code-page/qr-scan/hooks.tsx b/shared/provision/code-page/qr-scan/hooks.tsx index bf1687854d0b..c6515c36c5d4 100644 --- a/shared/provision/code-page/qr-scan/hooks.tsx +++ b/shared/provision/code-page/qr-scan/hooks.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import {useProvisionState} from '@/constants/provision' +import {useProvisionState} from '@/stores/provision' const useQR = () => { const submitTextCode = useProvisionState(s => s.dispatch.dynamic.submitTextCode) diff --git a/shared/provision/code-page/qr-scan/not-authorized.tsx b/shared/provision/code-page/qr-scan/not-authorized.tsx index ee5774db4a22..cdd21978b3b4 100644 --- a/shared/provision/code-page/qr-scan/not-authorized.tsx +++ b/shared/provision/code-page/qr-scan/not-authorized.tsx @@ -1,8 +1,8 @@ import * as Kb from '@/common-adapters' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const QRScanNotAuthorized = () => { - const onOpenSettings = useConfigState(s => s.dispatch.dynamic.openAppSettings) + const onOpenSettings = useConfigState(s => s.dispatch.defer.openAppSettings) return ( diff --git a/shared/provision/error.tsx b/shared/provision/error.tsx index 93125fdfb30b..a5cafc24ae99 100644 --- a/shared/provision/error.tsx +++ b/shared/provision/error.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' +import * as AutoReset from '@/stores/autoreset' import * as Kb from '@/common-adapters' import type * as React from 'react' import LoginContainer from '../login/forms/container' import openURL from '@/util/open-url' import * as T from '@/constants/types' -import {useProvisionState} from '@/constants/provision' +import {useProvisionState} from '@/stores/provision' const Wrapper = (p: {onBack: () => void; children: React.ReactNode}) => ( diff --git a/shared/provision/forgot-username.tsx b/shared/provision/forgot-username.tsx index 9b01068677b6..ad746c8e7579 100644 --- a/shared/provision/forgot-username.tsx +++ b/shared/provision/forgot-username.tsx @@ -2,8 +2,8 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import {SignupScreen, errorBanner} from '../signup/common' -import {useSettingsPhoneState} from '@/constants/settings-phone' -import {useProvisionState} from '@/constants/provision' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {useProvisionState} from '@/stores/provision' const ForgotUsername = () => { const defaultCountry = useSettingsPhoneState(s => s.defaultCountry) diff --git a/shared/provision/paper-key.tsx b/shared/provision/paper-key.tsx index 8fb1972d6f6a..0ae84be9dc0d 100644 --- a/shared/provision/paper-key.tsx +++ b/shared/provision/paper-key.tsx @@ -2,7 +2,7 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as React from 'react' import {SignupScreen, errorBanner} from '../signup/common' -import {useProvisionState} from '@/constants/provision' +import {useProvisionState} from '@/stores/provision' const Container = () => { const error = useProvisionState(s => s.error) diff --git a/shared/provision/password.tsx b/shared/provision/password.tsx index 3d1f2adddff5..3bef094b69fc 100644 --- a/shared/provision/password.tsx +++ b/shared/provision/password.tsx @@ -3,8 +3,8 @@ import * as Kb from '@/common-adapters' import * as React from 'react' import UserCard from '../login/user-card' import {SignupScreen, errorBanner} from '../signup/common' -import {useState as useRecoverState} from '@/constants/recover-password' -import {useProvisionState} from '@/constants/provision' +import {useState as useRecoverState} from '@/stores/recover-password' +import {useProvisionState} from '@/stores/provision' const Password = () => { const error = useProvisionState(s => s.error) diff --git a/shared/provision/select-other-device-connected.tsx b/shared/provision/select-other-device-connected.tsx index 6f1baa6a45cc..69499e1c7547 100644 --- a/shared/provision/select-other-device-connected.tsx +++ b/shared/provision/select-other-device-connected.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' +import * as AutoReset from '@/stores/autoreset' import * as React from 'react' import {useSafeSubmit} from '@/util/safe-submit' import SelectOtherDevice from './select-other-device' -import {useProvisionState} from '@/constants/provision' +import {useProvisionState} from '@/stores/provision' const SelectOtherDeviceContainer = () => { const devices = useProvisionState(s => s.devices) diff --git a/shared/provision/select-other-device.tsx b/shared/provision/select-other-device.tsx index b39e8c25308e..3ac49ebaba69 100644 --- a/shared/provision/select-other-device.tsx +++ b/shared/provision/select-other-device.tsx @@ -2,7 +2,7 @@ import * as Kb from '@/common-adapters' import * as React from 'react' import DeviceIcon from '../devices/device-icon' import {SignupScreen} from '../signup/common' -import {type Device} from '@/constants/provision' +import {type Device} from '@/stores/provision' type Props = { passwordRecovery?: boolean diff --git a/shared/provision/set-public-name.tsx b/shared/provision/set-public-name.tsx index d25bc7c81f22..3b5d7ba52058 100644 --- a/shared/provision/set-public-name.tsx +++ b/shared/provision/set-public-name.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Devices from '@/constants/devices' +import * as Devices from '@/stores/devices' import {useSafeSubmit} from '@/util/safe-submit' import * as Kb from '@/common-adapters' import * as React from 'react' import debounce from 'lodash/debounce' import {SignupScreen, errorBanner} from '../signup/common' -import * as Provision from '@/constants/provision' +import * as Provision from '@/stores/provision' const SetPublicName = () => { const devices = Provision.useProvisionState(s => s.devices) diff --git a/shared/provision/troubleshooting.tsx b/shared/provision/troubleshooting.tsx index 0f00a0cc0e8b..e7aa4f2318c7 100644 --- a/shared/provision/troubleshooting.tsx +++ b/shared/provision/troubleshooting.tsx @@ -1,9 +1,9 @@ import * as React from 'react' import * as C from '@/constants' -import * as Devices from '@/constants/devices' +import * as Devices from '@/stores/devices' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' -import {useProvisionState} from '@/constants/provision' +import {useProvisionState} from '@/stores/provision' type Props = { mode: 'QR' | 'text' onCancel: () => void diff --git a/shared/provision/username-or-email.tsx b/shared/provision/username-or-email.tsx index 0ead30e44502..fd06d797850d 100644 --- a/shared/provision/username-or-email.tsx +++ b/shared/provision/username-or-email.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' -import * as AutoReset from '@/constants/autoreset' -import {useSignupState} from '@/constants/signup' +import * as AutoReset from '@/stores/autoreset' +import {useSignupState} from '@/stores/signup' import {useSafeSubmit} from '@/util/safe-submit' import * as T from '@/constants/types' import * as React from 'react' @@ -8,9 +8,9 @@ import type {RPCError} from '@/util/errors' import * as Kb from '@/common-adapters' import UserCard from '@/login/user-card' import {SignupScreen, errorBanner} from '@/signup/common' -import {useProvisionState} from '@/constants/provision' +import {useProvisionState} from '@/stores/provision' -type OwnProps = {fromReset?: boolean} +type OwnProps = {fromReset?: boolean; username?: string} const decodeInlineError = (inlineRPCError: RPCError | undefined) => { let inlineError = '' @@ -59,7 +59,12 @@ const UsernameOrEmailContainer = (op: OwnProps) => { }, [_setUsername, waiting] ) - const [username, setUsername] = React.useState(_username) + const [username, setUsername] = React.useState(op.username ?? _username) + React.useEffect(() => { + if (op.username && op.username !== _username) { + _setUsername?.(op.username) + } + }, [op.username, _username, _setUsername]) const onSubmit = React.useCallback(() => { _onSubmit(username) }, [_onSubmit, username]) diff --git a/shared/router-v2/account-switcher/index.tsx b/shared/router-v2/account-switcher/index.tsx index 7ed7d926f309..f4a67159ff4d 100644 --- a/shared/router-v2/account-switcher/index.tsx +++ b/shared/router-v2/account-switcher/index.tsx @@ -1,15 +1,15 @@ import * as C from '@/constants' import './account-switcher.css' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as Kb from '@/common-adapters' import * as React from 'react' import type * as T from '@/constants/types' -import {settingsLogOutTab} from '@/constants/settings/util' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' -import {useUsersState} from '@/constants/users' -import {useCurrentUserState} from '@/constants/current-user' -import {useProvisionState} from '@/constants/provision' +import {settingsLogOutTab} from '@/constants/settings' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' +import {useUsersState} from '@/stores/users' +import {useCurrentUserState} from '@/stores/current-user' +import {useProvisionState} from '@/stores/provision' const prepareAccountRows = ( accountRows: ReadonlyArray, diff --git a/shared/router-v2/common.native.tsx b/shared/router-v2/common.native.tsx index af2f2056c052..8037a3caaedc 100644 --- a/shared/router-v2/common.native.tsx +++ b/shared/router-v2/common.native.tsx @@ -3,7 +3,7 @@ import * as Kb from '@/common-adapters' import {TabActions, type NavigationContainerRef} from '@react-navigation/core' import type {HeaderOptions} from '@react-navigation/elements' import {HeaderLeftArrowCanGoBack} from '@/common-adapters/header-hoc' -import type {NavState} from '@/constants/router2' +import type {NavState} from '@/stores/router2' export const headerDefaultStyle = { get backgroundColor() { diff --git a/shared/router-v2/header/index.desktop.tsx b/shared/router-v2/header/index.desktop.tsx index 2a46eb9be1c3..892c4fbde2a4 100644 --- a/shared/router-v2/header/index.desktop.tsx +++ b/shared/router-v2/header/index.desktop.tsx @@ -6,7 +6,7 @@ import {IconWithPopupDesktop as WhatsNewIconWithPopup} from '@/whats-new/icon' import * as ReactIs from 'react-is' import KB2 from '@/util/electron.desktop' import shallowEqual from 'shallowequal' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const {closeWindow, minimizeWindow, toggleMaximizeWindow} = KB2.functions diff --git a/shared/router-v2/header/syncing-folders.tsx b/shared/router-v2/header/syncing-folders.tsx index 578aa416f544..722a91e2bb98 100644 --- a/shared/router-v2/header/syncing-folders.tsx +++ b/shared/router-v2/header/syncing-folders.tsx @@ -1,9 +1,9 @@ import * as C from '@/constants' -import * as Constants from '@/constants/fs' +import * as Constants from '@/stores/fs' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import PieSlice from '@/fs/common/pie-slice' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' type OwnProps = { negative?: boolean diff --git a/shared/router-v2/hooks.native.tsx b/shared/router-v2/hooks.native.tsx index e957781818b8..0c6ec0747e58 100644 --- a/shared/router-v2/hooks.native.tsx +++ b/shared/router-v2/hooks.native.tsx @@ -1,12 +1,12 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useConfigState} from '@/constants/config' +import * as Chat from '@/stores/chat2' +import {useConfigState} from '@/stores/config' import * as Tabs from '@/constants/tabs' import * as React from 'react' -import {useDeepLinksState} from '@/constants/deeplinks' +import {handleAppLink} from '@/constants/deeplinks' import {Linking} from 'react-native' import {useColorScheme} from 'react-native' -import {usePushState} from '@/constants/push' +import {usePushState} from '@/stores/push' type InitialStateState = 'init' | 'loading' | 'loaded' @@ -112,7 +112,7 @@ export const useInitialState = (loggedInLoaded: boolean) => { } if (url && isValidLink(url)) { - setTimeout(() => url && useDeepLinksState.getState().dispatch.handleAppLink(url), 1) + setTimeout(() => url && handleAppLink(url), 1) } else if (startupFollowUser && !startupConversation) { url = `keybase://profile/show/${startupFollowUser}` diff --git a/shared/router-v2/left-tab-navigator.desktop.tsx b/shared/router-v2/left-tab-navigator.desktop.tsx index bac3cb3c04a5..a30eceb6a845 100644 --- a/shared/router-v2/left-tab-navigator.desktop.tsx +++ b/shared/router-v2/left-tab-navigator.desktop.tsx @@ -4,7 +4,8 @@ import TabBar from './tab-bar.desktop' import {useNavigationBuilder, TabRouter, createNavigatorFactory} from '@react-navigation/core' import type {TypedNavigator, NavigatorTypeBagBase, StaticConfig} from '@react-navigation/native' import type * as Tabs from '@/constants/tabs' -import * as Router2 from '@/constants/router2' +import {useRouterState} from '@/stores/router2' +import {getModalStack} from '@/constants/router2' type BackBehavior = Parameters[0]['backBehavior'] type Props = Parameters[1] & {backBehavior: BackBehavior} @@ -50,7 +51,7 @@ const LeftTabNavigator = React.memo(function LeftTabNavigator({ setRendered(next) }, [selectedRoute, rendered]) - const hasModals = Router2.useRouterState(() => Router2.getModalStack().length > 0) + const hasModals = useRouterState(() => getModalStack().length > 0) return ( diff --git a/shared/router-v2/router.desktop.tsx b/shared/router-v2/router.desktop.tsx index 3173b32abf55..51816647e878 100644 --- a/shared/router-v2/router.desktop.tsx +++ b/shared/router-v2/router.desktop.tsx @@ -1,6 +1,6 @@ import * as Common from './common.desktop' import * as C from '@/constants' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as Kb from '@/common-adapters' import * as React from 'react' import * as Shared from './router.shared' @@ -15,8 +15,8 @@ import {createNativeStackNavigator} from '@react-navigation/native-stack' import {modalRoutes, routes, loggedOutRoutes, tabRoots} from './routes' import {registerDebugClear} from '@/util/debug' import type {RootParamList} from '@/router-v2/route-params' -import {useCurrentUserState} from '@/constants/current-user' -import {useDaemonState} from '@/constants/daemon' +import {useCurrentUserState} from '@/stores/current-user' +import {useDaemonState} from '@/stores/daemon' import type {NativeStackNavigationOptions} from '@react-navigation/native-stack' import './router.css' diff --git a/shared/router-v2/router.native.tsx b/shared/router-v2/router.native.tsx index f807285c2920..53099d8043d7 100644 --- a/shared/router-v2/router.native.tsx +++ b/shared/router-v2/router.native.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Constants from '@/constants/router2' -import {useConfigState} from '@/constants/config' -import {useDarkModeState} from '@/constants/darkmode' +import * as Constants from '@/stores/router2' +import {useConfigState} from '@/stores/config' +import {useDarkModeState} from '@/stores/darkmode' import * as Kb from '@/common-adapters' import * as React from 'react' import * as Shared from './router.shared' @@ -20,7 +20,7 @@ import * as Hooks from './hooks.native' import * as TabBar from './tab-bar.native' import type {RootParamList} from '@/router-v2/route-params' import {useColorScheme} from 'react-native' -import {useDaemonState} from '@/constants/daemon' +import {useDaemonState} from '@/stores/daemon' if (module.hot) { module.hot.accept('', () => {}) @@ -68,7 +68,7 @@ const tabStacks = tabs.map(tab => ( name={tab} listeners={{ tabLongPress: () => { - C.useRouterState.getState().dispatch.dynamic.tabLongPress?.(tab) + C.useRouterState.getState().dispatch.defer.tabLongPress?.(tab) }, }} component={TabStack} diff --git a/shared/router-v2/router.shared.tsx b/shared/router-v2/router.shared.tsx index 7a88668f6157..e0bb7847af68 100644 --- a/shared/router-v2/router.shared.tsx +++ b/shared/router-v2/router.shared.tsx @@ -4,8 +4,8 @@ import * as React from 'react' import {Splash} from '../login/loading' import type {Theme} from '@react-navigation/native' import {colors, darkColors, themed} from '@/styles/colors' -import {useFSState} from '@/constants/fs' -import {useDarkModeState} from '@/constants/darkmode' +import {useFSState} from '@/stores/fs' +import {useDarkModeState} from '@/stores/darkmode' export const SimpleLoading = React.memo(function SimpleLoading() { return ( diff --git a/shared/router-v2/tab-bar.desktop.tsx b/shared/router-v2/tab-bar.desktop.tsx index 843f25c36ead..67baf715faf2 100644 --- a/shared/router-v2/tab-bar.desktop.tsx +++ b/shared/router-v2/tab-bar.desktop.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as Kbfs from '@/fs/common' import * as Platforms from '@/constants/platform' import * as T from '@/constants/types' @@ -13,13 +13,13 @@ import openURL from '@/util/open-url' import {isLinux} from '@/constants/platform' import KB2 from '@/util/electron.desktop' import './tab-bar.css' -import {settingsLogOutTab} from '@/constants/settings/util' -import {useTrackerState} from '@/constants/tracker2' -import {useFSState} from '@/constants/fs' -import {useProfileState} from '@/constants/profile' -import {useNotifState} from '@/constants/notifications' -import {useCurrentUserState} from '@/constants/current-user' -import {useProvisionState} from '@/constants/provision' +import {settingsLogOutTab} from '@/constants/settings' +import {useTrackerState} from '@/stores/tracker2' +import {useFSState} from '@/stores/fs' +import {useProfileState} from '@/stores/profile' +import {useNotifState} from '@/stores/notifications' +import {useCurrentUserState} from '@/stores/current-user' +import {useProvisionState} from '@/stores/provision' const {hideWindow, ctlQuit} = KB2.functions diff --git a/shared/router-v2/tab-bar.native.tsx b/shared/router-v2/tab-bar.native.tsx index 71bb34a5ff8f..30671cb861f4 100644 --- a/shared/router-v2/tab-bar.native.tsx +++ b/shared/router-v2/tab-bar.native.tsx @@ -6,8 +6,8 @@ import * as Shared from './router.shared' import {View} from 'react-native' import {useSafeAreaFrame} from 'react-native-safe-area-context' import {useColorScheme} from 'react-native' -import {usePushState} from '@/constants/push' -import {useNotifState} from '@/constants/notifications' +import {usePushState} from '@/stores/push' +import {useNotifState} from '@/stores/notifications' const settingsTabChildren = [Tabs.gitTab, Tabs.devicesTab, Tabs.settingsTab] as const const tabs = C.isTablet ? Tabs.tabletTabs : Tabs.phoneTabs diff --git a/shared/settings/account/add-modals.tsx b/shared/settings/account/add-modals.tsx index f5736b62172a..5d203251e85d 100644 --- a/shared/settings/account/add-modals.tsx +++ b/shared/settings/account/add-modals.tsx @@ -6,8 +6,8 @@ import {EnterEmailBody} from '@/signup/email' import {EnterPhoneNumberBody} from '@/signup/phone-number' import VerifyBody from '@/signup/phone-number/verify-body' import {e164ToDisplay} from '@/util/phone-numbers' -import {useSettingsPhoneState} from '@/constants/settings-phone' -import {useSettingsEmailState} from '@/constants/settings-email' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {useSettingsEmailState} from '@/stores/settings-email' export const Email = () => { const nav = useSafeNavigation() diff --git a/shared/settings/account/confirm-delete.tsx b/shared/settings/account/confirm-delete.tsx index cec9eaa58255..1b4fa2d6f9aa 100644 --- a/shared/settings/account/confirm-delete.tsx +++ b/shared/settings/account/confirm-delete.tsx @@ -2,8 +2,8 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as PhoneUtil from '@/util/phone-numbers' import {useSafeNavigation} from '@/util/safe-navigation' -import {useSettingsPhoneState} from '@/constants/settings-phone' -import {useSettingsEmailState} from '@/constants/settings-email' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {useSettingsEmailState} from '@/stores/settings-email' type OwnProps = { address: string diff --git a/shared/settings/account/email-phone-row.tsx b/shared/settings/account/email-phone-row.tsx index b3b7f6e32a3d..1eec1a6336e7 100644 --- a/shared/settings/account/email-phone-row.tsx +++ b/shared/settings/account/email-phone-row.tsx @@ -2,8 +2,8 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import {useSettingsPhoneState} from '@/constants/settings-phone' -import {useSettingsEmailState} from '@/constants/settings-email' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {useSettingsEmailState} from '@/stores/settings-email' const addSpacer = (into: string, add: string) => { return into + (into.length ? ' • ' : '') + add diff --git a/shared/settings/account/index.tsx b/shared/settings/account/index.tsx index 1cc8a39c9352..c45c1d9725b0 100644 --- a/shared/settings/account/index.tsx +++ b/shared/settings/account/index.tsx @@ -2,10 +2,10 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import type * as React from 'react' import EmailPhoneRow from './email-phone-row' -import {usePWState} from '@/constants/settings-password' -import {useSettingsPhoneState} from '@/constants/settings-phone' -import {useSettingsEmailState} from '@/constants/settings-email' -import {useSettingsState, settingsPasswordTab} from '@/constants/settings' +import {usePWState} from '@/stores/settings-password' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {useSettingsEmailState} from '@/stores/settings-email' +import {useSettingsState, settingsPasswordTab} from '@/stores/settings' export const SettingsSection = ({children}: {children: React.ReactNode}) => ( diff --git a/shared/settings/advanced.tsx b/shared/settings/advanced.tsx index cb9414ddbd4d..7f926fdc196a 100644 --- a/shared/settings/advanced.tsx +++ b/shared/settings/advanced.tsx @@ -3,10 +3,10 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as React from 'react' import {ProxySettings} from './proxy' -import {useSettingsState, traceInProgressKey, processorProfileInProgressKey} from '@/constants/settings' -import {usePWState} from '@/constants/settings-password' -import {useFSState} from '@/constants/fs' -import {useConfigState} from '@/constants/config' +import {useSettingsState, traceInProgressKey, processorProfileInProgressKey} from '@/stores/settings' +import {usePWState} from '@/stores/settings-password' +import {useFSState} from '@/stores/fs' +import {useConfigState} from '@/stores/config' let initialUseNativeFrame: boolean | undefined diff --git a/shared/settings/archive/index.tsx b/shared/settings/archive/index.tsx index 3ec5cf413eee..ab07df9512c9 100644 --- a/shared/settings/archive/index.tsx +++ b/shared/settings/archive/index.tsx @@ -3,9 +3,10 @@ import * as C from '@/constants' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {formatTimeForConversationList, formatTimeForChat} from '@/util/timestamp' -import {useArchiveState} from '@/constants/archive' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import {useArchiveState} from '@/stores/archive' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' +import {showShareActionSheet} from '@/util/platform-specific' const ChatJob = React.memo(function ChatJob(p: {index: number; id: string}) { const {id, index} = p @@ -29,7 +30,7 @@ const ChatJob = React.memo(function ChatJob(p: {index: number; id: string}) { resume(id) }, [resume, id]) - const openFinder = useFSState(s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop) + const openFinder = useFSState(s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop) const onShowFinder = React.useCallback(() => { if (!job) return openFinder?.(job.outPath) @@ -37,7 +38,7 @@ const ChatJob = React.memo(function ChatJob(p: {index: number; id: string}) { const onShare = React.useCallback(() => { if (!job?.outPath) return - C.PlatformSpecific.showShareActionSheet({ + showShareActionSheet({ filePath: job.outPath, mimeType: 'application/zip', }) @@ -152,7 +153,7 @@ const KBFSJob = React.memo(function KBFSJob(p: {index: number; id: string}) { loadKBFSJobFreshness(id) }) - const openFinder = useFSState(s => s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop) + const openFinder = useFSState(s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop) const onShowFinder = React.useCallback(() => { if (Kb.Styles.isMobile || !job) { return @@ -164,10 +165,7 @@ const KBFSJob = React.memo(function KBFSJob(p: {index: number; id: string}) { if (!Kb.Styles.isMobile || !job) { return } - C.PlatformSpecific.showShareActionSheet({ - filePath: job.zipFilePath, - mimeType: 'application/zip', - }) + showShareActionSheet({filePath: job.zipFilePath, mimeType: 'application/zip'}) .then(() => {}) .catch(() => {}) }, [job]) diff --git a/shared/settings/archive/modal.tsx b/shared/settings/archive/modal.tsx index ff13598e2693..1e77cb8b0a8c 100644 --- a/shared/settings/archive/modal.tsx +++ b/shared/settings/archive/modal.tsx @@ -4,8 +4,10 @@ import * as C from '@/constants' import type * as T from '@/constants/types' import {pickSave} from '@/util/pick-files' import * as FsCommon from '@/fs/common' -import {useArchiveState} from '@/constants/archive' -import {settingsArchiveTab} from '@/constants/settings' +import {useArchiveState} from '@/stores/archive' +import {settingsArchiveTab} from '@/stores/settings' +import {useCurrentUserState} from '@/stores/current-user' +import {getConvoState} from '@/stores/convostate' type Props = | {type: 'chatID'; conversationIDKey: T.Chat.ConversationIDKey} @@ -16,12 +18,29 @@ type Props = | {type: 'fsPath'; path: string} | {type: 'git'; gitURL: string} +const chatIDToDisplayname = (conversationIDKey: string) => { + const you = useCurrentUserState.getState().username + const cs = getConvoState(conversationIDKey) + const m = cs.meta + if (m.teamname) { + if (m.channelname) { + return `${m.teamname}#${m.channelname}` + } + return m.teamname + } + + const participants = cs.participants.name + if (participants.length === 1) { + return participants[0] ?? '' + } + return participants.filter(username => username !== you).join(',') +} + const ArchiveModal = (p: Props) => { const {type} = p - const chatIDToDisplayname = useArchiveState(s => s.chatIDToDisplayname) const displayname = React.useMemo(() => { return p.type === 'chatID' ? chatIDToDisplayname(p.conversationIDKey) : '' - }, [p, chatIDToDisplayname]) + }, [p]) let defaultPath = '' if (C.isElectron) { diff --git a/shared/settings/chat.tsx b/shared/settings/chat.tsx index 5a0fc327fbe0..1759ee2dbaa1 100644 --- a/shared/settings/chat.tsx +++ b/shared/settings/chat.tsx @@ -1,13 +1,13 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as T from '@/constants/types' import * as React from 'react' import Group from './group' -import {useSettingsChatState as useSettingsChatState} from '@/constants/settings-chat' -import {useSettingsNotifState} from '@/constants/settings-notifications' -import {useSettingsState} from '@/constants/settings' -import {useConfigState} from '@/constants/config' +import {useSettingsChatState as useSettingsChatState} from '@/stores/settings-chat' +import {useSettingsNotifState} from '@/stores/settings-notifications' +import {useSettingsState} from '@/stores/settings' +import {useConfigState} from '@/stores/config' const emptyList = new Array() diff --git a/shared/settings/contacts-joined.tsx b/shared/settings/contacts-joined.tsx index 0650fa5d2e47..994dcd5a5a85 100644 --- a/shared/settings/contacts-joined.tsx +++ b/shared/settings/contacts-joined.tsx @@ -4,9 +4,9 @@ import type * as T from '@/constants/types' import {useSafeNavigation} from '@/util/safe-navigation' import * as React from 'react' import UnconnectedFollowButton from '@/profile/user/actions/follow-button' -import {useSettingsContactsState} from '@/constants/settings-contacts' -import {useTrackerState} from '@/constants/tracker2' -import {useFollowerState} from '@/constants/followers' +import {useSettingsContactsState} from '@/stores/settings-contacts' +import {useTrackerState} from '@/stores/tracker2' +import {useFollowerState} from '@/stores/followers' const renderItem = (_: number, item: T.RPCGen.ProcessedContact) => diff --git a/shared/settings/db-nuke.confirm.tsx b/shared/settings/db-nuke.confirm.tsx index 5c4b71083a5d..d286b5367ddd 100644 --- a/shared/settings/db-nuke.confirm.tsx +++ b/shared/settings/db-nuke.confirm.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import {useSettingsState} from '@/constants/settings' +import {useSettingsState} from '@/stores/settings' const DbNukeConfirm = () => { const navigateUp = C.useRouterState(s => s.dispatch.navigateUp) diff --git a/shared/settings/delete-confirm/check-passphrase.native.tsx b/shared/settings/delete-confirm/check-passphrase.native.tsx index a861c09207b8..00010f8bf4db 100644 --- a/shared/settings/delete-confirm/check-passphrase.native.tsx +++ b/shared/settings/delete-confirm/check-passphrase.native.tsx @@ -2,7 +2,7 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' -import {useSettingsState} from '@/constants/settings' +import {useSettingsState} from '@/stores/settings' const CheckPassphraseMobile = () => { const [password, setPassword] = React.useState('') diff --git a/shared/settings/delete-confirm/index.tsx b/shared/settings/delete-confirm/index.tsx index 1a4a243a2ca5..e946c68aa858 100644 --- a/shared/settings/delete-confirm/index.tsx +++ b/shared/settings/delete-confirm/index.tsx @@ -2,9 +2,9 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' -import {usePWState} from '@/constants/settings-password' -import {useSettingsState} from '@/constants/settings' -import {useCurrentUserState} from '@/constants/current-user' +import {usePWState} from '@/stores/settings-password' +import {useSettingsState} from '@/stores/settings' +import {useCurrentUserState} from '@/stores/current-user' type CheckboxesProps = { checkData: boolean diff --git a/shared/settings/disable-cert-pinning-modal.tsx b/shared/settings/disable-cert-pinning-modal.tsx index b84956f0114d..8fe63caf18c2 100644 --- a/shared/settings/disable-cert-pinning-modal.tsx +++ b/shared/settings/disable-cert-pinning-modal.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import {useSettingsState} from '@/constants/settings' +import {useSettingsState} from '@/stores/settings' const DisableCertPinningModal = () => { const navigateUp = C.useRouterState(s => s.dispatch.navigateUp) diff --git a/shared/settings/display.tsx b/shared/settings/display.tsx index b95ba9a6e786..f7ab52aafed4 100644 --- a/shared/settings/display.tsx +++ b/shared/settings/display.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import logger from '@/logger' -import {useConfigState} from '@/constants/config' -import * as DarkMode from '@/constants/darkmode' +import {useConfigState} from '@/stores/config' +import * as DarkMode from '@/stores/darkmode' const Display = () => { const allowAnimatedEmojis = useConfigState(s => s.allowAnimatedEmojis) diff --git a/shared/settings/feedback/container.desktop.tsx b/shared/settings/feedback/container.desktop.tsx index 83933ea0ad0d..5ebef2dcdd73 100644 --- a/shared/settings/feedback/container.desktop.tsx +++ b/shared/settings/feedback/container.desktop.tsx @@ -2,7 +2,7 @@ import * as C from '@/constants' import Feedback from '.' import type {Props} from './container' import {useSendFeedback} from './shared' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const Container = (ownProps: Props) => { const {sendFeedback, error} = useSendFeedback() diff --git a/shared/settings/feedback/container.native.tsx b/shared/settings/feedback/container.native.tsx index b88db320114e..a504f4ab065a 100644 --- a/shared/settings/feedback/container.native.tsx +++ b/shared/settings/feedback/container.native.tsx @@ -1,5 +1,5 @@ -import {useConfigState} from '@/constants/config' -import {useCurrentUserState} from '@/constants/current-user' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' import * as Kb from '@/common-adapters' import * as React from 'react' import Feedback from '.' @@ -9,7 +9,7 @@ import {getExtraChatLogsForLogSend} from './shared' import {isAndroid, version, pprofDir} from '@/constants/platform' import {logSend, appVersionName, appVersionCode} from 'react-native-kb' import type {Props as OwnProps} from './container' -import {usePushState} from '@/constants/push' +import {usePushState} from '@/stores/push' export type Props = { chat: object diff --git a/shared/settings/files/hooks.tsx b/shared/settings/files/hooks.tsx index 7cd0b2d730c2..e8e29a846900 100644 --- a/shared/settings/files/hooks.tsx +++ b/shared/settings/files/hooks.tsx @@ -1,6 +1,6 @@ import {defaultNotificationThreshold} from '.' import * as C from '@/constants' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const useFiles = () => { const {areSettingsLoading, setSpaceAvailableNotificationThreshold, spaceAvailableNotificationThreshold} = diff --git a/shared/settings/files/index.desktop.tsx b/shared/settings/files/index.desktop.tsx index c8abd4d17097..f62f7811fba2 100644 --- a/shared/settings/files/index.desktop.tsx +++ b/shared/settings/files/index.desktop.tsx @@ -6,8 +6,8 @@ import * as Kbfs from '@/fs/common' import RefreshDriverStatusOnMount from '@/fs/common/refresh-driver-status-on-mount' import RefreshSettings from './refresh-settings' import useFiles from './hooks' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' type Props = ReturnType export const allowedNotificationThresholds = [100 * 1024 ** 2, 1024 ** 3, 3 * 1024 ** 3, 10 * 1024 ** 3] @@ -58,7 +58,7 @@ const FinderIntegration = () => { C.useShallow(s => ({ driverDisable: s.dispatch.driverDisable, driverStatus: s.sfmi.driverStatus, - openLocalPathInSystemFileManagerDesktop: s.dispatch.dynamic.openLocalPathInSystemFileManagerDesktop, + openLocalPathInSystemFileManagerDesktop: s.dispatch.defer.openLocalPathInSystemFileManagerDesktop, preferredMountDirs: s.sfmi.preferredMountDirs, })) ) diff --git a/shared/settings/files/index.native.tsx b/shared/settings/files/index.native.tsx index 66ecd2edf2f0..bbec8c0123d0 100644 --- a/shared/settings/files/index.native.tsx +++ b/shared/settings/files/index.native.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import useFiles from './hooks' -import * as FS from '@/constants/fs' -import {useFSState} from '@/constants/fs' +import * as FS from '@/stores/fs' +import {useFSState} from '@/stores/fs' type Props = ReturnType export const allowedNotificationThresholds = [100 * 1024 ** 2, 1024 ** 3, 3 * 1024 ** 3, 10 * 1024 ** 3] diff --git a/shared/settings/files/refresh-settings.tsx b/shared/settings/files/refresh-settings.tsx index aa4da1e72b56..383484e7b579 100644 --- a/shared/settings/files/refresh-settings.tsx +++ b/shared/settings/files/refresh-settings.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import {useFSState} from '@/constants/fs' +import {useFSState} from '@/stores/fs' const RefreshSettings = () => { const refresh = useFSState(s => s.dispatch.loadSettings) diff --git a/shared/settings/group.tsx b/shared/settings/group.tsx index 839ad9fa16b1..fefd140db264 100644 --- a/shared/settings/group.tsx +++ b/shared/settings/group.tsx @@ -1,5 +1,5 @@ import * as Kb from '@/common-adapters' -import type {NotificationsSettingsState} from '@/constants/settings-notifications' +import type {NotificationsSettingsState} from '@/stores/settings-notifications' type GroupProps = { allowEdit: boolean diff --git a/shared/settings/invites/index.desktop.tsx b/shared/settings/invites/index.desktop.tsx index 0636b633ea3b..c9dd76394d51 100644 --- a/shared/settings/invites/index.desktop.tsx +++ b/shared/settings/invites/index.desktop.tsx @@ -1,11 +1,11 @@ import * as Kb from '@/common-adapters' -import type {AcceptedInvite, PendingInvite} from '@/constants/settings-invites' +import type {AcceptedInvite, PendingInvite} from '@/stores/settings-invites' import * as React from 'react' import SubHeading from '../subheading' import * as dateFns from 'date-fns' import * as C from '@/constants' -import {useProfileState} from '@/constants/profile' -import {useState as useSettingsInvitesState} from '@/constants/settings-invites' +import {useProfileState} from '@/stores/profile' +import {useState as useSettingsInvitesState} from '@/stores/settings-invites' // Like intersperse but takes a function to define the separator function intersperseFn( diff --git a/shared/settings/logout.tsx b/shared/settings/logout.tsx index b5d765d9287c..c61c62e3656e 100644 --- a/shared/settings/logout.tsx +++ b/shared/settings/logout.tsx @@ -3,9 +3,9 @@ import {useSafeSubmit} from '@/util/safe-submit' import * as C from '@/constants' import * as Kb from '@/common-adapters' import {UpdatePassword} from './password' -import {usePWState} from '@/constants/settings-password' -import {useSettingsState} from '@/constants/settings' -import {useLogoutState} from '@/constants/logout' +import {usePWState} from '@/stores/settings-password' +import {useSettingsState} from '@/stores/settings' +import {useLogoutState} from '@/stores/logout' const LogoutContainer = () => { const {checkPassword, checkPasswordIsCorrect, resetCheckPassword} = useSettingsState( diff --git a/shared/settings/manage-contacts.tsx b/shared/settings/manage-contacts.tsx index fa5f9f6a3764..47cd4f558a43 100644 --- a/shared/settings/manage-contacts.tsx +++ b/shared/settings/manage-contacts.tsx @@ -2,9 +2,9 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import {SettingsSection} from './account' -import {useSettingsContactsState} from '@/constants/settings-contacts' -import {settingsFeedbackTab} from '@/constants/settings' -import {useConfigState} from '@/constants/config' +import {useSettingsContactsState} from '@/stores/settings-contacts' +import {settingsFeedbackTab} from '@/stores/settings' +import {useConfigState} from '@/stores/config' const enabledDescription = 'Your phone contacts are being synced on this device.' const disabledDescription = 'Import your phone contacts and start encrypted chats with your friends.' @@ -72,7 +72,7 @@ const ManageContactsBanner = () => { status: s.permissionStatus, })) ) - const onOpenAppSettings = useConfigState(s => s.dispatch.dynamic.openAppSettings) + const onOpenAppSettings = useConfigState(s => s.dispatch.defer.openAppSettings) const {appendNewChatBuilder, navigateAppend, switchTab} = C.useRouterState( C.useShallow(s => ({ appendNewChatBuilder: s.appendNewChatBuilder, diff --git a/shared/settings/notifications/hooks.tsx b/shared/settings/notifications/hooks.tsx index 210c1149d735..a3c0aecc59fd 100644 --- a/shared/settings/notifications/hooks.tsx +++ b/shared/settings/notifications/hooks.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import {useSettingsEmailState} from '@/constants/settings-email' -import {useSettingsNotifState} from '@/constants/settings-notifications' -import {useSettingsState, settingsAccountTab} from '@/constants/settings' +import {useSettingsEmailState} from '@/stores/settings-email' +import {useSettingsNotifState} from '@/stores/settings-notifications' +import {useSettingsState, settingsAccountTab} from '@/stores/settings' const useNotifications = () => { const _groups = useSettingsNotifState(s => s.groups) diff --git a/shared/settings/notifications/index.desktop.tsx b/shared/settings/notifications/index.desktop.tsx index dc0c8c53aa9e..cb3bf4b94e1b 100644 --- a/shared/settings/notifications/index.desktop.tsx +++ b/shared/settings/notifications/index.desktop.tsx @@ -1,8 +1,8 @@ import {Reloadable} from '@/common-adapters' import * as C from '@/constants' import Render from './render' -import {useSettingsNotifState} from '@/constants/settings-notifications' -import {useSettingsState} from '@/constants/settings' +import {useSettingsNotifState} from '@/stores/settings-notifications' +import {useSettingsState} from '@/stores/settings' const Notifications = () => { const loadSettings = useSettingsState(s => s.dispatch.loadSettings) diff --git a/shared/settings/notifications/index.native.tsx b/shared/settings/notifications/index.native.tsx index f06118f6b422..d7cf7fe2d81a 100644 --- a/shared/settings/notifications/index.native.tsx +++ b/shared/settings/notifications/index.native.tsx @@ -2,9 +2,9 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import Notifications from './render' import {Reloadable} from '@/common-adapters' -import {useSettingsNotifState} from '@/constants/settings-notifications' -import {useSettingsState} from '@/constants/settings' -import {usePushState} from '@/constants/push' +import {useSettingsNotifState} from '@/stores/settings-notifications' +import {useSettingsState} from '@/stores/settings' +import {usePushState} from '@/stores/push' const MobileNotifications = () => { const loadSettings = useSettingsState(s => s.dispatch.loadSettings) diff --git a/shared/settings/notifications/push-prompt.tsx b/shared/settings/notifications/push-prompt.tsx index 9917a9110fc4..1cf01b98196a 100644 --- a/shared/settings/notifications/push-prompt.tsx +++ b/shared/settings/notifications/push-prompt.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' -import {usePushState} from '@/constants/push' +import {usePushState} from '@/stores/push' const PushPrompt = () => { const rejectPermissions = usePushState(s => s.dispatch.rejectPermissions) diff --git a/shared/settings/notifications/render.tsx b/shared/settings/notifications/render.tsx index 7784aa673b12..fd67e846c9e0 100644 --- a/shared/settings/notifications/render.tsx +++ b/shared/settings/notifications/render.tsx @@ -1,7 +1,7 @@ import * as Kb from '@/common-adapters' import useNotifications from './hooks' import Group from '../group' -import {usePushState} from '@/constants/push' +import {usePushState} from '@/stores/push' type Props = ReturnType diff --git a/shared/settings/password.tsx b/shared/settings/password.tsx index afea9f8392ed..f9c157c2641b 100644 --- a/shared/settings/password.tsx +++ b/shared/settings/password.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import * as C from '@/constants' -import {usePWState} from '@/constants/settings-password' +import {usePWState} from '@/stores/settings-password' type Props = { error: string diff --git a/shared/settings/proxy.tsx b/shared/settings/proxy.tsx index 06975d317b3d..3239af09aba9 100644 --- a/shared/settings/proxy.tsx +++ b/shared/settings/proxy.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import {useSettingsState} from '@/constants/settings' +import {useSettingsState} from '@/stores/settings' const useConnect = () => { const allowTlsMitmToggle = useSettingsState(s => s.didToggleCertificatePinning) diff --git a/shared/settings/root-desktop-tablet.tsx b/shared/settings/root-desktop-tablet.tsx index 7b29c0799c0d..8c76227cdd56 100644 --- a/shared/settings/root-desktop-tablet.tsx +++ b/shared/settings/root-desktop-tablet.tsx @@ -6,7 +6,7 @@ import LeftNav from './sub-nav/left-nav' import {useNavigationBuilder, TabRouter, createNavigatorFactory} from '@react-navigation/core' import type {TypedNavigator, NavigatorTypeBagBase, StaticConfig} from '@react-navigation/native' import {sharedNewRoutes} from './routes' -import {settingsAccountTab} from '@/constants/settings' +import {settingsAccountTab} from '@/stores/settings' const settingsSubRoutes = { ...sharedNewRoutes, diff --git a/shared/settings/root-phone.tsx b/shared/settings/root-phone.tsx index 0ed12c417a3b..e481ddc5883d 100644 --- a/shared/settings/root-phone.tsx +++ b/shared/settings/root-phone.tsx @@ -1,16 +1,16 @@ import * as C from '@/constants' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import {keybaseFM} from '@/constants/whats-new' +import {keybaseFM} from '@/stores/whats-new' import SettingsItem from './sub-nav/settings-item' import WhatsNewIcon from '../whats-new/icon' import noop from 'lodash/noop' -import {useSettingsContactsState} from '@/constants/settings-contacts' -import * as Settings from '@/constants/settings' -import {usePushState} from '@/constants/push' -import {useNotifState} from '@/constants/notifications' +import {useSettingsContactsState} from '@/stores/settings-contacts' +import * as Settings from '@/stores/settings' +import {usePushState} from '@/stores/push' +import {useNotifState} from '@/stores/notifications' const PerfRow = () => { const [toSubmit, setToSubmit] = React.useState('') diff --git a/shared/settings/routes.tsx b/shared/settings/routes.tsx index 71bf575d9e67..dead7baabb06 100644 --- a/shared/settings/routes.tsx +++ b/shared/settings/routes.tsx @@ -3,7 +3,7 @@ import * as C from '@/constants' import {newRoutes as devicesRoutes} from '../devices/routes' import {newRoutes as gitRoutes} from '../git/routes' import {newRoutes as walletsRoutes} from '../wallets/routes' -import * as Settings from '@/constants/settings/util' +import * as Settings from '@/constants/settings' const SettingsRootDesktop = React.lazy(async () => import('./root-desktop-tablet')) diff --git a/shared/settings/sub-nav/left-nav.tsx b/shared/settings/sub-nav/left-nav.tsx index 12ecd1a074d8..6e7337a14961 100644 --- a/shared/settings/sub-nav/left-nav.tsx +++ b/shared/settings/sub-nav/left-nav.tsx @@ -3,10 +3,10 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import WhatsNewIcon from '@/whats-new/icon' import SettingsItem from './settings-item' -import {keybaseFM} from '@/constants/whats-new' -import * as Settings from '@/constants/settings' -import {usePushState} from '@/constants/push' -import {useNotifState} from '@/constants/notifications' +import {keybaseFM} from '@/stores/whats-new' +import * as Settings from '@/stores/settings' +import {usePushState} from '@/stores/push' +import {useNotifState} from '@/stores/notifications' type Props = { onClick: (s: string) => void diff --git a/shared/signup/common.tsx b/shared/signup/common.tsx index 3659e14d4c50..a7a4b486b22f 100644 --- a/shared/signup/common.tsx +++ b/shared/signup/common.tsx @@ -3,7 +3,7 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import {type Props as ButtonProps} from '@/common-adapters/button' import openURL from '@/util/open-url' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' type InfoIconProps = { invisible?: boolean diff --git a/shared/signup/device-name.tsx b/shared/signup/device-name.tsx index 8abc7478d70d..d53ddce22c04 100644 --- a/shared/signup/device-name.tsx +++ b/shared/signup/device-name.tsx @@ -2,8 +2,8 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as React from 'react' import {SignupScreen, errorBanner} from './common' -import * as Provision from '@/constants/provision' -import {useSignupState} from '@/constants/signup' +import * as Provision from '@/stores/provision' +import {useSignupState} from '@/stores/signup' const ConnectedEnterDevicename = () => { const error = useSignupState(s => s.devicenameError) diff --git a/shared/signup/email.tsx b/shared/signup/email.tsx index 1593cf89c1e0..b167e9e3db0b 100644 --- a/shared/signup/email.tsx +++ b/shared/signup/email.tsx @@ -2,9 +2,9 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import {SignupScreen, errorBanner} from './common' -import {useSettingsEmailState} from '@/constants/settings-email' -import {useSignupState} from '@/constants/signup' -import {usePushState} from '@/constants/push' +import {useSettingsEmailState} from '@/stores/settings-email' +import {useSignupState} from '@/stores/signup' +import {usePushState} from '@/stores/push' const ConnectedEnterEmail = () => { const _showPushPrompt = usePushState(s => C.isMobile && !s.hasPermissions && s.showPushPrompt) diff --git a/shared/signup/feedback.tsx b/shared/signup/feedback.tsx index 51c2ac8ace23..1bc404e8c6a3 100644 --- a/shared/signup/feedback.tsx +++ b/shared/signup/feedback.tsx @@ -4,7 +4,7 @@ import * as React from 'react' import FeedbackForm from '../settings/feedback/index' import {SignupScreen, errorBanner} from './common' import {useSendFeedback} from '../settings/feedback/shared' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const SignupFeedback = () => { const {error: sendError, sendFeedback: onSendFeedback} = useSendFeedback() diff --git a/shared/signup/phone-number/index.tsx b/shared/signup/phone-number/index.tsx index 9467647febbe..03ef62e70146 100644 --- a/shared/signup/phone-number/index.tsx +++ b/shared/signup/phone-number/index.tsx @@ -2,7 +2,7 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import {SignupScreen, errorBanner} from '../common' -import {useSettingsPhoneState} from '@/constants/settings-phone' +import {useSettingsPhoneState} from '@/stores/settings-phone' type BodyProps = { autoFocus?: boolean diff --git a/shared/signup/phone-number/verify.tsx b/shared/signup/phone-number/verify.tsx index 1e58e3fde7aa..b3368353a527 100644 --- a/shared/signup/phone-number/verify.tsx +++ b/shared/signup/phone-number/verify.tsx @@ -4,7 +4,7 @@ import * as Kb from '@/common-adapters' import {SignupScreen} from '../common' import {e164ToDisplay} from '@/util/phone-numbers' import VerifyBody from './verify-body' -import {useSettingsPhoneState} from '@/constants/settings-phone' +import {useSettingsPhoneState} from '@/stores/settings-phone' const Container = () => { const error = useSettingsPhoneState(s => (s.verificationState === 'error' ? s.error : '')) diff --git a/shared/signup/username.tsx b/shared/signup/username.tsx index a2b38f4c9cc8..f3f32f1156b3 100644 --- a/shared/signup/username.tsx +++ b/shared/signup/username.tsx @@ -2,8 +2,8 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as React from 'react' import {SignupScreen, errorBanner} from './common' -import {useSignupState} from '@/constants/signup' -import {useProvisionState} from '@/constants/provision' +import {useSignupState} from '@/stores/signup' +import {useProvisionState} from '@/stores/provision' const ConnectedEnterUsername = () => { const error = useSignupState(s => s.usernameError) diff --git a/shared/constants/archive/index.tsx b/shared/stores/archive.tsx similarity index 93% rename from shared/constants/archive/index.tsx rename to shared/stores/archive.tsx index 772f60224ffc..f5869166c8d0 100644 --- a/shared/constants/archive/index.tsx +++ b/shared/stores/archive.tsx @@ -1,12 +1,11 @@ -import * as T from '../types' +import * as T from '@/constants/types' import * as Z from '@/util/zustand' -import {ignorePromise} from '../utils' +import {ignorePromise} from '@/constants/utils' import * as EngineGen from '@/actions/engine-gen-gen' -import * as FS from '@/constants/fs' +import {pathToRPCPath} from '@/constants/fs' import {formatTimeForPopup} from '@/util/timestamp' import {uint8ArrayToHex} from 'uint8array-extras' -import {storeRegistry} from '../store-registry' -import {isMobile} from '../platform' +import {isMobile} from '@/constants/platform' type ChatJob = { id: string @@ -89,7 +88,6 @@ export interface State extends Store { onEngineIncomingImpl: (action: EngineGen.Actions) => void resetState: 'default' } - chatIDToDisplayname: (id: string) => string } export const useArchiveState = Z.createZustand((set, get) => { @@ -279,7 +277,7 @@ export const useArchiveState = Z.createZustand((set, get) => { await T.RPCGen.SimpleFSSimpleFSArchiveStartRpcPromise({ archiveJobStartPath: { archiveJobStartPathType: T.RPCGen.ArchiveJobStartPathType.kbfs, - kbfs: FS.pathToRPCPath(path).kbfs, + kbfs: pathToRPCPath(path).kbfs, }, outputPath: outPath, overwriteZip: true, @@ -447,23 +445,6 @@ export const useArchiveState = Z.createZustand((set, get) => { } return { ...initialStore, - chatIDToDisplayname: (conversationIDKey: string) => { - const you = storeRegistry.getState('current-user').username - const cs = storeRegistry.getConvoState(conversationIDKey) - const m = cs.meta - if (m.teamname) { - if (m.channelname) { - return `${m.teamname}#${m.channelname}` - } - return m.teamname - } - - const participants = cs.participants.name - if (participants.length === 1) { - return participants[0] ?? '' - } - return participants.filter(username => username !== you).join(',') - }, dispatch, } }) diff --git a/shared/constants/autoreset/index.tsx b/shared/stores/autoreset.tsx similarity index 85% rename from shared/constants/autoreset/index.tsx rename to shared/stores/autoreset.tsx index b7c0910473b7..5c066b41a4e5 100644 --- a/shared/constants/autoreset/index.tsx +++ b/shared/stores/autoreset.tsx @@ -1,12 +1,11 @@ import * as Z from '@/util/zustand' -import {ignorePromise} from '../utils' -import * as S from '../strings' +import {ignorePromise} from '@/constants/utils' +import * as S from '@/constants/strings' import * as T from '@/constants/types' import * as EngineGen from '@/actions/engine-gen-gen' import logger from '@/logger' import {RPCError} from '@/util/errors' -import {navigateAppend, navUpToScreen} from '../router2/util' -import {storeRegistry} from '../store-registry' +import {navigateAppend, navUpToScreen} from '@/constants/router2' type Store = T.Immutable<{ active: boolean @@ -33,6 +32,10 @@ const initialStore: Store = { export interface State extends Store { dispatch: { cancelReset: () => void + defer: { + onGetRecoverPasswordUsername: () => string + onStartProvision: (username: string, fromReset: boolean) => void + } onEngineIncomingImpl: (action: EngineGen.Actions) => void resetState: 'default' resetAccount: (password?: string) => void @@ -62,7 +65,7 @@ export const useAutoResetState = Z.createZustand((set, get) => { switch (error.code) { case T.RPCGen.StatusCode.scnosession: // We got logged out because we were revoked (which might have been - // becase the reset was completed and this device wasn't notified). + // because the reset was completed and this device wasn't notified). return undefined case T.RPCGen.StatusCode.scnotfound: // "User not in autoreset queue." @@ -82,6 +85,14 @@ export const useAutoResetState = Z.createZustand((set, get) => { } ignorePromise(f()) }, + defer: { + onGetRecoverPasswordUsername: () => { + throw new Error('onGetRecoverPasswordUsername not properly initialized') + }, + onStartProvision: (_username: string, _fromReset: boolean) => { + throw new Error('onStartProvision not properly initialized') + }, + }, onEngineIncomingImpl: action => { switch (action.type) { case EngineGen.keybase1NotifyBadgesBadgeState: { @@ -118,7 +129,7 @@ export const useAutoResetState = Z.createZustand((set, get) => { set(s => { s.error = '' }) - storeRegistry.getState('provision').dispatch.startProvision(get().username, true) + get().dispatch.defer.onStartProvision(get().username, true) } else { navUpToScreen('login') } @@ -140,12 +151,7 @@ export const useAutoResetState = Z.createZustand((set, get) => { s.endTime = params.endTime * 1000 }) } - storeRegistry - .getState('router') - .dispatch.navigateAppend( - {props: {pipelineStarted: !params.needVerify}, selected: 'resetWaiting'}, - true - ) + navigateAppend({props: {pipelineStarted: !params.needVerify}, selected: 'resetWaiting'}, true) }, }, params: { @@ -169,7 +175,7 @@ export const useAutoResetState = Z.createZustand((set, get) => { }, resetState: 'default', startAccountReset: (skipPassword, _username) => { - const username = _username || storeRegistry.getState('recover-password').username + const username = _username || get().dispatch.defer.onGetRecoverPasswordUsername() || '' set(s => { s.skipPassword = skipPassword s.error = '' diff --git a/shared/constants/bots/index.tsx b/shared/stores/bots.tsx similarity index 91% rename from shared/constants/bots/index.tsx rename to shared/stores/bots.tsx index beb76b45a4d4..e922a4538206 100644 --- a/shared/constants/bots/index.tsx +++ b/shared/stores/bots.tsx @@ -1,8 +1,8 @@ -import * as T from '../types' +import * as T from '@/constants/types' import * as EngineGen from '@/actions/engine-gen-gen' import * as Z from '@/util/zustand' -import {ignorePromise, RPCError, isNetworkErr} from '../utils' -import * as S from '../strings' +import {ignorePromise, RPCError, isNetworkErr} from '@/constants/utils' +import * as S from '@/constants/strings' import logger from '@/logger' type BotSearchResults = { @@ -183,4 +183,17 @@ export const useBotsState = Z.createZustand((set, get) => { } }) -export {getFeaturedSorted} from './util' +export const getFeaturedSorted = ( + featuredBotsMap: ReadonlyMap +): Array => { + const featured = [...featuredBotsMap.values()] + featured.sort((a: T.RPCGen.FeaturedBot, b: T.RPCGen.FeaturedBot) => { + if (a.rank < b.rank) { + return 1 + } else if (a.rank > b.rank) { + return -1 + } + return 0 + }) + return featured +} diff --git a/shared/stores/chat2.tsx b/shared/stores/chat2.tsx new file mode 100644 index 000000000000..693304814f4d --- /dev/null +++ b/shared/stores/chat2.tsx @@ -0,0 +1,2047 @@ +import * as Common from '@/constants/chat2/common' +import * as EngineGen from '@/actions/engine-gen-gen' +import * as Message from '@/constants/chat2/message' +import * as Meta from '@/constants/chat2/meta' +import * as S from '@/constants/strings' +import * as T from '@/constants/types' +import * as Tabs from '@/constants/tabs' +import * as TeamConstants from '@/constants/teams' +import * as Z from '@/util/zustand' +import isEqual from 'lodash/isEqual' +import logger from '@/logger' +import type * as Router2 from '@/stores/router2' +import {type ChatProviderProps, ProviderScreen} from '@/stores/convostate' +import type {GetOptionsRet} from '@/constants/types/router2' +import {RPCError} from '@/util/errors' +import {bodyToJSON} from '@/constants/rpc-utils' +import {clearChatStores, chatStores} from '@/stores/convostate' +import {ignorePromise, timeoutPromise, type ViewPropsToPageProps} from '@/constants/utils' +import {isMobile, isPhone} from '@/constants/platform' +import { + navigateAppend, + navUpToScreen, + switchTab, + getModalStack, + getTab, + getVisibleScreen, +} from '@/constants/router2' +import {storeRegistry} from '@/stores/store-registry' +import {uint8ArrayToString} from 'uint8array-extras' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' +import {useWaitingState} from '@/stores/waiting' + +const defaultTopReacjis = [ + {name: ':+1:'}, + {name: ':-1:'}, + {name: ':tada:'}, + {name: ':joy:'}, + {name: ':sunglasses:'}, +] +const defaultSkinTone = 1 +const defaultUserReacjis = {skinTone: defaultSkinTone, topReacjis: defaultTopReacjis} + +// while we're debugging chat issues +export const DEBUG_CHAT_DUMP = true + +const blockButtonsGregorPrefix = 'blockButtons.' + +export const inboxSearchMaxTextMessages = 25 +export const inboxSearchMaxTextResults = 50 +export const inboxSearchMaxNameResults = 7 +export const inboxSearchMaxUnreadNameResults = isMobile ? 5 : 10 + +export const makeInboxSearchInfo = (): T.Chat.InboxSearchInfo => ({ + botsResults: [], + botsResultsSuggested: false, + botsStatus: 'initial', + indexPercent: 0, + nameResults: [], + nameResultsUnread: false, + nameStatus: 'initial', + openTeamsResults: [], + openTeamsResultsSuggested: false, + openTeamsStatus: 'initial', + query: '', + selectedIndex: 0, + textResults: [], + textStatus: 'initial', +}) + +const getInboxSearchSelected = ( + inboxSearch: T.Immutable +): + | undefined + | { + conversationIDKey: T.Chat.ConversationIDKey + query?: string + } => { + const {selectedIndex, nameResults, botsResults, openTeamsResults, textResults} = inboxSearch + const firstTextResultIdx = botsResults.length + openTeamsResults.length + nameResults.length + const firstOpenTeamResultIdx = nameResults.length + + if (selectedIndex < firstOpenTeamResultIdx) { + const maybeNameResults = nameResults[selectedIndex] + const conversationIDKey = maybeNameResults === undefined ? undefined : maybeNameResults.conversationIDKey + if (conversationIDKey) { + return { + conversationIDKey, + query: undefined, + } + } + } else if (selectedIndex < firstTextResultIdx) { + return + } else if (selectedIndex >= firstTextResultIdx) { + const result = textResults[selectedIndex - firstTextResultIdx] + if (result) { + return { + conversationIDKey: result.conversationIDKey, + query: result.query, + } + } + } + return +} + +export const getMessageKey = (message: T.Chat.Message) => + `${message.conversationIDKey}:${T.Chat.ordinalToNumber(message.ordinal)}` + +export const getBotsAndParticipants = ( + meta: T.Immutable, + participantInfo: T.Immutable, + sort?: boolean +) => { + const isAdhocTeam = meta.teamType === 'adhoc' + const teamMembers = + useChatState.getState().dispatch.defer.onGetTeamsTeamIDToMembers(meta.teamID) ?? + new Map() + let bots: Array = [] + if (isAdhocTeam) { + bots = participantInfo.all.filter(p => !participantInfo.name.includes(p)) + } else { + bots = [...teamMembers.values()] + .filter( + p => + TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p.username, 'restrictedbot') || + TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p.username, 'bot') + ) + .map(p => p.username) + .sort((l, r) => l.localeCompare(r)) + } + let participants: ReadonlyArray = participantInfo.all + if (meta.channelname === 'general') { + participants = [...teamMembers.values()].reduce>((l, mi) => { + l.push(mi.username) + return l + }, []) + } + participants = participants.filter(p => !bots.includes(p)) + participants = sort + ? participants + .map(p => ({ + isAdmin: !isAdhocTeam ? TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p, 'admin') : false, + isOwner: !isAdhocTeam ? TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p, 'owner') : false, + username: p, + })) + .sort((l, r) => { + const leftIsAdmin = l.isAdmin || l.isOwner + const rightIsAdmin = r.isAdmin || r.isOwner + if (leftIsAdmin && !rightIsAdmin) { + return -1 + } else if (!leftIsAdmin && rightIsAdmin) { + return 1 + } + return l.username.localeCompare(r.username) + }) + .map(p => p.username) + : participants + return {bots, participants} +} + +export const getTeamMentionName = (name: string, channel: string) => { + return name + (channel ? `#${channel}` : '') +} + +export const isAssertion = (username: string) => username.includes('@') + +export const clampImageSize = (width: number, height: number, maxWidth: number, maxHeight: number) => { + const aspectRatio = width / height + + let newWidth = width + let newHeight = height + + if (newWidth > maxWidth) { + newWidth = maxWidth + newHeight = newWidth / aspectRatio + } + + if (newHeight > maxHeight) { + newHeight = maxHeight + newWidth = newHeight * aspectRatio + } + + return { + height: Math.ceil(newHeight), + width: Math.ceil(newWidth), + } +} + +export const zoomImage = (width: number, height: number, maxThumbSize: number) => { + const dims = + height > width + ? {height: (maxThumbSize * height) / width, width: maxThumbSize} + : {height: maxThumbSize, width: (maxThumbSize * width) / height} + const marginHeight = dims.height > maxThumbSize ? (dims.height - maxThumbSize) / 2 : 0 + const marginWidth = dims.width > maxThumbSize ? (dims.width - maxThumbSize) / 2 : 0 + return { + dims, + margins: { + marginBottom: -marginHeight, + marginLeft: -marginWidth, + marginRight: -marginWidth, + marginTop: -marginHeight, + }, + } +} + +const uiParticipantsToParticipantInfo = ( + uiParticipants: ReadonlyArray +): T.Chat.ParticipantInfo => { + const participantInfo = {all: new Array(), contactName: new Map(), name: new Array()} + uiParticipants.forEach(part => { + const {assertion, contactName, inConvName} = part + participantInfo.all.push(assertion) + if (inConvName) { + participantInfo.name.push(assertion) + } + if (contactName) { + participantInfo.contactName.set(assertion, contactName) + } + }) + return participantInfo +} + +/** + * Returns true if the team is big and you're a member + */ +export const isBigTeam = (state: State, teamID: string): boolean => { + const bigTeams = state.inboxLayout?.bigTeams + return (bigTeams || []).some(v => v.state === T.RPCChat.UIInboxBigTeamRowTyp.label && v.label.id === teamID) +} + +// prettier-ignore +type PreviewReason = + | 'appLink' | 'channelHeader' | 'convertAdHoc' | 'files' | 'forward' | 'fromAReset' + | 'journeyCardPopular' | 'manageView' | 'memberView' | 'messageLink' | 'newChannel' + | 'profile' | 'requestedPayment' | 'resetChatWithoutThem' | 'search' | 'sentPayment' + | 'teamHeader' | 'teamInvite' | 'teamMember' | 'teamMention' | 'teamRow' | 'tracker' | 'transaction' + +type Store = T.Immutable<{ + botPublicCommands: Map + createConversationError?: T.Chat.CreateConversationError + smallTeamBadgeCount: number + bigTeamBadgeCount: number + smallTeamsExpanded: boolean // if we're showing all small teams, + lastCoord?: T.Chat.Coordinate + paymentStatusMap: Map + staticConfig?: T.Chat.StaticConfig // static config stuff from the service. only needs to be loaded once. if null, it hasn't been loaded, + trustedInboxHasLoaded: boolean // if we've done initial trusted inbox load, + userReacjis: T.Chat.UserReacjis + userEmojis?: Array + userEmojisForAutocomplete?: Array + infoPanelShowing: boolean + infoPanelSelectedTab?: 'settings' | 'members' | 'attachments' | 'bots' + inboxNumSmallRows?: number + inboxHasLoaded: boolean // if we've ever loaded, + inboxLayout?: T.RPCChat.UIInboxLayout // layout of the inbox + inboxSearch?: T.Chat.InboxSearchInfo + teamIDToGeneralConvID: Map + flipStatusMap: Map + maybeMentionMap: Map + blockButtonsMap: Map // Should we show block buttons for this team ID? +}> + +const initialStore: Store = { + bigTeamBadgeCount: 0, + blockButtonsMap: new Map(), + botPublicCommands: new Map(), + createConversationError: undefined, + flipStatusMap: new Map(), + inboxHasLoaded: false, + inboxLayout: undefined, + inboxNumSmallRows: 5, + inboxSearch: undefined, + infoPanelSelectedTab: undefined, + infoPanelShowing: false, + lastCoord: undefined, + maybeMentionMap: new Map(), + paymentStatusMap: new Map(), + smallTeamBadgeCount: 0, + smallTeamsExpanded: false, + staticConfig: undefined, + teamIDToGeneralConvID: new Map(), + trustedInboxHasLoaded: false, + userEmojis: undefined, + userEmojisForAutocomplete: undefined, + userReacjis: defaultUserReacjis, +} + +export type RefreshReason = + | 'bootstrap' + | 'componentNeverLoaded' + | 'inboxStale' + | 'inboxSyncedClear' + | 'inboxSyncedUnknown' + | 'joinedAConversation' + | 'leftAConversation' + | 'teamTypeChanged' + | 'maybeKickedFromTeam' + | 'widgetRefresh' + | 'shareConfigSearch' + +export interface State extends Store { + dispatch: { + badgesUpdated: (badgeState?: T.RPCGen.BadgeState) => void + clearMetas: () => void + defer: { + onGetDaemonState: () => {handshakeVersion: number; dispatch: any} + onGetTeamsTeamIDToMembers: ( + teamID: T.Teams.TeamID + ) => ReadonlyMap | undefined + onGetUsersInfoMap: () => ReadonlyMap + onTeamsGetMembers: (teamID: T.Teams.TeamID) => void + onTeamsUpdateTeamRetentionPolicy: (metas: ReadonlyArray) => void + onUsersUpdates: (updates: ReadonlyArray<{name: string; info: Partial}>) => void + } + conversationErrored: ( + allowedUsers: ReadonlyArray, + disallowedUsers: ReadonlyArray, + code: number, + message: string + ) => void + createConversation: (participants: ReadonlyArray, highlightMessageID?: T.Chat.MessageID) => void + ensureWidgetMetas: () => void + findGeneralConvIDFromTeamID: (teamID: T.Teams.TeamID) => void + fetchUserEmoji: (conversationIDKey?: T.Chat.ConversationIDKey, onlyInTeam?: boolean) => void + inboxRefresh: (reason: RefreshReason) => void + inboxSearch: (query: string) => void + inboxSearchMoveSelectedIndex: (increment: boolean) => void + inboxSearchSelect: ( + conversationIDKey?: T.Chat.ConversationIDKey, + query?: string, + selectedIndex?: number + ) => void + loadStaticConfig: () => void + loadedUserEmoji: (results: T.RPCChat.UserEmojiRes) => void + maybeChangeSelectedConv: () => void + messageSendByUsername: (username: string, text: string, waitingKey?: string) => void + metasReceived: ( + metas: ReadonlyArray, + removals?: ReadonlyArray // convs to remove + ) => void + navigateToInbox: (allowSwitchTab?: boolean) => void + onChatThreadStale: (action: EngineGen.Chat1NotifyChatChatThreadsStalePayload) => void + onEngineIncomingImpl: (action: EngineGen.Actions) => void + onChatInboxSynced: (action: EngineGen.Chat1NotifyChatChatInboxSyncedPayload) => void + onGetInboxConvsUnboxed: (action: EngineGen.Chat1ChatUiChatInboxConversationPayload) => void + onGetInboxUnverifiedConvs: (action: EngineGen.Chat1ChatUiChatInboxUnverifiedPayload) => void + onIncomingInboxUIItem: (inboxUIItem?: T.RPCChat.InboxUIItem) => void + onRouteChanged: (prev: T.Immutable, next: T.Immutable) => void + onTeamBuildingFinished: (users: ReadonlySet) => void + paymentInfoReceived: (paymentInfo: T.Chat.ChatPaymentInfo) => void + previewConversation: (p: { + participants?: ReadonlyArray + teamname?: string + channelname?: string + conversationIDKey?: T.Chat.ConversationIDKey // we only use this when we click on channel mentions. we could maybe change that plumbing but keeping it for now + highlightMessageID?: T.Chat.MessageID + reason: PreviewReason + }) => void + queueMetaToRequest: (ids: ReadonlyArray) => void + queueMetaHandle: () => void + refreshBotPublicCommands: (username: string) => void + resetConversationErrored: () => void + resetState: () => void + setMaybeMentionInfo: (name: string, info: T.RPCChat.UIMaybeMentionInfo) => void + setTrustedInboxHasLoaded: () => void + setInfoPanelTab: (tab: 'settings' | 'members' | 'attachments' | 'bots' | undefined) => void + setInboxNumSmallRows: (rows: number, ignoreWrite?: boolean) => void + toggleInboxSearch: (enabled: boolean) => void + toggleSmallTeamsExpanded: () => void + unboxRows: (ids: ReadonlyArray, force?: boolean) => void + updateCoinFlipStatus: (statuses: ReadonlyArray) => void + updateInboxLayout: (layout: string) => void + updateLastCoord: (coord: T.Chat.Coordinate) => void + updateUserReacjis: (userReacjis: T.RPCGen.UserReacjis) => void + updatedGregor: ( + items: ReadonlyArray<{md: T.RPCGen.Gregor1.Metadata; item: T.RPCGen.Gregor1.Item}> + ) => void + updateInfoPanel: (show: boolean, tab: 'settings' | 'members' | 'attachments' | 'bots' | undefined) => void + } + getBackCount: (conversationIDKey: T.Chat.ConversationIDKey) => number + getBadgeHiddenCount: (ids: ReadonlySet) => { + badgeCount: number + hiddenCount: number + } + getUnreadIndicies: (ids: ReadonlyArray) => Map +} + +// Only get the untrusted conversations out +const untrustedConversationIDKeys = (ids: ReadonlyArray) => + ids.filter(id => storeRegistry.getConvoState(id).meta.trustedState === 'untrusted') + +// generic chat store +export const useChatState = Z.createZustand((set, get) => { + // We keep a set of conversations to unbox + let metaQueue = new Set() + + const dispatch: State['dispatch'] = { + badgesUpdated: b => { + if (!b) return + // clear all first + for (const [, cs] of chatStores) { + cs.getState().dispatch.badgesUpdated(0) + } + b.conversations?.forEach(c => { + const id = T.Chat.conversationIDToKey(c.convID) + storeRegistry.getConvoState(id).dispatch.badgesUpdated(c.badgeCount) + storeRegistry.getConvoState(id).dispatch.unreadUpdated(c.unreadMessages) + }) + const {bigTeamBadgeCount, smallTeamBadgeCount} = b + set(s => { + s.smallTeamBadgeCount = smallTeamBadgeCount + s.bigTeamBadgeCount = bigTeamBadgeCount + }) + }, + clearMetas: () => { + for (const [, cs] of chatStores) { + cs.getState().dispatch.setMeta() + } + }, + conversationErrored: (allowedUsers, disallowedUsers, code, message) => { + set(s => { + s.createConversationError = T.castDraft({ + allowedUsers, + code, + disallowedUsers, + message, + }) + }) + }, + createConversation: (participants, highlightMessageID) => { + // TODO This will break if you try to make 2 new conversations at the same time because there is + // only one pending conversation state. + // The fix involves being able to make multiple pending conversations + const f = async () => { + const username = useCurrentUserState.getState().username + if (!username) { + logger.error('Making a convo while logged out?') + return + } + try { + const result = await T.RPCChat.localNewConversationLocalRpcPromise( + { + identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, + membersType: T.RPCChat.ConversationMembersType.impteamnative, + tlfName: [...new Set([username, ...participants])].join(','), + tlfVisibility: T.RPCGen.TLFVisibility.private, + topicType: T.RPCChat.TopicType.chat, + }, + S.waitingKeyChatCreating + ) + const {conv, uiConv} = result + const conversationIDKey = T.Chat.conversationIDToKey(conv.info.id) + if (!conversationIDKey) { + logger.warn("Couldn't make a new conversation?") + } else { + const meta = Meta.inboxUIItemToConversationMeta(uiConv) + if (meta) { + get().dispatch.metasReceived([meta]) + } + + const participantInfo: T.Chat.ParticipantInfo = uiParticipantsToParticipantInfo( + uiConv.participants ?? [] + ) + if (participantInfo.all.length > 0) { + storeRegistry + .getConvoState(T.Chat.stringToConversationIDKey(uiConv.convID)) + .dispatch.setParticipants(participantInfo) + } + storeRegistry + .getConvoState(conversationIDKey) + .dispatch.navigateToThread('justCreated', highlightMessageID) + } + } catch (error) { + if (error instanceof RPCError) { + const f = error.fields as Array<{key?: string}> | undefined + const errUsernames = f?.filter(elem => elem.key === 'usernames') as + | undefined + | Array<{key: string; value: string}> + let disallowedUsers: Array = [] + if (errUsernames?.length) { + const {value} = errUsernames[0] ?? {value: ''} + disallowedUsers = value.split(',') + } + const allowedUsers = participants.filter(x => !disallowedUsers.includes(x)) + get().dispatch.conversationErrored(allowedUsers, disallowedUsers, error.code, error.desc) + storeRegistry + .getConvoState(T.Chat.pendingErrorConversationIDKey) + .dispatch.navigateToThread('justCreated', highlightMessageID) + } + } + } + ignorePromise(f()) + }, + defer: { + onGetDaemonState: () => { + throw new Error('onGetDaemonState not properly initialized') + }, + onGetTeamsTeamIDToMembers: (_teamID: T.Teams.TeamID) => { + throw new Error('onGetTeamsTeamIDToMembers not properly initialized') + }, + onGetUsersInfoMap: () => { + throw new Error('onGetUsersInfoMap not properly initialized') + }, + onTeamsGetMembers: (_teamID: T.Teams.TeamID) => { + throw new Error('onTeamsGetMembers not properly initialized') + }, + onTeamsUpdateTeamRetentionPolicy: (_metas: ReadonlyArray) => { + throw new Error('onTeamsUpdateTeamRetentionPolicy not properly initialized') + }, + onUsersUpdates: (_updates: ReadonlyArray<{name: string; info: Partial}>) => { + throw new Error('onUsersUpdates not properly initialized') + }, + }, + ensureWidgetMetas: () => { + const {inboxLayout} = get() + if (!inboxLayout?.widgetList) { + return + } + const missing = inboxLayout.widgetList.reduce>((l, v) => { + if (!storeRegistry.getConvoState(v.convID).isMetaGood()) { + l.push(v.convID) + } + return l + }, []) + if (missing.length === 0) { + return + } + get().dispatch.unboxRows(missing, true) + }, + fetchUserEmoji: (conversationIDKey, onlyInTeam) => { + const f = async () => { + const results = await T.RPCChat.localUserEmojisRpcPromise( + { + convID: + conversationIDKey && conversationIDKey !== T.Chat.noConversationIDKey + ? T.Chat.keyToConversationID(conversationIDKey) + : null, + opts: { + getAliases: true, + getCreationInfo: false, + onlyInTeam: onlyInTeam ?? false, + }, + }, + S.waitingKeyChatLoadingEmoji + ) + get().dispatch.loadedUserEmoji(results) + } + ignorePromise(f()) + }, + findGeneralConvIDFromTeamID: teamID => { + const f = async () => { + try { + const conv = await T.RPCChat.localFindGeneralConvFromTeamIDRpcPromise({teamID}) + const meta = Meta.inboxUIItemToConversationMeta(conv) + if (!meta) { + logger.info(`findGeneralConvIDFromTeamID: failed to convert to meta`) + return + } + get().dispatch.metasReceived([meta]) + set(s => { + s.teamIDToGeneralConvID.set(teamID, T.Chat.stringToConversationIDKey(conv.convID)) + }) + } catch (error) { + if (error instanceof RPCError) { + logger.info(`findGeneralConvIDFromTeamID: failed to get general conv: ${error.message}`) + } + } + } + ignorePromise(f()) + }, + inboxRefresh: reason => { + const f = async () => { + const {username} = useCurrentUserState.getState() + const {loggedIn} = useConfigState.getState() + if (!loggedIn || !username) { + return + } + const clearExistingMetas = reason === 'inboxSyncedClear' + const clearExistingMessages = reason === 'inboxSyncedClear' + + logger.info(`Inbox refresh due to ${reason}`) + const reselectMode = + get().inboxHasLoaded || isPhone + ? T.RPCChat.InboxLayoutReselectMode.default + : T.RPCChat.InboxLayoutReselectMode.force + await T.RPCChat.localRequestInboxLayoutRpcPromise({reselectMode}) + if (clearExistingMetas) { + get().dispatch.clearMetas() + } + if (clearExistingMessages) { + for (const [, cs] of chatStores) { + cs.getState().dispatch.messagesClear() + } + } + } + ignorePromise(f()) + }, + inboxSearch: query => { + set(s => { + const {inboxSearch} = s + if (inboxSearch) { + inboxSearch.query = query + } + }) + const f = async () => { + const teamType = (t: T.RPCChat.TeamType) => (t === T.RPCChat.TeamType.complex ? 'big' : 'small') + + const onConvHits = (resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchConvHits']['inParam']) => { + const results = (resp.hits.hits || []).reduce>((arr, h) => { + arr.push({ + conversationIDKey: T.Chat.stringToConversationIDKey(h.convID), + name: h.name, + teamType: teamType(h.teamType), + }) + return arr + }, []) + + set(s => { + const unread = resp.hits.unreadMatches + const {inboxSearch} = s + if (inboxSearch?.nameStatus === 'inprogress') { + inboxSearch.nameResults = results + inboxSearch.nameResultsUnread = unread + inboxSearch.nameStatus = 'success' + } + }) + + const missingMetas = results.reduce>((arr, r) => { + if (!storeRegistry.getConvoState(r.conversationIDKey).isMetaGood()) { + arr.push(r.conversationIDKey) + } + return arr + }, []) + if (missingMetas.length > 0) { + get().dispatch.unboxRows(missingMetas, true) + } + } + + const onOpenTeamHits = ( + resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchTeamHits']['inParam'] + ) => { + const results = (resp.hits.hits || []).reduce>((arr, h) => { + const {description, name, memberCount, inTeam} = h + arr.push({ + description: description ?? '', + inTeam, + memberCount, + name, + publicAdmins: [], + }) + return arr + }, []) + const suggested = resp.hits.suggestedMatches + set(s => { + const {inboxSearch} = s + if (inboxSearch?.openTeamsStatus === 'inprogress') { + inboxSearch.openTeamsResultsSuggested = suggested + inboxSearch.openTeamsResults = T.castDraft(results) + inboxSearch.openTeamsStatus = 'success' + } + }) + } + + const onBotsHits = (resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchBotHits']['inParam']) => { + const results = resp.hits.hits || [] + const suggested = resp.hits.suggestedMatches + set(s => { + const {inboxSearch} = s + if (inboxSearch?.botsStatus === 'inprogress') { + inboxSearch.botsResultsSuggested = suggested + inboxSearch.botsResults = T.castDraft(results) + inboxSearch.botsStatus = 'success' + } + }) + } + + const onTextHit = (resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchInboxHit']['inParam']) => { + const {convID, convName, hits, query, teamType: tt, time} = resp.searchHit + + const result = { + conversationIDKey: T.Chat.conversationIDToKey(convID), + name: convName, + numHits: hits?.length ?? 0, + query, + teamType: teamType(tt), + time, + } as const + set(s => { + const {inboxSearch} = s + if (inboxSearch?.textStatus === 'inprogress') { + const {conversationIDKey} = result + const textResults = inboxSearch.textResults.filter( + r => r.conversationIDKey !== conversationIDKey + ) + textResults.push(result) + inboxSearch.textResults = textResults.sort((l, r) => r.time - l.time) + } + }) + + if ( + storeRegistry.getConvoState(result.conversationIDKey).meta.conversationIDKey === + T.Chat.noConversationIDKey + ) { + get().dispatch.unboxRows([result.conversationIDKey], true) + } + } + const onStart = () => { + set(s => { + const {inboxSearch} = s + if (inboxSearch) { + inboxSearch.nameStatus = 'inprogress' + inboxSearch.selectedIndex = 0 + inboxSearch.textResults = [] + inboxSearch.textStatus = 'inprogress' + inboxSearch.openTeamsStatus = 'inprogress' + inboxSearch.botsStatus = 'inprogress' + } + }) + } + const onDone = () => { + set(s => { + const status = 'success' + const inboxSearch = s.inboxSearch ?? makeInboxSearchInfo() + s.inboxSearch = T.castDraft(inboxSearch) + inboxSearch.textStatus = status + }) + } + + const onIndexStatus = ( + resp: T.RPCChat.MessageTypes['chat.1.chatUi.chatSearchIndexStatus']['inParam'] + ) => { + const percent = resp.status.percentIndexed + set(s => { + const {inboxSearch} = s + if (inboxSearch?.textStatus === 'inprogress') { + inboxSearch.indexPercent = percent + } + }) + } + + try { + await T.RPCChat.localSearchInboxRpcListener({ + incomingCallMap: { + 'chat.1.chatUi.chatSearchBotHits': onBotsHits, + 'chat.1.chatUi.chatSearchConvHits': onConvHits, + 'chat.1.chatUi.chatSearchInboxDone': onDone, + 'chat.1.chatUi.chatSearchInboxHit': onTextHit, + 'chat.1.chatUi.chatSearchInboxStart': onStart, + 'chat.1.chatUi.chatSearchIndexStatus': onIndexStatus, + 'chat.1.chatUi.chatSearchTeamHits': onOpenTeamHits, + }, + params: { + identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, + namesOnly: false, + opts: { + afterContext: 0, + beforeContext: 0, + isRegex: false, + matchMentions: false, + maxBots: 10, + maxConvsHit: inboxSearchMaxTextResults, + maxConvsSearched: 0, + maxHits: inboxSearchMaxTextMessages, + maxMessages: -1, + maxNameConvs: query.length > 0 ? inboxSearchMaxNameResults : inboxSearchMaxUnreadNameResults, + maxTeams: 10, + reindexMode: T.RPCChat.ReIndexingMode.postsearchSync, + sentAfter: 0, + sentBefore: 0, + sentBy: '', + sentTo: '', + skipBotCache: false, + }, + query, + }, + }) + } catch (error) { + if (error instanceof RPCError) { + if (!(error.code === T.RPCGen.StatusCode.sccanceled)) { + logger.error('search failed: ' + error.message) + set(s => { + const status = 'error' + const inboxSearch = s.inboxSearch ?? makeInboxSearchInfo() + s.inboxSearch = T.castDraft(inboxSearch) + inboxSearch.textStatus = status + }) + } + } + } + } + ignorePromise(f()) + }, + inboxSearchMoveSelectedIndex: increment => { + set(s => { + const {inboxSearch} = s + if (inboxSearch) { + const {selectedIndex} = inboxSearch + const totalResults = inboxSearch.nameResults.length + inboxSearch.textResults.length + if (increment && selectedIndex < totalResults - 1) { + inboxSearch.selectedIndex = selectedIndex + 1 + } else if (!increment && selectedIndex > 0) { + inboxSearch.selectedIndex = selectedIndex - 1 + } + } + }) + }, + inboxSearchSelect: (_conversationIDKey, q, selectedIndex) => { + let conversationIDKey = _conversationIDKey + let query = q + set(s => { + const {inboxSearch} = s + if (inboxSearch && selectedIndex !== undefined) { + inboxSearch.selectedIndex = selectedIndex + } + }) + + const {inboxSearch} = get() + if (!inboxSearch) { + return + } + const selected = getInboxSearchSelected(inboxSearch) + if (!conversationIDKey) { + conversationIDKey = selected?.conversationIDKey + } + + if (!conversationIDKey) { + return + } + if (!query) { + query = selected?.query + } + + storeRegistry.getConvoState(conversationIDKey).dispatch.navigateToThread('inboxSearch') + if (query) { + const cs = storeRegistry.getConvoState(conversationIDKey) + cs.dispatch.setThreadSearchQuery(query) + cs.dispatch.toggleThreadSearch(false) + cs.dispatch.threadSearch(query) + } else { + get().dispatch.toggleInboxSearch(false) + } + }, + loadStaticConfig: () => { + if (get().staticConfig) { + return + } + const {handshakeVersion, dispatch} = get().dispatch.defer.onGetDaemonState() + const f = async () => { + const name = 'chat.loadStatic' + dispatch.wait(name, handshakeVersion, true) + try { + const res = await T.RPCChat.localGetStaticConfigRpcPromise() + if (!res.deletableByDeleteHistory) { + logger.error('chat.loadStaticConfig: got no deletableByDeleteHistory in static config') + return + } + const deletableByDeleteHistory = res.deletableByDeleteHistory.reduce>( + (res, type) => { + const ourTypes = Message.serviceMessageTypeToMessageTypes(type) + res.push(...ourTypes) + return res + }, + [] + ) + set(s => { + s.staticConfig = { + builtinCommands: (res.builtinCommands || []).reduce( + (map, c) => { + map[c.typ] = T.castDraft(c.commands) || [] + return map + }, + { + [T.RPCChat.ConversationBuiltinCommandTyp.none]: [], + [T.RPCChat.ConversationBuiltinCommandTyp.adhoc]: [], + [T.RPCChat.ConversationBuiltinCommandTyp.smallteam]: [], + [T.RPCChat.ConversationBuiltinCommandTyp.bigteam]: [], + [T.RPCChat.ConversationBuiltinCommandTyp.bigteamgeneral]: [], + } + ), + deletableByDeleteHistory: new Set(deletableByDeleteHistory), + } + }) + } finally { + dispatch.wait(name, handshakeVersion, false) + } + } + ignorePromise(f()) + }, + loadedUserEmoji: results => { + set(s => { + const newEmojis: Array = [] + results.emojis.emojis?.forEach(group => { + group.emojis?.forEach(e => newEmojis.push(e)) + }) + s.userEmojisForAutocomplete = newEmojis + s.userEmojis = T.castDraft(results.emojis.emojis) ?? [] + }) + }, + maybeChangeSelectedConv: () => { + const {inboxLayout} = get() + const newConvID = inboxLayout?.reselectInfo?.newConvID + const oldConvID = inboxLayout?.reselectInfo?.oldConvID + + const selectedConversation = Common.getSelectedConversation() + + if (!newConvID && !oldConvID) { + return + } + + const existingValid = T.Chat.isValidConversationIDKey(selectedConversation) + // no new id, just take the opportunity to resolve + if (!newConvID) { + if (!existingValid && isPhone) { + logger.info(`maybeChangeSelectedConv: no new and no valid, so go to inbox`) + get().dispatch.navigateToInbox(false) + } + return + } + // not matching? + if (selectedConversation !== oldConvID) { + if (!existingValid && isPhone) { + logger.info(`maybeChangeSelectedConv: no new and no valid, so go to inbox`) + get().dispatch.navigateToInbox(false) + } + return + } + // matching + if (isPhone) { + // on mobile just head back to the inbox if we have something selected + if (T.Chat.isValidConversationIDKey(selectedConversation)) { + logger.info(`maybeChangeSelectedConv: mobile: navigating up on conv change`) + get().dispatch.navigateToInbox(false) + return + } + logger.info(`maybeChangeSelectedConv: mobile: ignoring conv change, no conv selected`) + return + } else { + logger.info( + `maybeChangeSelectedConv: selecting new conv: new:${newConvID} old:${oldConvID} prevselected ${selectedConversation}` + ) + storeRegistry.getConvoState(newConvID).dispatch.navigateToThread('findNewestConversation') + } + }, + messageSendByUsername: (username, text, waitingKey) => { + const f = async () => { + const tlfName = `${useCurrentUserState.getState().username},${username}` + try { + const result = await T.RPCChat.localNewConversationLocalRpcPromise( + { + identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, + membersType: T.RPCChat.ConversationMembersType.impteamnative, + tlfName, + tlfVisibility: T.RPCGen.TLFVisibility.private, + topicType: T.RPCChat.TopicType.chat, + }, + waitingKey + ) + storeRegistry + .getConvoState(T.Chat.conversationIDToKey(result.conv.info.id)) + .dispatch.sendMessage(text) + } catch (error) { + if (error instanceof RPCError) { + logger.warn('Could not send in messageSendByUsernames', error.message) + } + } + } + ignorePromise(f()) + }, + metasReceived: (metas, removals) => { + removals?.forEach(r => { + storeRegistry.getConvoState(r).dispatch.setMeta() + }) + metas.forEach(m => { + const {meta: oldMeta, dispatch, isMetaGood} = storeRegistry.getConvoState(m.conversationIDKey) + if (isMetaGood()) { + dispatch.updateMeta(Meta.updateMeta(oldMeta, m)) + } else { + dispatch.setMeta(m) + } + }) + + const selectedConversation = Common.getSelectedConversation() + const {isMetaGood, meta} = storeRegistry.getConvoState(selectedConversation) + if (isMetaGood()) { + const {teamID} = meta + if (!get().dispatch.defer.onGetTeamsTeamIDToMembers(teamID) && meta.teamname) { + get().dispatch.defer.onTeamsGetMembers(teamID) + } + } + }, + navigateToInbox: (allowSwitchTab = true) => { + // components can call us during render sometimes so always defer + setTimeout(() => { + navUpToScreen('chatRoot') + if (allowSwitchTab) { + switchTab(Tabs.chatTab) + } + }, 1) + }, + onChatInboxSynced: action => { + const {syncRes} = action.payload.params + const {clear} = useWaitingState.getState().dispatch + const {inboxRefresh} = get().dispatch + clear(S.waitingKeyChatInboxSyncStarted) + + switch (syncRes.syncType) { + // Just clear it all + case T.RPCChat.SyncInboxResType.clear: + inboxRefresh('inboxSyncedClear') + break + // We're up to date + case T.RPCChat.SyncInboxResType.current: + break + // We got some new messages appended + case T.RPCChat.SyncInboxResType.incremental: { + const items = syncRes.incremental.items || [] + const selectedConversation = Common.getSelectedConversation() + let loadMore = false as boolean + const metas = items.reduce>((arr, i) => { + const meta = Meta.unverifiedInboxUIItemToConversationMeta(i.conv) + if (meta) { + arr.push(meta) + if (meta.conversationIDKey === selectedConversation) { + loadMore = true + } + } + return arr + }, []) + if (loadMore) { + storeRegistry.getConvoState(selectedConversation).dispatch.loadMoreMessages({reason: 'got stale'}) + } + const removals = syncRes.incremental.removals?.map(T.Chat.stringToConversationIDKey) + // Update new untrusted + if (metas.length || removals?.length) { + get().dispatch.metasReceived(metas, removals) + } + + get().dispatch.unboxRows( + items.filter(i => i.shouldUnbox).map(i => T.Chat.stringToConversationIDKey(i.conv.convID)), + true + ) + break + } + default: + inboxRefresh('inboxSyncedUnknown') + } + }, + onChatThreadStale: (action: EngineGen.Chat1NotifyChatChatThreadsStalePayload) => { + const {updates} = action.payload.params + const keys = ['clear', 'newactivity'] as const + if (__DEV__) { + if (keys.length * 2 !== Object.keys(T.RPCChat.StaleUpdateType).length) { + throw new Error('onChatThreadStale invalid enum') + } + } + let loadMore = false as boolean + const selectedConversation = Common.getSelectedConversation() + keys.forEach(key => { + const conversationIDKeys = (updates || []).reduce>((arr, u) => { + const cid = T.Chat.conversationIDToKey(u.convID) + if (u.updateType === T.RPCChat.StaleUpdateType[key]) { + arr.push(cid) + } + // mentioned? + if (cid === selectedConversation) { + loadMore = true + } + return arr + }, []) + // load the inbox instead + if (conversationIDKeys.length > 0) { + logger.info( + `onChatThreadStale: dispatching thread reload actions for ${conversationIDKeys.length} convs of type ${key}` + ) + get().dispatch.unboxRows(conversationIDKeys, true) + if (T.RPCChat.StaleUpdateType[key] === T.RPCChat.StaleUpdateType.clear) { + conversationIDKeys.forEach(convID => storeRegistry.getConvoState(convID).dispatch.messagesClear()) + } + } + }) + if (loadMore) { + storeRegistry.getConvoState(selectedConversation).dispatch.loadMoreMessages({reason: 'got stale'}) + } + }, + onEngineIncomingImpl: action => { + switch (action.type) { + case EngineGen.chat1ChatUiChatInboxFailed: // fallthrough + case EngineGen.chat1NotifyChatChatSetConvSettings: // fallthrough + case EngineGen.chat1NotifyChatChatAttachmentUploadStart: // fallthrough + case EngineGen.chat1NotifyChatChatPromptUnfurl: // fallthrough + case EngineGen.chat1NotifyChatChatPaymentInfo: // fallthrough + case EngineGen.chat1NotifyChatChatRequestInfo: // fallthrough + case EngineGen.chat1NotifyChatChatAttachmentDownloadProgress: //fallthrough + case EngineGen.chat1NotifyChatChatAttachmentDownloadComplete: //fallthrough + case EngineGen.chat1NotifyChatChatAttachmentUploadProgress: { + const {convID} = action.payload.params + const conversationIDKey = T.Chat.conversationIDToKey(convID) + storeRegistry.getConvoState(conversationIDKey).dispatch.onEngineIncoming(action) + break + } + case EngineGen.chat1ChatUiChatCommandMarkdown: //fallthrough + case EngineGen.chat1ChatUiChatGiphyToggleResultWindow: // fallthrough + case EngineGen.chat1ChatUiChatCommandStatus: // fallthrough + case EngineGen.chat1ChatUiChatBotCommandsUpdateStatus: //fallthrough + case EngineGen.chat1ChatUiChatGiphySearchResults: { + const {convID} = action.payload.params + const conversationIDKey = T.Chat.stringToConversationIDKey(convID) + storeRegistry.getConvoState(conversationIDKey).dispatch.onEngineIncoming(action) + break + } + case EngineGen.chat1NotifyChatChatParticipantsInfo: { + const {participants: participantMap} = action.payload.params + Object.keys(participantMap ?? {}).forEach(convIDStr => { + const participants = participantMap?.[convIDStr] + const conversationIDKey = T.Chat.stringToConversationIDKey(convIDStr) + if (participants) { + storeRegistry + .getConvoState(conversationIDKey) + .dispatch.setParticipants(uiParticipantsToParticipantInfo(participants)) + } + }) + break + } + case EngineGen.chat1ChatUiChatMaybeMentionUpdate: { + const {teamName, channel, info} = action.payload.params + get().dispatch.setMaybeMentionInfo(getTeamMentionName(teamName, channel), info) + break + } + case EngineGen.chat1NotifyChatChatConvUpdate: { + const {conv} = action.payload.params + if (conv) { + const meta = Meta.inboxUIItemToConversationMeta(conv) + meta && get().dispatch.metasReceived([meta]) + } + break + } + case EngineGen.chat1ChatUiChatCoinFlipStatus: { + const {statuses} = action.payload.params + get().dispatch.updateCoinFlipStatus(statuses || []) + break + } + case EngineGen.chat1NotifyChatChatThreadsStale: + get().dispatch.onChatThreadStale(action) + break + case EngineGen.chat1NotifyChatChatSubteamRename: { + const {convs} = action.payload.params + const conversationIDKeys = (convs ?? []).map(c => T.Chat.stringToConversationIDKey(c.convID)) + get().dispatch.unboxRows(conversationIDKeys, true) + break + } + case EngineGen.chat1NotifyChatChatTLFFinalize: + get().dispatch.unboxRows([T.Chat.conversationIDToKey(action.payload.params.convID)]) + break + case EngineGen.chat1NotifyChatChatIdentifyUpdate: { + // Some participants are broken/fixed now + const {update} = action.payload.params + const usernames = update.CanonicalName.split(',') + const broken = (update.breaks.breaks || []).map(b => b.user.username) + const updates = usernames.map(name => ({info: {broken: broken.includes(name)}, name})) + get().dispatch.defer.onUsersUpdates(updates) + break + } + case EngineGen.chat1ChatUiChatInboxUnverified: + get().dispatch.onGetInboxUnverifiedConvs(action) + break + case EngineGen.chat1NotifyChatChatInboxSyncStarted: + useWaitingState.getState().dispatch.increment(S.waitingKeyChatInboxSyncStarted) + break + + case EngineGen.chat1NotifyChatChatInboxSynced: + get().dispatch.onChatInboxSynced(action) + break + case EngineGen.chat1ChatUiChatInboxLayout: + get().dispatch.updateInboxLayout(action.payload.params.layout) + get().dispatch.maybeChangeSelectedConv() + get().dispatch.ensureWidgetMetas() + break + case EngineGen.chat1NotifyChatChatInboxStale: + get().dispatch.inboxRefresh('inboxStale') + break + case EngineGen.chat1ChatUiChatInboxConversation: + get().dispatch.onGetInboxConvsUnboxed(action) + break + case EngineGen.chat1NotifyChatNewChatActivity: { + const {activity} = action.payload.params + switch (activity.activityType) { + case T.RPCChat.ChatActivityType.incomingMessage: { + const {incomingMessage} = activity + const conversationIDKey = T.Chat.conversationIDToKey(incomingMessage.convID) + storeRegistry.getConvoState(conversationIDKey).dispatch.onIncomingMessage(incomingMessage) + get().dispatch.onIncomingInboxUIItem(incomingMessage.conv ?? undefined) + break + } + case T.RPCChat.ChatActivityType.setStatus: + get().dispatch.onIncomingInboxUIItem(activity.setStatus.conv ?? undefined) + break + case T.RPCChat.ChatActivityType.readMessage: + get().dispatch.onIncomingInboxUIItem(activity.readMessage.conv ?? undefined) + break + case T.RPCChat.ChatActivityType.newConversation: + get().dispatch.onIncomingInboxUIItem(activity.newConversation.conv ?? undefined) + break + case T.RPCChat.ChatActivityType.failedMessage: { + const {failedMessage} = activity + get().dispatch.onIncomingInboxUIItem(failedMessage.conv ?? undefined) + const {outboxRecords} = failedMessage + if (!outboxRecords) return + for (const outboxRecord of outboxRecords) { + const s = outboxRecord.state + if (s.state !== T.RPCChat.OutboxStateType.error) return + const {error} = s + const conversationIDKey = T.Chat.conversationIDToKey(outboxRecord.convID) + const outboxID = T.Chat.rpcOutboxIDToOutboxID(outboxRecord.outboxID) + // This is temp until fixed by CORE-7112. We get this error but not the call to let us show the red banner + const reason = Message.rpcErrorToString(error) + storeRegistry + .getConvoState(conversationIDKey) + .dispatch.onMessageErrored(outboxID, reason, error.typ) + + if (error.typ === T.RPCChat.OutboxErrorType.identify) { + // Find out the user who failed identify + const match = error.message.match(/"(.*)"/) + const tempForceRedBox = match?.[1] + if (tempForceRedBox) { + storeRegistry + .getState('users') + .dispatch.updates([{info: {broken: true}, name: tempForceRedBox}]) + } + } + } + break + } + case T.RPCChat.ChatActivityType.membersUpdate: + get().dispatch.unboxRows([T.Chat.conversationIDToKey(activity.membersUpdate.convID)], true) + break + case T.RPCChat.ChatActivityType.setAppNotificationSettings: { + const {setAppNotificationSettings} = activity + const conversationIDKey = T.Chat.conversationIDToKey(setAppNotificationSettings.convID) + const settings = setAppNotificationSettings.settings + const cs = storeRegistry.getConvoState(conversationIDKey) + if (cs.isMetaGood()) { + cs.dispatch.updateMeta(Meta.parseNotificationSettings(settings)) + } + break + } + case T.RPCChat.ChatActivityType.expunge: { + // Get actions to update messagemap / metamap when retention policy expunge happens + const {expunge} = activity + const conversationIDKey = T.Chat.conversationIDToKey(expunge.convID) + const staticConfig = get().staticConfig + // The types here are askew. It confuses frontend MessageType with protocol MessageType. + // Placeholder is an example where it doesn't make sense. + const deletableMessageTypes = staticConfig?.deletableByDeleteHistory || Common.allMessageTypes + storeRegistry.getConvoState(conversationIDKey).dispatch.messagesWereDeleted({ + deletableMessageTypes, + upToMessageID: T.Chat.numberToMessageID(expunge.expunge.upto), + }) + break + } + case T.RPCChat.ChatActivityType.ephemeralPurge: { + const {ephemeralPurge} = activity + // Get actions to update messagemap / metamap when ephemeral messages expire + const conversationIDKey = T.Chat.conversationIDToKey(ephemeralPurge.convID) + const messageIDs = ephemeralPurge.msgs?.reduce>((arr, msg) => { + const msgID = Message.getMessageID(msg) + if (msgID) { + arr.push(msgID) + } + return arr + }, []) + + !!messageIDs && + storeRegistry.getConvoState(conversationIDKey).dispatch.messagesExploded(messageIDs) + break + } + case T.RPCChat.ChatActivityType.reactionUpdate: { + // Get actions to update the messagemap when reactions are updated + const {reactionUpdate} = activity + const conversationIDKey = T.Chat.conversationIDToKey(reactionUpdate.convID) + if (!reactionUpdate.reactionUpdates || reactionUpdate.reactionUpdates.length === 0) { + logger.warn(`Got ReactionUpdateNotif with no reactionUpdates for convID=${conversationIDKey}`) + break + } + const updates = reactionUpdate.reactionUpdates.map(ru => ({ + reactions: Message.reactionMapToReactions(ru.reactions), + targetMsgID: T.Chat.numberToMessageID(ru.targetMsgID), + })) + logger.info(`Got ${updates.length} reaction updates for convID=${conversationIDKey}`) + storeRegistry.getConvoState(conversationIDKey).dispatch.updateReactions(updates) + get().dispatch.updateUserReacjis(reactionUpdate.userReacjis) + break + } + case T.RPCChat.ChatActivityType.messagesUpdated: { + const {messagesUpdated} = activity + const conversationIDKey = T.Chat.conversationIDToKey(messagesUpdated.convID) + storeRegistry.getConvoState(conversationIDKey).dispatch.onMessagesUpdated(messagesUpdated) + break + } + default: + } + break + } + case EngineGen.chat1NotifyChatChatTypingUpdate: { + const {typingUpdates} = action.payload.params + typingUpdates?.forEach(u => { + storeRegistry + .getConvoState(T.Chat.conversationIDToKey(u.convID)) + .dispatch.setTyping(new Set(u.typers?.map(t => t.username))) + }) + break + } + case EngineGen.chat1NotifyChatChatSetConvRetention: { + const {conv, convID} = action.payload.params + if (!conv) { + logger.warn('onChatSetConvRetention: no conv given') + return + } + const meta = Meta.inboxUIItemToConversationMeta(conv) + if (!meta) { + logger.warn(`onChatSetConvRetention: no meta found for ${convID.toString()}`) + return + } + const cs = storeRegistry.getConvoState(meta.conversationIDKey) + // only insert if the convo is already in the inbox + if (cs.isMetaGood()) { + cs.dispatch.setMeta(meta) + } + break + } + case EngineGen.chat1NotifyChatChatSetTeamRetention: { + const {convs} = action.payload.params + const metas = (convs ?? []).reduce>((l, c) => { + const meta = Meta.inboxUIItemToConversationMeta(c) + if (meta) { + l.push(meta) + } + return l + }, []) + if (metas.length) { + metas.forEach(meta => { + const cs = storeRegistry.getConvoState(meta.conversationIDKey) + // only insert if the convo is already in the inbox + if (cs.isMetaGood()) { + cs.dispatch.setMeta(meta) + } + }) + get().dispatch.defer.onTeamsUpdateTeamRetentionPolicy(metas) + } + // this is a more serious problem, but we don't need to bug the user about it + logger.error( + 'got NotifyChat.ChatSetTeamRetention with no attached InboxUIItems. The local version may be out of date' + ) + break + } + case EngineGen.keybase1NotifyBadgesBadgeState: { + const {badgeState} = action.payload.params + get().dispatch.badgesUpdated(badgeState) + break + } + case EngineGen.keybase1GregorUIPushState: { + const {state} = action.payload.params + const items = state.items || [] + const goodState = items.reduce>( + (arr, {md, item}) => { + md && item && arr.push({item, md}) + return arr + }, + [] + ) + if (goodState.length !== items.length) { + logger.warn('Lost some messages in filtering out nonNull gregor items') + } + get().dispatch.updatedGregor(goodState) + break + } + default: + } + }, + onGetInboxConvsUnboxed: (action: EngineGen.Chat1ChatUiChatInboxConversationPayload) => { + // TODO not reactive + const infoMap = get().dispatch.defer.onGetUsersInfoMap() + const {convs} = action.payload.params + const inboxUIItems = JSON.parse(convs) as Array + const metas: Array = [] + let added = false as boolean + const usernameToFullname: {[username: string]: string} = {} + inboxUIItems.forEach(inboxUIItem => { + const meta = Meta.inboxUIItemToConversationMeta(inboxUIItem) + if (meta) { + metas.push(meta) + } + const participantInfo: T.Chat.ParticipantInfo = uiParticipantsToParticipantInfo( + inboxUIItem.participants ?? [] + ) + if (participantInfo.all.length > 0) { + storeRegistry + .getConvoState(T.Chat.stringToConversationIDKey(inboxUIItem.convID)) + .dispatch.setParticipants(participantInfo) + } + inboxUIItem.participants?.forEach((part: T.RPCChat.UIParticipant) => { + const {assertion, fullName} = part + if (!infoMap.get(assertion) && fullName) { + added = true + usernameToFullname[assertion] = fullName + } + }) + }) + if (added) { + get().dispatch.defer.onUsersUpdates( + Object.keys(usernameToFullname).map(name => ({ + info: {fullname: usernameToFullname[name]}, + name, + })) + ) + } + if (metas.length > 0) { + get().dispatch.metasReceived(metas) + } + }, + onGetInboxUnverifiedConvs: (action: EngineGen.Chat1ChatUiChatInboxUnverifiedPayload) => { + const {inbox} = action.payload.params + const result = JSON.parse(inbox) as T.RPCChat.UnverifiedInboxUIItems + const items: ReadonlyArray = result.items ?? [] + // We get a subset of meta information from the cache even in the untrusted payload + const metas = items.reduce>((arr, item) => { + const m = Meta.unverifiedInboxUIItemToConversationMeta(item) + m && arr.push(m) + return arr + }, []) + get().dispatch.setTrustedInboxHasLoaded() + // Check if some of our existing stored metas might no longer be valid + get().dispatch.metasReceived(metas) + }, + onIncomingInboxUIItem: conv => { + if (!conv) return + const meta = Meta.inboxUIItemToConversationMeta(conv) + const usernameToFullname = (conv.participants ?? []).reduce<{[key: string]: string}>((map, part) => { + if (part.fullName) { + map[part.assertion] = part.fullName + } + return map + }, {}) + + get().dispatch.defer.onUsersUpdates( + Object.keys(usernameToFullname).map(name => ({ + info: {fullname: usernameToFullname[name]}, + name, + })) + ) + + if (meta) { + get().dispatch.metasReceived([meta]) + } + }, + onRouteChanged: (prev, next) => { + const maybeChangeChatSelection = () => { + const wasModal = prev && getModalStack(prev).length > 0 + const isModal = next && getModalStack(next).length > 0 + // ignore if changes involve a modal + if (wasModal || isModal) { + return + } + const p = getVisibleScreen(prev) + const n = getVisibleScreen(next) + const wasChat = p?.name === Common.threadRouteName + const isChat = n?.name === Common.threadRouteName + // nothing to do with chat + if (!wasChat && !isChat) { + return + } + const pParams = p?.params as undefined | {conversationIDKey?: T.Chat.ConversationIDKey} + const nParams = n?.params as undefined | {conversationIDKey?: T.Chat.ConversationIDKey} + const wasID = pParams?.conversationIDKey + const isID = nParams?.conversationIDKey + + logger.info('maybeChangeChatSelection ', {isChat, isID, wasChat, wasID}) + + // same? ignore + if (wasChat && isChat && wasID === isID) { + // if we've never loaded anything, keep going so we load it + if (!isID || storeRegistry.getConvoState(isID).loaded) { + return + } + } + + // deselect if there was one + const deselectAction = () => { + if (wasChat && wasID && T.Chat.isValidConversationIDKey(wasID)) { + get().dispatch.unboxRows([wasID], true) + // needed? + // storeRegistry.getConvoState(wasID).dispatch.clearOrangeLine('deselected') + } + } + + // still chatting? just select new one + if (wasChat && isChat && isID && T.Chat.isValidConversationIDKey(isID)) { + deselectAction() + storeRegistry.getConvoState(isID).dispatch.selectedConversation() + return + } + + // leaving a chat + if (wasChat && !isChat) { + deselectAction() + return + } + + // going into a chat + if (isChat && isID && T.Chat.isValidConversationIDKey(isID)) { + deselectAction() + storeRegistry.getConvoState(isID).dispatch.selectedConversation() + return + } + } + + const maybeChatTabSelected = () => { + if (getTab(prev) !== Tabs.chatTab && getTab(next) === Tabs.chatTab) { + const n = getVisibleScreen(next) + const nParams = n?.params as undefined | {conversationIDKey?: T.Chat.ConversationIDKey} + const isID = nParams?.conversationIDKey + isID && storeRegistry.getConvoState(isID).dispatch.tabSelected() + } + } + maybeChangeChatSelection() + maybeChatTabSelected() + }, + onTeamBuildingFinished: users => { + const f = async () => { + // need to let the mdoal hide first else its thrashy + await timeoutPromise(500) + storeRegistry + .getConvoState(T.Chat.pendingWaitingConversationIDKey) + .dispatch.navigateToThread('justCreated') + get().dispatch.createConversation([...users].map(u => u.id)) + } + ignorePromise(f()) + }, + paymentInfoReceived: paymentInfo => { + set(s => { + s.paymentStatusMap.set(paymentInfo.paymentID, paymentInfo) + }) + }, + previewConversation: p => { + // We always make adhoc convos and never preview it + const previewConversationPersonMakesAConversation = () => { + const {participants, teamname, highlightMessageID} = p + if (teamname) return + if (!participants) return + const toFind = [...participants].sort().join(',') + const toFindN = participants.length + for (const cs of chatStores.values()) { + const names = cs.getState().participants.name + if (names.length !== toFindN) continue + const p = [...names].sort().join(',') + if (p === toFind) { + storeRegistry + .getConvoState(cs.getState().id) + .dispatch.navigateToThread('justCreated', highlightMessageID) + return + } + } + + storeRegistry + .getConvoState(T.Chat.pendingWaitingConversationIDKey) + .dispatch.navigateToThread('justCreated') + get().dispatch.createConversation(participants, highlightMessageID) + } + + // We preview channels + const previewConversationTeam = async () => { + const {conversationIDKey, highlightMessageID, teamname, reason} = p + if (conversationIDKey) { + if ( + reason === 'messageLink' || + reason === 'teamMention' || + reason === 'channelHeader' || + reason === 'manageView' + ) { + // Add preview channel to inbox + await T.RPCChat.localPreviewConversationByIDLocalRpcPromise({ + convID: T.Chat.keyToConversationID(conversationIDKey), + }) + } + + storeRegistry + .getConvoState(conversationIDKey) + .dispatch.navigateToThread('previewResolved', highlightMessageID) + return + } + + if (!teamname) { + return + } + + const channelname = p.channelname || 'general' + try { + const results = await T.RPCChat.localFindConversationsLocalRpcPromise({ + identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, + membersType: T.RPCChat.ConversationMembersType.team, + oneChatPerTLF: true, + tlfName: teamname, + topicName: channelname, + topicType: T.RPCChat.TopicType.chat, + visibility: T.RPCGen.TLFVisibility.private, + }) + const resultMetas = (results.uiConversations || []) + .map(row => Meta.inboxUIItemToConversationMeta(row)) + .filter(Boolean) + + const first = resultMetas[0] + if (!first) { + if (p.reason === 'appLink') { + navigateAppend({ + props: { + error: + "We couldn't find this team chat channel. Please check that you're a member of the team and the channel exists.", + }, + selected: 'keybaseLinkError', + }) + return + } else { + return + } + } + + const results2 = await T.RPCChat.localPreviewConversationByIDLocalRpcPromise({ + convID: T.Chat.keyToConversationID(first.conversationIDKey), + }) + const meta = Meta.inboxUIItemToConversationMeta(results2.conv) + if (meta) { + get().dispatch.metasReceived([meta]) + } + + storeRegistry + .getConvoState(first.conversationIDKey) + .dispatch.navigateToThread('previewResolved', highlightMessageID) + } catch (error) { + if ( + error instanceof RPCError && + error.code === T.RPCGen.StatusCode.scteamnotfound && + reason === 'appLink' + ) { + navigateAppend({ + props: { + error: + "We couldn't find this team. Please check that you're a member of the team and the channel exists.", + }, + selected: 'keybaseLinkError', + }) + return + } else { + throw error + } + } + } + previewConversationPersonMakesAConversation() + ignorePromise(previewConversationTeam()) + }, + queueMetaHandle: () => { + // Watch the meta queue and take up to 10 items. Choose the last items first since they're likely still visible + const f = async () => { + const maxToUnboxAtATime = 10 + const ar = [...metaQueue] + const maybeUnbox = ar.slice(0, maxToUnboxAtATime) + metaQueue = new Set(ar.slice(maxToUnboxAtATime)) + const conversationIDKeys = untrustedConversationIDKeys(maybeUnbox) + if (conversationIDKeys.length) { + get().dispatch.unboxRows(conversationIDKeys) + } + if (metaQueue.size && conversationIDKeys.length) { + await timeoutPromise(100) + } + if (metaQueue.size) { + get().dispatch.queueMetaHandle() + } + } + ignorePromise(f()) + }, + queueMetaToRequest: ids => { + let added = false as boolean + untrustedConversationIDKeys(ids).forEach(k => { + if (!metaQueue.has(k)) { + added = true + metaQueue.add(k) + } + }) + if (added) { + // only unboxMore if something changed + get().dispatch.queueMetaHandle() + } else { + logger.info('skipping meta queue run, queue unchanged') + } + }, + refreshBotPublicCommands: username => { + set(s => { + s.botPublicCommands.delete(username) + }) + const f = async () => { + let res: T.RPCChat.ListBotCommandsLocalRes | undefined + try { + res = await T.RPCChat.localListPublicBotCommandsLocalRpcPromise({ + username, + }) + } catch (error) { + if (error instanceof RPCError) { + logger.info('refreshBotPublicCommands: failed to get public commands: ' + error.message) + set(s => { + s.botPublicCommands.set(username, {commands: [], loadError: true}) + }) + } + } + const commands = (res?.commands ?? []).reduce>((l, c) => { + l.push(c.name) + return l + }, []) + + set(s => { + s.botPublicCommands.set(username, {commands, loadError: false}) + }) + } + ignorePromise(f()) + }, + resetConversationErrored: () => { + set(s => { + s.createConversationError = undefined + }) + }, + resetState: () => { + set(s => ({ + ...s, + ...initialStore, + dispatch: s.dispatch, + staticConfig: s.staticConfig, + })) + // also blow away convoState + clearChatStores() + }, + setInboxNumSmallRows: (rows, ignoreWrite) => { + set(s => { + if (rows > 0) { + s.inboxNumSmallRows = rows + } + }) + if (ignoreWrite) { + return + } + const {inboxNumSmallRows} = get() + if (inboxNumSmallRows === undefined || inboxNumSmallRows <= 0) { + return + } + const f = async () => { + try { + await T.RPCGen.configGuiSetValueRpcPromise({ + path: 'ui.inboxSmallRows', + value: {i: inboxNumSmallRows, isNull: false}, + }) + } catch {} + } + ignorePromise(f()) + }, + setInfoPanelTab: tab => { + set(s => { + s.infoPanelSelectedTab = tab + }) + }, + setMaybeMentionInfo: (name, info) => { + set(s => { + const {maybeMentionMap} = s + maybeMentionMap.set(name, T.castDraft(info)) + }) + }, + setTrustedInboxHasLoaded: () => { + set(s => { + s.trustedInboxHasLoaded = true + }) + }, + toggleInboxSearch: enabled => { + set(s => { + const {inboxSearch} = s + if (enabled && !inboxSearch) { + s.inboxSearch = T.castDraft(makeInboxSearchInfo()) + } else if (!enabled && inboxSearch) { + s.inboxSearch = undefined + } + }) + const f = async () => { + const {inboxSearch} = get() + if (!inboxSearch) { + await T.RPCChat.localCancelActiveInboxSearchRpcPromise() + return + } + if (inboxSearch.nameStatus === 'initial') { + get().dispatch.inboxSearch('') + } + } + ignorePromise(f()) + }, + toggleSmallTeamsExpanded: () => { + set(s => { + s.smallTeamsExpanded = !s.smallTeamsExpanded + }) + }, + unboxRows: (ids, force) => { + // We want to unbox rows that have scroll into view + const f = async () => { + if (!useConfigState.getState().loggedIn) { + return + } + + // Get valid keys that we aren't already loading or have loaded + const conversationIDKeys = ids.reduce((arr: Array, id) => { + if (id && T.Chat.isValidConversationIDKey(id)) { + const cs = storeRegistry.getConvoState(id) + const trustedState = cs.meta.trustedState + if (force || (trustedState !== 'requesting' && trustedState !== 'trusted')) { + arr.push(id) + cs.dispatch.updateMeta({trustedState: 'requesting'}) + } + } + return arr + }, []) + + if (!conversationIDKeys.length) { + return + } + logger.info( + `unboxRows: unboxing len: ${conversationIDKeys.length} convs: ${conversationIDKeys.join(',')}` + ) + try { + await T.RPCChat.localRequestInboxUnboxRpcPromise({ + convIDs: conversationIDKeys.map(k => T.Chat.keyToConversationID(k)), + }) + } catch (error) { + if (error instanceof RPCError) { + logger.info(`unboxRows: failed ${error.desc}`) + } + } + } + ignorePromise(f()) + }, + updateCoinFlipStatus: statuses => { + set(s => { + const {flipStatusMap} = s + statuses.forEach(status => { + flipStatusMap.set(status.gameID, T.castDraft(status)) + }) + }) + }, + updateInboxLayout: str => { + set(s => { + try { + const {inboxHasLoaded} = s + const _layout = JSON.parse(str) as unknown + if (!_layout || typeof _layout !== 'object') { + console.log('Invalid layout?') + return + } + const layout = _layout as T.RPCChat.UIInboxLayout + + if (!isEqual(s.inboxLayout, layout)) { + s.inboxLayout = T.castDraft(layout) + } + s.inboxHasLoaded = !!layout + if (!inboxHasLoaded) { + // on first layout, initialize any drafts and muted status + // After the first layout, any other updates will come in the form of meta updates. + layout.smallTeams?.forEach(t => { + const cs = storeRegistry.getConvoState(t.convID) + cs.dispatch.updateFromUIInboxLayout(t) + }) + layout.bigTeams?.forEach(t => { + if (t.state === T.RPCChat.UIInboxBigTeamRowTyp.channel) { + const cs = storeRegistry.getConvoState(t.channel.convID) + cs.dispatch.updateFromUIInboxLayout(t.channel) + } + }) + } + } catch (e) { + logger.info('failed to JSON parse inbox layout: ' + e) + } + }) + }, + updateInfoPanel: (show, tab) => { + set(s => { + s.infoPanelShowing = show + s.infoPanelSelectedTab = tab + }) + }, + updateLastCoord: coord => { + set(s => { + s.lastCoord = coord + }) + const f = async () => { + const {accuracy, lat, lon} = coord + await T.RPCChat.localLocationUpdateRpcPromise({coord: {accuracy, lat, lon}}) + } + ignorePromise(f()) + }, + updateUserReacjis: userReacjis => { + set(s => { + const {skinTone, topReacjis} = userReacjis + s.userReacjis.skinTone = skinTone + // filter out non-simple emojis + s.userReacjis.topReacjis = + T.castDraft(topReacjis)?.filter(r => /^:[^:]+:$/.test(r.name)) ?? defaultTopReacjis + }) + }, + updatedGregor: items => { + const explodingItems = items.filter(i => + i.item.category.startsWith(Common.explodingModeGregorKeyPrefix) + ) + if (!explodingItems.length) { + // No conversations have exploding modes, clear out what is set + for (const s of chatStores.values()) { + s.getState().dispatch.setExplodingMode(0, true) + } + } else { + // logger.info('Got push state with some exploding modes') + explodingItems.forEach(i => { + try { + const {category, body} = i.item + const secondsString = uint8ArrayToString(body) + const seconds = parseInt(secondsString, 10) + if (isNaN(seconds)) { + logger.warn(`Got dirty exploding mode ${secondsString} for category ${category}`) + return + } + const _conversationIDKey = category.substring(Common.explodingModeGregorKeyPrefix.length) + const conversationIDKey = T.Chat.stringToConversationIDKey(_conversationIDKey) + storeRegistry.getConvoState(conversationIDKey).dispatch.setExplodingMode(seconds, true) + } catch (e) { + logger.info('Error parsing exploding' + e) + } + }) + } + + set(s => { + const blockButtons = items.some(i => i.item.category.startsWith(blockButtonsGregorPrefix)) + if (blockButtons || s.blockButtonsMap.size > 0) { + const shouldKeepExistingBlockButtons = new Map() + s.blockButtonsMap.forEach((_, teamID: string) => shouldKeepExistingBlockButtons.set(teamID, false)) + items + .filter(i => i.item.category.startsWith(blockButtonsGregorPrefix)) + .forEach(i => { + try { + const teamID = i.item.category.substring(blockButtonsGregorPrefix.length) + if (!s.blockButtonsMap.get(teamID)) { + const body = bodyToJSON(i.item.body) as {adder: string} + const adder = body.adder + s.blockButtonsMap.set(teamID, {adder}) + } else { + shouldKeepExistingBlockButtons.set(teamID, true) + } + } catch (e) { + logger.info('block buttons parse fail', e) + } + }) + shouldKeepExistingBlockButtons.forEach((keep, teamID) => { + if (!keep) { + s.blockButtonsMap.delete(teamID) + } + }) + } + }) + }, + } + return { + ...initialStore, + dispatch, + getBackCount: conversationIDKey => { + let count = 0 + chatStores.forEach(s => { + const {id, badge} = s.getState() + // only show sum of badges that aren't for the current conversation + if (id !== conversationIDKey) { + count += badge + } + }) + return count + }, + getBadgeHiddenCount: ids => { + let badgeCount = 0 + let hiddenCount = 0 + + chatStores.forEach(s => { + const {id, badge} = s.getState() + if (ids.has(id)) { + badgeCount -= badge + hiddenCount -= 1 + } + }) + + return {badgeCount, hiddenCount} + }, + getUnreadIndicies: ids => { + const unreadIndices: Map = new Map() + ids.forEach((cur, idx) => { + Array.from(chatStores.values()).some(s => { + const {id, badge} = s.getState() + if (id === cur && badge > 0) { + unreadIndices.set(idx, badge) + return true + } + return false + }) + }) + return unreadIndices + }, + } +}) + +export function makeChatScreen>( + Component: COM, + options?: { + getOptions?: GetOptionsRet | ((props: ChatProviderProps>) => GetOptionsRet) + skipProvider?: boolean + canBeNullConvoID?: boolean + } +) { + return { + ...options, + screen: function Screen(p: ChatProviderProps>) { + const Comp = Component as any + return options?.skipProvider ? ( + + ) : ( + + + + ) + }, + } +} + +export * from '@/stores/convostate' +export * from '@/constants/chat2/common' +export * from '@/constants/chat2/meta' +export * from '@/constants/chat2/message' + +export { + noConversationIDKey, + pendingWaitingConversationIDKey, + pendingErrorConversationIDKey, + isValidConversationIDKey, + dummyConversationIDKey, +} from '@/constants/types/chat2/common' diff --git a/shared/constants/config/index.tsx b/shared/stores/config.tsx similarity index 67% rename from shared/constants/config/index.tsx rename to shared/stores/config.tsx index 5fb8ffbb8ea9..249d056e4de9 100644 --- a/shared/constants/config/index.tsx +++ b/shared/stores/config.tsx @@ -1,25 +1,24 @@ -import * as T from '../types' -import {ignorePromise, timeoutPromise} from '../utils' -import {serverConfigFileName} from '../platform' -import {waitingKeyConfigLogin} from '../strings' +import type * as NetInfo from '@react-native-community/netinfo' +import * as T from '@/constants/types' +import {ignorePromise, timeoutPromise} from '@/constants/utils' +import {waitingKeyConfigLogin} from '@/constants/strings' import * as EngineGen from '@/actions/engine-gen-gen' -import * as RemoteGen from '@/actions/remote-gen' import * as Stats from '@/engine/stats' import * as Z from '@/util/zustand' -import {noConversationIDKey} from '../types/chat2/common' +import {noConversationIDKey} from '@/constants/types/chat2/common' import isEqual from 'lodash/isEqual' import logger from '@/logger' -import type {Tab} from '../tabs' +import type {Tab} from '@/constants/tabs' import {RPCError, convertToError, isEOFError, isErrorTransient, niceError} from '@/util/errors' -import {defaultUseNativeFrame, isMobile} from '../platform' +import {defaultUseNativeFrame, isMobile} from '@/constants/platform' import {type CommonResponseHandler} from '@/engine/types' -import {invalidPasswordErrorString} from './util' -import {navigateAppend, switchTab} from '../router2/util' -import {storeRegistry} from '../store-registry' -import {getSelectedConversation} from '@/constants/chat2/common' +import {invalidPasswordErrorString} from '@/constants/config' +import {navigateAppend} from '@/constants/router2' + +export type ConnectionType = NetInfo.NetInfoStateType | 'notavailable' type Store = T.Immutable<{ - forceSmallNav: boolean + active: boolean allowAnimatedEmojis: boolean androidShare?: | {type: T.RPCGen.IncomingShareType.file; urls: Array} @@ -28,6 +27,7 @@ type Store = T.Immutable<{ badgeState?: T.RPCGen.BadgeState configuredAccounts: Array defaultUsername: string + forceSmallNav: boolean globalError?: Error | RPCError gregorReachable?: T.RPCGen.Reachable gregorPushState: Array<{md: T.RPCGregor.Metadata; item: T.RPCGregor.Item}> @@ -50,7 +50,7 @@ type Store = T.Immutable<{ | 'reloggedIn' | 'startupOrReloginButNotInARush' mobileAppState: 'active' | 'background' | 'inactive' | 'unknown' - networkStatus?: {online: boolean; type: T.Config.ConnectionType; isInit?: boolean} + networkStatus?: {online: boolean; type: ConnectionType; isInit?: boolean} notifySound: boolean openAtLogin: boolean outOfDate: T.Config.OutOfDate @@ -86,6 +86,7 @@ type Store = T.Immutable<{ }> const initialStore: Store = { + active: true, allowAnimatedEmojis: true, androidShare: undefined, appFocused: true, @@ -146,7 +147,7 @@ const initialStore: Store = { export interface State extends Store { dispatch: { - dynamic: { + defer: { copyToClipboard: (s: string) => void dumpLogsNative?: (reason: string) => Promise onFilePickerError?: (error: Error) => void @@ -163,7 +164,6 @@ export interface State extends Store { changedFocus: (f: boolean) => void checkForUpdate: () => void dumpLogs: (reason: string) => Promise - eventFromRemoteWindows: (action: RemoteGen.Actions) => void filePickerError: (error: Error) => void initAppUpdateLoop: () => void initNotifySound: () => void @@ -177,16 +177,17 @@ export interface State extends Store { setLoginError: (error?: RPCError) => void logoutAndTryToLogInAs: (username: string) => void onEngineConnected: () => void - onEngineDisonnected: () => void onEngineIncoming: (action: EngineGen.Actions) => void - osNetworkStatusChanged: (online: boolean, type: T.Config.ConnectionType, isInit?: boolean) => void + osNetworkStatusChanged: (online: boolean, type: ConnectionType, isInit?: boolean) => void openUnlockFolders: (devices: ReadonlyArray) => void powerMonitorEvent: (event: string) => void resetState: (isDebug?: boolean) => void remoteWindowNeedsProps: (component: string, params: string) => void resetRevokedSelf: () => void - revoke: (deviceName: string) => void + revoke: (deviceName: string, wasCurrentDevice: boolean) => void + refreshAccounts: () => Promise setAccounts: (a: Store['configuredAccounts']) => void + setActive: (a: boolean) => void setAndroidShare: (s: Store['androidShare']) => void setBadgeState: (b: State['badgeState']) => void setDefaultUsername: (u: string) => void @@ -201,8 +202,9 @@ export interface State extends Store { setStartupDetails: (st: Omit) => void setOpenAtLogin: (open: boolean) => void setOutOfDate: (outOfDate: T.Config.OutOfDate) => void - setUserSwitching: (sw: boolean) => void + setUpdating: () => void setUseNativeFrame: (use: boolean) => void + setUserSwitching: (sw: boolean) => void showMain: () => void toggleRuntimeStats: () => void updateGregorCategory: (category: string, body: string, dtime?: {offset: number; time: number}) => void @@ -245,15 +247,6 @@ export const useConfigState = Z.createZustand((set, get) => { set(s => { s.gregorReachable = r }) - // Re-get info about our account if you log in/we're done handshaking/became reachable - if (r === T.RPCGen.Reachable.yes) { - // not in waiting state - if (storeRegistry.getState('daemon').handshakeWaiters.size === 0) { - ignorePromise(storeRegistry.getState('daemon').dispatch.loadDaemonBootstrapStatus()) - } - } - - storeRegistry.getState('teams').dispatch.eagerLoadTeams() } const setGregorPushState = (state: T.RPCGen.Gregor1.State) => { @@ -276,29 +269,6 @@ export const useConfigState = Z.createZustand((set, get) => { set(s => { s.allowAnimatedEmojis = allowAnimatedEmojis }) - - const lastSeenItem = goodState.find(i => i.item.category === 'whatsNewLastSeenVersion') - storeRegistry.getState('whats-new').dispatch.updateLastSeen(lastSeenItem) - } - - const updateApp = () => { - const f = async () => { - await T.RPCGen.configStartUpdateIfNeededRpcPromise() - } - ignorePromise(f()) - // * If user choose to update: - // We'd get killed and it doesn't matter what happens here. - // * If user hits "Ignore": - // Note that we ignore the snooze here, so the state shouldn't change, - // and we'd back to where we think we still need an update. So we could - // have just unset the "updating" flag.However, in case server has - // decided to pull out the update between last time we asked the updater - // and now, we'd be in a wrong state if we didn't check with the service. - // Since user has interacted with it, we still ask the service to make - // sure. - set(s => { - s.outOfDate.updating = true - }) } const updateRuntimeStats = (stats?: T.RPCGen.RuntimeStats) => { @@ -320,13 +290,6 @@ export const useConfigState = Z.createZustand((set, get) => { set(s => { s.appFocused = f }) - - if (!isMobile || !f) { - return - } - const {dispatch} = storeRegistry.getConvoState(getSelectedConversation()) - dispatch.loadMoreMessages({reason: 'foregrounding'}) - dispatch.markThreadAsRead() }, checkForUpdate: () => { const f = async () => { @@ -334,10 +297,7 @@ export const useConfigState = Z.createZustand((set, get) => { } ignorePromise(f()) }, - dumpLogs: async reason => { - await get().dispatch.dynamic.dumpLogsNative?.(reason) - }, - dynamic: { + defer: { copyToClipboard: () => { throw new Error('copyToClipboard not implemented?????') }, @@ -353,156 +313,11 @@ export const useConfigState = Z.createZustand((set, get) => { showMainNative: undefined, showShareActionSheet: undefined, }, - eventFromRemoteWindows: (action: RemoteGen.Actions) => { - switch (action.type) { - case RemoteGen.resetStore: - break - case RemoteGen.openChatFromWidget: { - get().dispatch.showMain() - storeRegistry - .getConvoState(action.payload.conversationIDKey) - .dispatch.navigateToThread('inboxSmall') - break - } - case RemoteGen.inboxRefresh: { - storeRegistry.getState('chat').dispatch.inboxRefresh('widgetRefresh') - break - } - case RemoteGen.engineConnection: { - if (action.payload.connected) { - storeRegistry.getState('engine').dispatch.onEngineConnected() - } else { - storeRegistry.getState('engine').dispatch.onEngineDisconnected() - } - break - } - case RemoteGen.switchTab: { - switchTab(action.payload.tab) - break - } - case RemoteGen.setCriticalUpdate: { - storeRegistry.getState('fs').dispatch.setCriticalUpdate(action.payload.critical) - break - } - case RemoteGen.userFileEditsLoad: { - storeRegistry.getState('fs').dispatch.userFileEditsLoad() - break - } - case RemoteGen.openFilesFromWidget: { - storeRegistry.getState('fs').dispatch.dynamic.openFilesFromWidgetDesktop?.(action.payload.path) - break - } - case RemoteGen.saltpackFileOpen: { - storeRegistry.getState('deeplinks').dispatch.handleSaltPackOpen(action.payload.path) - break - } - case RemoteGen.pinentryOnCancel: { - storeRegistry.getState('pinentry').dispatch.dynamic.onCancel?.() - break - } - case RemoteGen.pinentryOnSubmit: { - storeRegistry.getState('pinentry').dispatch.dynamic.onSubmit?.(action.payload.password) - break - } - case RemoteGen.openPathInSystemFileManager: { - storeRegistry - .getState('fs') - .dispatch.dynamic.openPathInSystemFileManagerDesktop?.(action.payload.path) - break - } - case RemoteGen.unlockFoldersSubmitPaperKey: { - T.RPCGen.loginPaperKeySubmitRpcPromise( - {paperPhrase: action.payload.paperKey}, - 'unlock-folders:waiting' - ) - .then(() => { - get().dispatch.openUnlockFolders([]) - }) - .catch((e: unknown) => { - if (!(e instanceof RPCError)) return - set(s => { - s.unlockFoldersError = e.desc - }) - }) - break - } - case RemoteGen.closeUnlockFolders: { - T.RPCGen.rekeyRekeyStatusFinishRpcPromise() - .then(() => {}) - .catch(() => {}) - get().dispatch.openUnlockFolders([]) - break - } - case RemoteGen.stop: { - storeRegistry.getState('settings').dispatch.stop(action.payload.exitCode) - break - } - case RemoteGen.trackerChangeFollow: { - storeRegistry - .getState('tracker2') - .dispatch.changeFollow(action.payload.guiID, action.payload.follow) - break - } - case RemoteGen.trackerIgnore: { - storeRegistry.getState('tracker2').dispatch.ignore(action.payload.guiID) - break - } - case RemoteGen.trackerCloseTracker: { - storeRegistry.getState('tracker2').dispatch.closeTracker(action.payload.guiID) - break - } - case RemoteGen.trackerLoad: { - storeRegistry.getState('tracker2').dispatch.load(action.payload) - break - } - case RemoteGen.link: - { - const {link} = action.payload - storeRegistry.getState('deeplinks').dispatch.handleAppLink(link) - } - break - case RemoteGen.installerRan: - get().dispatch.installerRan() - break - case RemoteGen.updateNow: - updateApp() - break - case RemoteGen.powerMonitorEvent: - get().dispatch.powerMonitorEvent(action.payload.event) - break - case RemoteGen.showMain: - get().dispatch.showMain() - break - case RemoteGen.dumpLogs: - ignorePromise(get().dispatch.dumpLogs(action.payload.reason)) - break - case RemoteGen.remoteWindowWantsProps: - get().dispatch.remoteWindowNeedsProps(action.payload.component, action.payload.param) - break - case RemoteGen.updateWindowMaxState: - set(s => { - s.windowState.isMaximized = action.payload.max - }) - break - case RemoteGen.updateWindowState: - get().dispatch.updateWindowState(action.payload.windowState) - break - case RemoteGen.updateWindowShown: { - const win = action.payload.component - set(s => { - s.windowShownCount.set(win, (s.windowShownCount.get(win) ?? 0) + 1) - }) - break - } - case RemoteGen.previewConversation: - storeRegistry - .getState('chat') - .dispatch.previewConversation({participants: [action.payload.participant], reason: 'tracker'}) - break - } + dumpLogs: async reason => { + await get().dispatch.defer.dumpLogsNative?.(reason) }, filePickerError: error => { - get().dispatch.dynamic.onFilePickerError?.(error) + get().dispatch.defer.onFilePickerError?.(error) }, initAppUpdateLoop: () => { const f = async () => { @@ -571,8 +386,6 @@ export const useConfigState = Z.createZustand((set, get) => { set(s => { s.installerRanCount++ }) - - storeRegistry.getState('fs').dispatch.checkKbfsDaemonRpcStatus() }, loadIsOnline: () => { const f = async () => { @@ -592,59 +405,6 @@ export const useConfigState = Z.createZustand((set, get) => { set(s => { s.loadOnStartPhase = phase }) - - if (phase === 'startupOrReloginButNotInARush') { - const getFollowerInfo = () => { - const {uid} = storeRegistry.getState('current-user') - logger.info(`getFollowerInfo: init; uid=${uid}`) - if (uid) { - // request follower info in the background - T.RPCGen.configRequestFollowingAndUnverifiedFollowersRpcPromise() - .then(() => {}) - .catch(() => {}) - } - } - - const updateServerConfig = async () => { - if (get().loggedIn) { - try { - await T.RPCGen.configUpdateLastLoggedInAndServerConfigRpcPromise({ - serverConfigPath: serverConfigFileName, - }) - } catch {} - } - } - - const updateTeams = () => { - storeRegistry.getState('teams').dispatch.getTeams() - storeRegistry.getState('teams').dispatch.refreshTeamRoleMap() - } - - const updateSettings = () => { - storeRegistry.getState('settings-contacts').dispatch.loadContactImportEnabled() - } - - const updateChat = async () => { - // On login lets load the untrusted inbox. This helps make some flows easier - if (storeRegistry.getState('current-user').username) { - const {inboxRefresh} = storeRegistry.getState('chat').dispatch - inboxRefresh('bootstrap') - } - try { - const rows = await T.RPCGen.configGuiGetValueRpcPromise({path: 'ui.inboxSmallRows'}) - const ri = rows.i ?? -1 - if (ri > 0) { - storeRegistry.getState('chat').dispatch.setInboxNumSmallRows(ri, true) - } - } catch {} - } - - getFollowerInfo() - ignorePromise(updateServerConfig()) - updateTeams() - updateSettings() - ignorePromise(updateChat()) - } }, login: (username, passphrase) => { const cancelDesc = 'Canceling RPC' @@ -661,7 +421,7 @@ export const useConfigState = Z.createZustand((set, get) => { 'keybase.1.provisionUi.DisplayAndPromptSecret': cancelOnCallback, 'keybase.1.provisionUi.PromptNewDeviceName': (_, response) => { cancelOnCallback(undefined, response) - storeRegistry.getState('provision').dispatch.dynamic.setUsername?.(username) + navigateAppend({props: {username}, selected: 'username'}) }, 'keybase.1.provisionUi.chooseDevice': cancelOnCallback, 'keybase.1.provisionUi.chooseGPGMethod': cancelOnCallback, @@ -729,8 +489,6 @@ export const useConfigState = Z.createZustand((set, get) => { ignorePromise(f()) }, onEngineConnected: () => { - storeRegistry.getState('daemon').dispatch.startHandshake() - // The startReachability RPC call both starts and returns the current // reachability state. Then we'll get updates of changes from this state via reachabilityChanged. // This should be run on app start and service re-connect in case the service somehow crashed or was restarted manually. @@ -755,16 +513,9 @@ export const useConfigState = Z.createZustand((set, get) => { } ignorePromise(registerForGregorNotifications()) - get().dispatch.dynamic.onEngineConnectedDesktop?.() + get().dispatch.defer.onEngineConnectedDesktop?.() get().dispatch.loadOnStart('initialStartupAsEarlyAsPossible') }, - onEngineDisonnected: () => { - const f = async () => { - await logger.dump() - } - ignorePromise(f()) - storeRegistry.getState('daemon').dispatch.setError(new Error('Disconnected')) - }, onEngineIncoming: action => { switch (action.type) { case EngineGen.keybase1GregorUIPushState: { @@ -798,34 +549,6 @@ export const useConfigState = Z.createZustand((set, get) => { } break } - case EngineGen.keybase1NotifyTeamAvatarUpdated: { - const {name} = action.payload.params - storeRegistry.getState('avatar').dispatch.updated(name) - break - } - case EngineGen.keybase1NotifyTrackingTrackingChanged: { - const {isTracking, username} = action.payload.params - storeRegistry.getState('followers').dispatch.updateFollowing(username, isTracking) - break - } - case EngineGen.keybase1NotifyTrackingTrackingInfo: { - const {uid, followers: _newFollowers, followees: _newFollowing} = action.payload.params - if (storeRegistry.getState('current-user').uid !== uid) { - return - } - const newFollowers = new Set(_newFollowers) - const newFollowing = new Set(_newFollowing) - const { - following: oldFollowing, - followers: oldFollowers, - dispatch, - } = storeRegistry.getState('followers') - const following = isEqual(newFollowing, oldFollowing) ? oldFollowing : newFollowing - const followers = isEqual(newFollowers, oldFollowers) ? oldFollowers : newFollowers - dispatch.replace(followers, following) - break - } - case EngineGen.keybase1ReachabilityReachabilityChanged: if (get().loggedIn) { setGregorReachable(action.payload.params.reachability.reachable) @@ -843,7 +566,7 @@ export const useConfigState = Z.createZustand((set, get) => { })) }) }, - osNetworkStatusChanged: (online: boolean, type: T.Config.ConnectionType, isInit?: boolean) => { + osNetworkStatusChanged: (online: boolean, type: ConnectionType, isInit?: boolean) => { const old = get().networkStatus set(s => { if (!s.networkStatus) { @@ -881,6 +604,30 @@ export const useConfigState = Z.createZustand((set, get) => { } ignorePromise(f()) }, + refreshAccounts: async () => { + const defaultUsername = get().defaultUsername + const configuredAccounts = (await T.RPCGen.loginGetConfiguredAccountsRpcPromise()) ?? [] + const {setAccounts, setDefaultUsername} = get().dispatch + + let existingDefaultFound = false as boolean + let currentName = '' + const nextConfiguredAccounts: Array = [] + + configuredAccounts.forEach(account => { + const {username, isCurrent, fullname, hasStoredSecret} = account + if (username === defaultUsername) { + existingDefaultFound = true + } + if (isCurrent) { + currentName = account.username + } + nextConfiguredAccounts.push({fullname, hasStoredSecret, username}) + }) + if (!existingDefaultFound) { + setDefaultUsername(currentName) + } + setAccounts(nextConfiguredAccounts) + }, remoteWindowNeedsProps: (component, params) => { set(s => { const map = s.remoteWindowNeedsProps.get(component) ?? new Map() @@ -909,8 +656,7 @@ export const useConfigState = Z.createZustand((set, get) => { userSwitching: s.userSwitching, })) }, - revoke: name => { - const wasCurrentDevice = storeRegistry.getState('current-user').deviceName === name + revoke: (name, wasCurrentDevice) => { if (wasCurrentDevice) { const {configuredAccounts, defaultUsername} = get() const acc = configuredAccounts.find(n => n.username !== defaultUsername) @@ -920,7 +666,6 @@ export const useConfigState = Z.createZustand((set, get) => { s.justRevokedSelf = name s.revokedTrigger++ }) - storeRegistry.getState('daemon').dispatch.loadDaemonAccounts() } }, setAccounts: a => { @@ -930,6 +675,11 @@ export const useConfigState = Z.createZustand((set, get) => { } }) }, + setActive: a => { + set(s => { + s.active = a + }) + }, setAndroidShare: share => { set(s => { s.androidShare = T.castDraft(share) @@ -1015,11 +765,6 @@ export const useConfigState = Z.createZustand((set, get) => { if (!changed) return - if (loggedIn) { - ignorePromise(storeRegistry.getState('daemon').dispatch.loadDaemonBootstrapStatus()) - } - storeRegistry.getState('daemon').dispatch.loadDaemonAccounts() - const {loadOnStart} = get().dispatch if (loggedIn) { if (!causedByStartup) { @@ -1035,14 +780,6 @@ export const useConfigState = Z.createZustand((set, get) => { } else { Z.resetAllStores() } - - if (loggedIn) { - storeRegistry.getState('fs').dispatch.checkKbfsDaemonRpcStatus() - } - - if (!causedByStartup) { - ignorePromise(storeRegistry.getState('daemon').dispatch.refreshAccounts()) - } }, setLoginError: error => { set(s => { @@ -1057,9 +794,6 @@ export const useConfigState = Z.createZustand((set, get) => { set(s => { s.mobileAppState = nextAppState }) - if (nextAppState === 'background' && storeRegistry.getState('chat').inboxSearch) { - storeRegistry.getState('chat').dispatch.toggleInboxSearch(false) - } }, setNotifySound: n => { set(s => { @@ -1099,6 +833,11 @@ export const useConfigState = Z.createZustand((set, get) => { } }) }, + setUpdating: () => { + set(s => { + s.outOfDate.updating = true + }) + }, setUseNativeFrame: use => { set(s => { s.useNativeFrame = use @@ -1119,7 +858,7 @@ export const useConfigState = Z.createZustand((set, get) => { }) }, showMain: () => { - get().dispatch.dynamic.showMainNative?.() + get().dispatch.defer.showMainNative?.() }, toggleRuntimeStats: () => { const f = async () => { diff --git a/shared/constants/chat2/convostate.tsx b/shared/stores/convostate.tsx similarity index 94% rename from shared/constants/chat2/convostate.tsx rename to shared/stores/convostate.tsx index aa73c3798337..372a1e9cb9f1 100644 --- a/shared/constants/chat2/convostate.tsx +++ b/shared/stores/convostate.tsx @@ -1,7 +1,7 @@ // TODO remove useChatNavigateAppend // TODO remove -import * as TeamsUtil from '../teams/util' -import * as PlatformSpecific from '../platform-specific' +import * as TeamsUtil from '@/constants/teams' +import * as PlatformSpecific from '@/util/platform-specific' import { clearModals, navigateAppend, @@ -11,19 +11,19 @@ import { getVisibleScreen, getModalStack, navToThread, -} from '../router2/util' -import {isIOS} from '../platform' -import {updateImmer} from '../utils' -import * as T from '../types' +} from '@/constants/router2' +import {isIOS} from '@/constants/platform' +import {updateImmer} from '@/constants/utils' +import * as T from '@/constants/types' import * as Styles from '@/styles' -import * as Common from './common' -import * as Tabs from '../tabs' +import * as Common from '@/constants/chat2/common' +import * as Tabs from '@/constants/tabs' import * as EngineGen from '@/actions/engine-gen-gen' -import * as Message from './message' -import * as Meta from './meta' +import * as Message from '@/constants/chat2/message' +import * as Meta from '@/constants/chat2/meta' import * as React from 'react' import * as Z from '@/util/zustand' -import {makeActionForOpenPathInFilesTab} from '@/constants/fs/util' +import {navToPath} from '@/constants/fs' import HiddenString from '@/util/hidden-string' import isEqual from 'lodash/isEqual' import logger from '@/logger' @@ -32,21 +32,23 @@ import type {DebouncedFunc} from 'lodash' import {RPCError} from '@/util/errors' import {findLast} from '@/util/arrays' import {mapGetEnsureValue} from '@/util/map' -import {noConversationIDKey} from '../types/chat2/common' +import {noConversationIDKey} from '@/constants/types/chat2/common' import {type StoreApi, type UseBoundStore, useStore} from 'zustand' -import * as Platform from '../platform' +import * as Platform from '@/constants/platform' import KB2 from '@/util/electron' import NotifyPopup from '@/util/notify-popup' import {hexToUint8Array} from 'uint8array-extras' import assign from 'lodash/assign' import {clearChatTimeCache} from '@/util/timestamp' import {registerDebugClear} from '@/util/debug' -import * as Config from '@/constants/config/util' +import * as Config from '@/constants/config' import {isMobile} from '@/constants/platform' -import {enumKeys, ignorePromise, shallowEqual} from '../utils' +import {enumKeys, ignorePromise, shallowEqual} from '@/constants/utils' import * as Strings from '@/constants/strings' -import {storeRegistry} from '../store-registry' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' +import type {useChatState, RefreshReason} from '@/stores/chat2' const {darwinCopyToChatTempUploadFile} = KB2.functions @@ -223,6 +225,25 @@ export interface ConvoState extends ConvoStore { botCommandsUpdateStatus: (b: T.RPCChat.UIBotCommandsUpdateStatus) => void channelSuggestionsTriggered: () => void clearAttachmentView: () => void + defer: { + chatBlockButtonsMapHas: (teamID: T.RPCGen.TeamID) => boolean + chatInboxLayoutSmallTeamsFirstConvID: () => T.Chat.ConversationIDKey | undefined + chatInboxRefresh: (reason: RefreshReason) => void + chatMetasReceived: (metas: ReadonlyArray) => void + chatNavigateToInbox: () => void + chatPaymentInfoReceived: (messageID: T.Chat.MessageID, paymentInfo: T.Chat.ChatPaymentInfo) => void + chatPreviewConversation: ( + p: Parameters['dispatch']['previewConversation']>[0] + ) => void + chatResetConversationErrored: () => void + chatUnboxRows: (convIDs: ReadonlyArray, force: boolean) => void + chatUpdateInfoPanel: ( + show: boolean, + tab: 'settings' | 'members' | 'attachments' | 'bots' | undefined + ) => void + teamsGetMembers: (teamID: T.RPCGen.TeamID) => void + usersGetBio: (username: string) => void + } dismissBottomBanner: () => void dismissBlockButtons: (teamID: T.RPCGen.TeamID) => void dismissJourneycard: (cardType: T.RPCChat.JourneycardType, ordinal: T.Chat.Ordinal) => void @@ -296,7 +317,6 @@ export interface ConvoState extends ConvoStore { setReplyTo: (o: T.Chat.Ordinal) => void setThreadSearchQuery: (query: string) => void setTyping: DebouncedFunc<(t: Set) => void> - setupSubscriptions: () => void showInfoPanel: (show: boolean, tab: 'settings' | 'members' | 'attachments' | 'bots' | undefined) => void tabSelected: () => void threadSearch: (query: string) => void @@ -372,11 +392,68 @@ type ScrollDirection = 'none' | 'back' | 'forward' export const numMessagesOnInitialLoad = isMobile ? 20 : 100 export const numMessagesOnScrollback = isMobile ? 100 : 100 -const createSlice: Z.ImmerStateCreator = (set, get) => { +const stubDefer: ConvoState['dispatch']['defer'] = { + chatBlockButtonsMapHas: () => { + throw new Error('convostate defer not initialized') + }, + chatInboxLayoutSmallTeamsFirstConvID: () => { + throw new Error('convostate defer not initialized') + }, + chatInboxRefresh: () => { + throw new Error('convostate defer not initialized') + }, + chatMetasReceived: () => { + throw new Error('convostate defer not initialized') + }, + chatNavigateToInbox: () => { + throw new Error('convostate defer not initialized') + }, + chatPaymentInfoReceived: () => { + throw new Error('convostate defer not initialized') + }, + chatPreviewConversation: () => { + throw new Error('convostate defer not initialized') + }, + chatResetConversationErrored: () => { + throw new Error('convostate defer not initialized') + }, + chatUnboxRows: () => { + throw new Error('convostate defer not initialized') + }, + chatUpdateInfoPanel: () => { + throw new Error('convostate defer not initialized') + }, + teamsGetMembers: () => { + throw new Error('convostate defer not initialized') + }, + usersGetBio: () => { + throw new Error('convostate defer not initialized') + }, +} + +let convoDeferImpl: ConvoState['dispatch']['defer'] | undefined + +export const setConvoDefer = (impl: ConvoState['dispatch']['defer']) => { + convoDeferImpl = impl + for (const store of chatStores.values()) { + const s = store.getState() + store.setState({ + ...s, + dispatch: { + ...s.dispatch, + defer: impl, + }, + }) + } +} + +const createSlice = (): Z.ImmerStateCreator => (set, get) => { + const defer = convoDeferImpl ?? stubDefer + const closeBotModal = () => { clearModals() if (get().meta.teamname) { - storeRegistry.getState('teams').dispatch.getMembers(get().meta.teamID) + get().dispatch.defer.teamsGetMembers(get().meta.teamID) } } @@ -493,13 +570,13 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } const onClick = () => { - storeRegistry.getState('config').dispatch.showMain() - storeRegistry.getState('chat').dispatch.navigateToInbox() + useConfigState.getState().dispatch.showMain() + get().dispatch.defer.chatNavigateToInbox() get().dispatch.navigateToThread('desktopNotification') } const onClose = () => {} logger.info('invoking NotifyPopup for chat notification') - const sound = storeRegistry.getState('config').notifySound + const sound = useConfigState.getState().notifySound const cleanBody = body.replaceAll(/!>(.*?) = (set, get) => { logger.error(errMsg) throw new Error(errMsg) } - storeRegistry.getState('chat').dispatch.paymentInfoReceived(paymentInfo) + get().dispatch.defer.chatPaymentInfoReceived(T.Chat.numberToMessageID(msgID), paymentInfo) getConvoState(conversationIDKey).dispatch.paymentInfoReceived(msgID, paymentInfo) } @@ -648,7 +725,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { const refreshMutualTeamsInConv = () => { const f = async () => { const {id: conversationIDKey} = get() - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username const otherParticipants = Meta.getRowParticipants(get().participants, username || '') const results = await T.RPCChat.localGetMutualTeamsLocalRpcPromise( {usernames: otherParticipants}, @@ -834,7 +911,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } const onInboxFailed = (convID: Uint8Array, error: T.RPCChat.InboxUIItemError) => { - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username const conversationIDKey = T.Chat.conversationIDToKey(convID) switch (error.typ) { case T.RPCChat.ConversationErrorType.transient: @@ -1069,7 +1146,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } // If there are block buttons on this conversation, clear them. - if (storeRegistry.getState('chat').blockButtonsMap.has(meta.teamID)) { + if (get().dispatch.defer.chatBlockButtonsMapHas(meta.teamID)) { get().dispatch.dismissBlockButtons(meta.teamID) } @@ -1237,8 +1314,8 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { }, blockConversation: reportUser => { const f = async () => { - storeRegistry.getState('chat').dispatch.navigateToInbox() - storeRegistry.getState('config').dispatch.dynamic.persistRoute?.(false, false) + get().dispatch.defer.chatNavigateToInbox() + useConfigState.getState().dispatch.defer.persistRoute?.(false, false) await T.RPCChat.localSetConversationStatusLocalRpcPromise({ conversationID: get().getConvID(), identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, @@ -1270,6 +1347,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { s.attachmentViewMap = new Map() }) }, + defer, dismissBlockButtons: teamID => { const f = async () => { try { @@ -1341,7 +1419,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { // Nav to inbox but don't use findNewConversation since changeSelectedConversation // does that with better information. It knows the conversation is hidden even before // that state bounces back. - storeRegistry.getState('chat').dispatch.navigateToInbox() + get().dispatch.defer.chatNavigateToInbox() get().dispatch.showInfoPanel(false, undefined) } @@ -1394,7 +1472,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { const params = vs?.params as undefined | {conversationIDKey?: T.Chat.ConversationIDKey} if (params?.conversationIDKey === get().id) { // select a convo - const next = storeRegistry.getState('chat').inboxLayout?.smallTeams?.[0]?.convID + const next = get().dispatch.defer.chatInboxLayoutSmallTeamsFirstConvID() if (next) { getConvoState(next).dispatch.navigateToThread('findNewestConversationFromLayout') } @@ -1419,8 +1497,8 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { hit: T.RPCChat.MessageTypes['chat.1.chatUi.chatLoadGalleryHit']['inParam'] ) => { const getLastOrdinal = () => get().messageOrdinals?.at(-1) ?? T.Chat.numberToOrdinal(0) - const username = storeRegistry.getState('current-user').username - const devicename = storeRegistry.getState('current-user').deviceName + const username = useCurrentUserState.getState().username + const devicename = useCurrentUserState.getState().deviceName const m = Message.uiMessageToMessage( conversationIDKey, hit.message, @@ -1564,8 +1642,8 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { s.loaded = true }) - const username = storeRegistry.getState('current-user').username - const devicename = storeRegistry.getState('current-user').deviceName + const username = useCurrentUserState.getState().username + const devicename = useCurrentUserState.getState().deviceName const getLastOrdinal = () => get().messageOrdinals?.at(-1) ?? T.Chat.numberToOrdinal(0) const uiMessages = JSON.parse(thread) as T.RPCChat.UIMessages @@ -1659,9 +1737,8 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { logger.warn(`loadMoreMessages: error: ${error.desc}`) // no longer in team if (error.code === T.RPCGen.StatusCode.scchatnotinteam) { - const {inboxRefresh, navigateToInbox} = storeRegistry.getState('chat').dispatch - inboxRefresh('maybeKickedFromTeam') - navigateToInbox() + get().dispatch.defer.chatInboxRefresh('maybeKickedFromTeam') + get().dispatch.defer.chatNavigateToInbox() } if (error.code !== T.RPCGen.StatusCode.scteamreaderror) { // scteamreaderror = user is not in team. they'll see the rekey screen so don't throw for that @@ -1703,8 +1780,8 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { }) if (result.message) { - const devicename = storeRegistry.getState('current-user').deviceName - const username = storeRegistry.getState('current-user').username + const devicename = useCurrentUserState.getState().deviceName + const username = useCurrentUserState.getState().username const getLastOrdinal = () => get().messageOrdinals?.at(-1) ?? T.Chat.numberToOrdinal(0) const goodMessage = Message.uiMessageToMessage( get().id, @@ -1753,7 +1830,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { }, markTeamAsRead: teamID => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { logger.info('bail on not logged in') return } @@ -1764,7 +1841,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { }, markThreadAsRead: force => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { logger.info('mark read bail on not logged in') return } @@ -1957,7 +2034,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { logger.warn("messageReplyPrivately: can't find message to reply to", ordinal) return } - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username if (!username) { throw new Error('messageReplyPrivately: making a convo while logged out?') } @@ -1988,7 +2065,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { const text = formatTextForQuoting(message.text.stringValue()) getConvoState(newThreadCID).dispatch.injectIntoInput(text) - storeRegistry.getState('chat').dispatch.metasReceived([meta]) + get().dispatch.defer.chatMetasReceived([meta]) getConvoState(newThreadCID).dispatch.navigateToThread('createdMessagePrivately') } ignorePromise(f()) @@ -2133,7 +2210,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { loadMessages() // load meta - storeRegistry.getState('chat').dispatch.unboxRows([get().id], true) + get().dispatch.defer.chatUnboxRows([get().id], true) const updateNav = () => { const reason = _reason @@ -2169,9 +2246,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { clearModals() } - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {conversationIDKey}, selected: Common.threadRouteName}, replace) + navigateAppend({props: {conversationIDKey}, selected: Common.threadRouteName}, replace) } } updateNav() @@ -2249,7 +2324,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { }, onIncomingMessage: incoming => { const {message: cMsg} = incoming - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username // check for a reaction outbox notification before doing anything if ( cMsg.state === T.RPCChat.MessageUnboxedState.outbox && @@ -2275,7 +2350,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } const conversationIDKey = get().id - const devicename = storeRegistry.getState('current-user').deviceName + const devicename = useCurrentUserState.getState().deviceName const getLastOrdinal = () => get().messageOrdinals?.at(-1) ?? T.Chat.numberToOrdinal(0) // special case mutations @@ -2331,8 +2406,8 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { }, onMessagesUpdated: messagesUpdated => { if (!messagesUpdated.updates) return - const username = storeRegistry.getState('current-user').username - const devicename = storeRegistry.getState('current-user').deviceName + const username = useCurrentUserState.getState().username + const devicename = useCurrentUserState.getState().deviceName const getLastOrdinal = () => get().messageOrdinals?.at(-1) ?? T.Chat.numberToOrdinal(0) const toAdd = new Array() messagesUpdated.updates.forEach(uimsg => { @@ -2363,7 +2438,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { ? Config.teamFolder(meta.teamname) : Config.privateFolderWithUsers(participantInfo.name) ) - makeActionForOpenPathInFilesTab(path) + navToPath(path) }, paymentInfoReceived: (messageID, paymentInfo) => { set(s => { @@ -2461,7 +2536,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { // remove all bad people const goodParticipants = new Set(participantInfo.all) meta.resetParticipants.forEach(r => goodParticipants.delete(r)) - storeRegistry.getState('chat').dispatch.previewConversation({ + get().dispatch.defer.chatPreviewConversation({ participants: [...goodParticipants], reason: 'resetChatWithoutThem', }) @@ -2493,7 +2568,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { const fetchConversationBio = () => { const participantInfo = get().participants - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username const otherParticipants = Meta.getRowParticipants(participantInfo, username || '') if (otherParticipants.length === 1) { // we're in a one-on-one convo @@ -2504,7 +2579,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { return } - storeRegistry.getState('users').dispatch.getBio(username) + get().dispatch.defer.usersGetBio(username) } } @@ -2514,19 +2589,19 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { if (isMetaGood()) { const {teamID, teamname} = meta if (teamname) { - storeRegistry.getState('teams').dispatch.getMembers(teamID) + get().dispatch.defer.teamsGetMembers(teamID) } } } ensureSelectedTeamLoaded() const participantInfo = get().participants const force = !get().isMetaGood() || participantInfo.all.length === 0 - storeRegistry.getState('chat').dispatch.unboxRows([conversationIDKey], force) + get().dispatch.defer.chatUnboxRows([conversationIDKey], force) set(s => { s.threadLoadStatus = T.RPCChat.UIChatThreadStatusTyp.none }) fetchConversationBio() - storeRegistry.getState('chat').dispatch.resetConversationErrored() + get().dispatch.defer.chatResetConversationErrored() }, sendAudioRecording: async (path, duration, amps) => { const outboxID = Common.generateOutboxID() @@ -2608,7 +2683,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { let ordinal = T.Chat.numberToOrdinal(0) // Editing last message if (e === 'last') { - const editLastUser = storeRegistry.getState('current-user').username + const editLastUser = useCurrentUserState.getState().username // Editing your last message const ordinals = get().messageOrdinals const found = @@ -2703,7 +2778,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } const conversationIDKey = get().id const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { logger.info('mark unread bail on not logged in') return } @@ -2846,9 +2921,8 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } }) }, 1000), - setupSubscriptions: () => {}, showInfoPanel: (show, tab) => { - storeRegistry.getState('chat').dispatch.updateInfoPanel(show, tab) + get().dispatch.defer.chatUpdateInfoPanel(show, tab) const conversationIDKey = get().id if (Platform.isPhone) { const visibleScreen = getVisibleScreen() @@ -2876,8 +2950,8 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { const f = async () => { const conversationIDKey = get().id const getLastOrdinal = () => get().messageOrdinals?.at(-1) ?? T.Chat.numberToOrdinal(0) - const username = storeRegistry.getState('current-user').username - const devicename = storeRegistry.getState('current-user').deviceName + const username = useCurrentUserState.getState().username + const devicename = useCurrentUserState.getState().deviceName const onDone = () => { set(s => { s.threadSearchInfo.status = 'done' @@ -3252,10 +3326,9 @@ registerDebugClear(() => { const createConvoStore = (id: T.Chat.ConversationIDKey) => { const existing = chatStores.get(id) if (existing) return existing - const next = Z.createZustand(createSlice) + const next = Z.createZustand(createSlice()) next.setState({id}) chatStores.set(id, next) - next.getState().dispatch.setupSubscriptions() return next } @@ -3330,14 +3403,12 @@ export const ProviderScreen = React.memo(function ProviderScreen(p: { import type {NavigateAppendType} from '@/router-v2/route-params' export const useChatNavigateAppend = () => { - const useRouterState = storeRegistry.getStore('router') - const navigateAppend = useRouterState(s => s.dispatch.navigateAppend) const cid = useChatContext(s => s.id) return React.useCallback( (makePath: (cid: T.Chat.ConversationIDKey) => NavigateAppendType, replace?: boolean) => { navigateAppend(makePath(cid), replace) }, - [cid, navigateAppend] + [cid] ) } diff --git a/shared/constants/crypto/index.tsx b/shared/stores/crypto.tsx similarity index 97% rename from shared/constants/crypto/index.tsx rename to shared/stores/crypto.tsx index c4ee70dcfe73..99fd2fdd3781 100644 --- a/shared/constants/crypto/index.tsx +++ b/shared/stores/crypto.tsx @@ -1,15 +1,15 @@ import * as Z from '@/util/zustand' -import {ignorePromise} from '../utils' -import {isMobile} from '../platform' -import {waitingKeyCrypto} from '../strings' +import {ignorePromise} from '@/constants/utils' +import {isMobile} from '@/constants/platform' +import {waitingKeyCrypto} from '@/constants/strings' import HiddenString from '@/util/hidden-string' import logger from '@/logger' -import * as T from '../types' +import * as T from '@/constants/types' import {RPCError} from '@/util/errors' -import {navigateAppend} from '../router2/util' -import {storeRegistry} from '../store-registry' -import {Operations} from './util' -export * from './util' +import {navigateAppend} from '@/constants/router2' +import {useCurrentUserState} from '@/stores/current-user' +import {Operations} from '@/constants/crypto' +export * from '@/constants/crypto' type CommonStore = { bytesComplete: number @@ -214,7 +214,7 @@ export const useCryptoState = Z.createZustand((set, get) => { const encrypt = (destinationDir: string = '') => { const f = async () => { const start = get().encrypt - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username const signed = start.options.sign const inputType = start.inputType const input = start.input.stringValue() @@ -349,7 +349,7 @@ export const useCryptoState = Z.createZustand((set, get) => { const output = await (inputType === 'text' ? callText() : callFile()) - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username set(s => { onSuccess(s.sign, s.sign.input.stringValue() === input, '', output, inputType, true, username, '') }) @@ -530,7 +530,7 @@ export const useCryptoState = Z.createZustand((set, get) => { // User set themselves as a recipient, so don't show 'includeSelf' option // However we don't want to set hideIncludeSelf if we are also encrypting to an SBS user (since we must force includeSelf) - const currentUser = storeRegistry.getState('current-user').username + const currentUser = useCurrentUserState.getState().username const {options} = get().encrypt if (usernames.includes(currentUser) && !hasSBS) { get().dispatch.setEncryptOptions(options, true) diff --git a/shared/constants/current-user/index.tsx b/shared/stores/current-user.tsx similarity index 87% rename from shared/constants/current-user/index.tsx rename to shared/stores/current-user.tsx index 8219dbc286f9..aaba7e2cc2cd 100644 --- a/shared/constants/current-user/index.tsx +++ b/shared/stores/current-user.tsx @@ -1,6 +1,7 @@ -import type * as T from '../types' +import type * as T from '@/constants/types' import * as Z from '@/util/zustand' +// This store has no dependencies on other stores and is safe to import directly from other stores. type Store = T.Immutable<{ deviceID: T.RPCGen.DeviceID deviceName: string diff --git a/shared/constants/daemon/index.tsx b/shared/stores/daemon.tsx similarity index 70% rename from shared/constants/daemon/index.tsx rename to shared/stores/daemon.tsx index 15d3f95b7f9c..340fcd3949e5 100644 --- a/shared/constants/daemon/index.tsx +++ b/shared/stores/daemon.tsx @@ -1,25 +1,26 @@ import logger from '@/logger' -import {ignorePromise} from '../utils' -import * as T from '../types' +import {ignorePromise} from '@/constants/utils' +import * as T from '@/constants/types' import * as Z from '@/util/zustand' -import {storeRegistry} from '../store-registry' -import {maxHandshakeTries} from '../values' +import {maxHandshakeTries} from '@/constants/values' // Load accounts, this call can be slow so we attempt to continue w/o waiting if we determine we're logged in // normally this wouldn't be worth it but this is startup const getAccountsWaitKey = 'config.getAccounts' type Store = T.Immutable<{ + bootstrapStatus?: T.RPCGen.BootstrapStatus error?: Error - handshakeState: T.Config.DaemonHandshakeState handshakeFailedReason: string handshakeRetriesLeft: number + handshakeState: T.Config.DaemonHandshakeState + handshakeVersion: number handshakeWaiters: Map // if we ever restart handshake up this so we can ignore any waiters for old things - handshakeVersion: number }> const initialStore: Store = { + bootstrapStatus: undefined, handshakeFailedReason: '', handshakeRetriesLeft: maxHandshakeTries, handshakeState: 'starting', @@ -29,9 +30,8 @@ const initialStore: Store = { export interface State extends Store { dispatch: { - loadDaemonAccounts: () => void + loadDaemonAccounts: (configuredAccountsLength: number, loggedIn: boolean, refreshAccounts: () => Promise) => void loadDaemonBootstrapStatus: () => Promise - refreshAccounts: () => Promise resetState: () => void setError: (e?: Error) => void setFailed: (r: string) => void @@ -91,7 +91,6 @@ export const useDaemonState = Z.createZustand((set, get) => { // When there are no more waiters, we can show the actual app - let _emitStartupOnLoadDaemonConnectedOnce = false const dispatch: State['dispatch'] = { daemonHandshake: version => { get().dispatch.setState('waitingForWaiters') @@ -109,22 +108,19 @@ export const useDaemonState = Z.createZustand((set, get) => { wait(name, version, true) try { await get().dispatch.loadDaemonBootstrapStatus() - storeRegistry.getState('dark-mode').dispatch.loadDarkPrefs() - storeRegistry.getState('chat').dispatch.loadStaticConfig() } finally { wait(name, version, false) } } ignorePromise(f()) - get().dispatch.loadDaemonAccounts() }, daemonHandshakeDone: () => { get().dispatch.setState('done') }, - loadDaemonAccounts: () => { + loadDaemonAccounts: (configuredAccountsLength: number, loggedIn: boolean, refreshAccounts: () => Promise) => { const f = async () => { const version = get().handshakeVersion - if (storeRegistry.getState('config').configuredAccounts.length) { + if (configuredAccountsLength) { // bail on already loaded return } @@ -133,7 +129,7 @@ export const useDaemonState = Z.createZustand((set, get) => { const handshakeVersion = version // did we beat getBootstrapStatus? - if (!storeRegistry.getState('config').loggedIn) { + if (!loggedIn) { handshakeWait = true } @@ -143,7 +139,7 @@ export const useDaemonState = Z.createZustand((set, get) => { wait(getAccountsWaitKey, handshakeVersion, true) } - await get().dispatch.refreshAccounts() + await refreshAccounts() if (handshakeWait) { // someone dismissed this already? @@ -170,32 +166,22 @@ export const useDaemonState = Z.createZustand((set, get) => { const {wait} = get().dispatch const f = async () => { - const {setBootstrap} = storeRegistry.getState('current-user').dispatch - const {setDefaultUsername} = storeRegistry.getState('config').dispatch const s = await T.RPCGen.configGetBootstrapStatusRpcPromise() - const {userReacjis, deviceName, deviceID, uid, loggedIn, username} = s - setBootstrap({deviceID, deviceName, uid, username}) - if (username) { - setDefaultUsername(username) - } - if (loggedIn) { - storeRegistry.getState('config').dispatch.setUserSwitching(false) - } + set(state => { + state.bootstrapStatus = T.castDraft(s) + }) - logger.info(`[Bootstrap] loggedIn: ${loggedIn ? 1 : 0}`) - storeRegistry.getState('config').dispatch.setLoggedIn(loggedIn, false) - storeRegistry.getState('chat').dispatch.updateUserReacjis(userReacjis) + logger.info(`[Bootstrap] loggedIn: ${s.loggedIn ? 1 : 0}`) // set HTTP srv info if (s.httpSrvInfo) { logger.info(`[Bootstrap] http server: addr: ${s.httpSrvInfo.address} token: ${s.httpSrvInfo.token}`) - storeRegistry.getState('config').dispatch.setHTTPSrvInfo(s.httpSrvInfo.address, s.httpSrvInfo.token) } else { logger.info(`[Bootstrap] http server: no info given`) } // if we're logged in act like getAccounts is done already - if (loggedIn) { + if (s.loggedIn) { const {handshakeWaiters} = get() if (handshakeWaiters.get(getAccountsWaitKey)) { wait(getAccountsWaitKey, version, false) @@ -205,39 +191,6 @@ export const useDaemonState = Z.createZustand((set, get) => { return await f() }, onRestartHandshakeNative: _onRestartHandshakeNative, - refreshAccounts: async () => { - const configuredAccounts = (await T.RPCGen.loginGetConfiguredAccountsRpcPromise()) ?? [] - // already have one? - const {defaultUsername} = storeRegistry.getState('config') - const {setAccounts, setDefaultUsername} = storeRegistry.getState('config').dispatch - - let existingDefaultFound = false as boolean - let currentName = '' - const nextConfiguredAccounts: Array = [] - const usernameToFullname: {[username: string]: string} = {} - - configuredAccounts.forEach(account => { - const {username, isCurrent, fullname, hasStoredSecret} = account - if (username === defaultUsername) { - existingDefaultFound = true - } - if (isCurrent) { - currentName = account.username - } - nextConfiguredAccounts.push({hasStoredSecret, username}) - usernameToFullname[username] = fullname - }) - if (!existingDefaultFound) { - setDefaultUsername(currentName) - } - setAccounts(nextConfiguredAccounts) - storeRegistry.getState('users').dispatch.updates( - Object.keys(usernameToFullname).map(name => ({ - info: {fullname: usernameToFullname[name]}, - name, - })) - ) - }, resetState: () => { set(s => ({ ...s, @@ -268,13 +221,6 @@ export const useDaemonState = Z.createZustand((set, get) => { set(s => { s.handshakeState = ds }) - - if (ds !== 'done') return - - if (!_emitStartupOnLoadDaemonConnectedOnce) { - _emitStartupOnLoadDaemonConnectedOnce = true - storeRegistry.getState('config').dispatch.loadOnStart('connectedToDaemonForFirstTime') - } }, startHandshake: () => { get().dispatch.setError() diff --git a/shared/constants/darkmode/index.tsx b/shared/stores/darkmode.tsx similarity index 95% rename from shared/constants/darkmode/index.tsx rename to shared/stores/darkmode.tsx index f5e240e3a4d3..c616904f1e4e 100644 --- a/shared/constants/darkmode/index.tsx +++ b/shared/stores/darkmode.tsx @@ -1,10 +1,11 @@ -import * as T from '../types' +import * as T from '@/constants/types' import * as Z from '@/util/zustand' import {Appearance} from 'react-native' -import {isMobile} from '../platform' +import {isMobile} from '@/constants/platform' export type DarkModePreference = 'system' | 'alwaysDark' | 'alwaysLight' +// This store has no dependencies on other stores and is safe to import directly from other stores. type Store = T.Immutable<{ darkModePreference: DarkModePreference systemDarkMode: boolean diff --git a/shared/constants/devices/index.tsx b/shared/stores/devices.tsx similarity index 96% rename from shared/constants/devices/index.tsx rename to shared/stores/devices.tsx index 61608a6b712f..a8404ad69f98 100644 --- a/shared/constants/devices/index.tsx +++ b/shared/stores/devices.tsx @@ -1,8 +1,8 @@ import * as React from 'react' import * as Z from '@/util/zustand' -import * as S from '../strings' -import {ignorePromise, updateImmerMap} from '../utils' -import * as T from '../types' +import * as S from '@/constants/strings' +import {ignorePromise, updateImmerMap} from '@/constants/utils' +import * as T from '@/constants/types' import * as EngineGen from '@/actions/engine-gen-gen' import debounce from 'lodash/debounce' diff --git a/shared/constants/followers/index.tsx b/shared/stores/followers.tsx similarity index 92% rename from shared/constants/followers/index.tsx rename to shared/stores/followers.tsx index f7f550f23102..b403722e07af 100644 --- a/shared/constants/followers/index.tsx +++ b/shared/stores/followers.tsx @@ -1,6 +1,6 @@ import * as T from '@/constants/types' import * as Z from '@/util/zustand' - +// This store has no dependencies on other stores and is safe to import directly from other stores. type Store = T.Immutable<{ followers: Set following: Set diff --git a/shared/constants/fs/index.tsx b/shared/stores/fs.tsx similarity index 68% rename from shared/constants/fs/index.tsx rename to shared/stores/fs.tsx index b5f112cfcbe5..f3bdde88ab18 100644 --- a/shared/constants/fs/index.tsx +++ b/shared/stores/fs.tsx @@ -1,160 +1,32 @@ import * as EngineGen from '@/actions/engine-gen-gen' -import {ignorePromise, timeoutPromise} from '../utils' -import * as S from '../strings' -import {requestPermissionsToWrite} from '../platform-specific' -import * as Tabs from '../tabs' -import * as T from '../types' +import {ignorePromise, timeoutPromise} from '@/constants/utils' +import * as S from '@/constants/strings' +import {requestPermissionsToWrite} from '@/util/platform-specific' +import * as Tabs from '@/constants/tabs' +import * as T from '@/constants/types' import * as Z from '@/util/zustand' import NotifyPopup from '@/util/notify-popup' import {RPCError} from '@/util/errors' import logger from '@/logger' -import {isLinux, isMobile} from '../platform' import {tlfToPreferredOrder} from '@/util/kbfs' import isObject from 'lodash/isObject' import isEqual from 'lodash/isEqual' -import {settingsFsTab} from '../settings/util' -import {navigateAppend, navigateUp} from '../router2/util' -import {storeRegistry} from '../store-registry' +import {navigateAppend, navigateUp} from '@/constants/router2' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' +import * as Constants from '@/constants/fs' -export {makeActionForOpenPathInFilesTab} from './util' +export * from '@/constants/fs' -const subscriptionDeduplicateIntervalSecond = 1 -export const defaultPath = T.FS.stringToPath('/keybase') - -export const rpcFolderTypeToTlfType = (rpcFolderType: T.RPCGen.FolderType) => { - switch (rpcFolderType) { - case T.RPCGen.FolderType.private: - return T.FS.TlfType.Private - case T.RPCGen.FolderType.public: - return T.FS.TlfType.Public - case T.RPCGen.FolderType.team: - return T.FS.TlfType.Team - default: - return null - } -} - -export const rpcConflictStateToConflictState = ( - rpcConflictState?: T.RPCGen.ConflictState -): T.FS.ConflictState => { - if (rpcConflictState) { - if (rpcConflictState.conflictStateType === T.RPCGen.ConflictStateType.normalview) { - const nv = rpcConflictState.normalview - return makeConflictStateNormalView({ - localViewTlfPaths: (nv.localViews || []).reduce>((arr, p) => { - p.PathType === T.RPCGen.PathType.kbfs && arr.push(rpcPathToPath(p.kbfs)) - return arr - }, []), - resolvingConflict: nv.resolvingConflict, - stuckInConflict: nv.stuckInConflict, - }) - } else { - const nv = rpcConflictState.manualresolvinglocalview.normalView - return makeConflictStateManualResolvingLocalView({ - normalViewTlfPath: nv.PathType === T.RPCGen.PathType.kbfs ? rpcPathToPath(nv.kbfs) : defaultPath, - }) - } - } else { - return tlfNormalViewWithNoConflict - } -} - -export const getSyncConfigFromRPC = ( - tlfName: string, - tlfType: T.FS.TlfType, - config?: T.RPCGen.FolderSyncConfig -): T.FS.TlfSyncConfig => { - if (!config) { - return tlfSyncDisabled - } - switch (config.mode) { - case T.RPCGen.FolderSyncMode.disabled: - return tlfSyncDisabled - case T.RPCGen.FolderSyncMode.enabled: - return tlfSyncEnabled - case T.RPCGen.FolderSyncMode.partial: - return makeTlfSyncPartial({ - enabledPaths: config.paths - ? config.paths.map(str => T.FS.getPathFromRelative(tlfName, tlfType, str)) - : [], - }) - default: - return tlfSyncDisabled - } -} - -// See Installer.m: KBExitFuseKextError -export const ExitCodeFuseKextError = 4 -// See Installer.m: KBExitFuseKextPermissionError -export const ExitCodeFuseKextPermissionError = 5 -// See Installer.m: KBExitAuthCanceledError -export const ExitCodeAuthCanceledError = 6 - -export const emptyNewFolder: T.FS.Edit = { - error: undefined, - name: 'New Folder', - originalName: 'New Folder', - parentPath: T.FS.stringToPath('/keybase'), - type: T.FS.EditType.NewFolder, -} - -export const prefetchNotStarted: T.FS.PrefetchNotStarted = { - state: T.FS.PrefetchState.NotStarted, -} - -export const prefetchComplete: T.FS.PrefetchComplete = { - state: T.FS.PrefetchState.Complete, -} - -export const emptyPrefetchInProgress: T.FS.PrefetchInProgress = { - bytesFetched: 0, - bytesTotal: 0, - endEstimate: 0, - startTime: 0, - state: T.FS.PrefetchState.InProgress, -} - -const pathItemMetadataDefault = { - lastModifiedTimestamp: 0, - lastWriter: '', - name: 'unknown', - prefetchStatus: prefetchNotStarted, - size: 0, - writable: false, -} - -export const emptyFolder: T.FS.FolderPathItem = { - ...pathItemMetadataDefault, - children: new Set(), - progress: T.FS.ProgressType.Pending, - type: T.FS.PathType.Folder, -} - -export const emptyFile: T.FS.FilePathItem = { - ...pathItemMetadataDefault, - type: T.FS.PathType.File, -} - -export const emptySymlink: T.FS.SymlinkPathItem = { - ...pathItemMetadataDefault, - linkTarget: '', - type: T.FS.PathType.Symlink, -} - -export const unknownPathItem: T.FS.UnknownPathItem = { - ...pathItemMetadataDefault, - type: T.FS.PathType.Unknown, -} - -export const tlfSyncEnabled: T.FS.TlfSyncEnabled = { +const tlfSyncEnabled: T.FS.TlfSyncEnabled = { mode: T.FS.TlfSyncMode.Enabled, } -export const tlfSyncDisabled: T.FS.TlfSyncDisabled = { +const tlfSyncDisabled: T.FS.TlfSyncDisabled = { mode: T.FS.TlfSyncMode.Disabled, } -export const makeTlfSyncPartial = ({ +const makeTlfSyncPartial = ({ enabledPaths, }: { enabledPaths?: T.FS.TlfSyncPartial['enabledPaths'] @@ -163,7 +35,7 @@ export const makeTlfSyncPartial = ({ mode: T.FS.TlfSyncMode.Partial, }) -export const makeConflictStateNormalView = ({ +const makeConflictStateNormalView = ({ localViewTlfPaths, resolvingConflict, stuckInConflict, @@ -174,16 +46,16 @@ export const makeConflictStateNormalView = ({ type: T.FS.ConflictStateType.NormalView, }) -export const tlfNormalViewWithNoConflict = makeConflictStateNormalView({}) +const tlfNormalViewWithNoConflict = makeConflictStateNormalView({}) -export const makeConflictStateManualResolvingLocalView = ({ +const makeConflictStateManualResolvingLocalView = ({ normalViewTlfPath, }: Partial): T.FS.ConflictStateManualResolvingLocalView => ({ - normalViewTlfPath: normalViewTlfPath || defaultPath, + normalViewTlfPath: normalViewTlfPath || Constants.defaultPath, type: T.FS.ConflictStateType.ManualResolvingLocalView, }) -export const makeTlf = (p: Partial): T.FS.Tlf => { +const makeTlf = (p: Partial): T.FS.Tlf => { const {conflictState, isFavorite, isIgnored, isNew, name, resetParticipants, syncConfig, teamId, tlfMtime} = p return { @@ -204,186 +76,90 @@ export const makeTlf = (p: Partial): T.FS.Tlf => { } } -export const emptySyncingFoldersProgress: T.FS.SyncingFoldersProgress = { - bytesFetched: 0, - bytesTotal: 0, - endEstimate: 0, - start: 0, -} - -export const emptyOverallSyncStatus: T.FS.OverallSyncStatus = { - diskSpaceStatus: T.FS.DiskSpaceStatus.Ok, - showingBanner: false, - syncingFoldersProgress: emptySyncingFoldersProgress, -} - -export const defaultPathUserSetting: T.FS.PathUserSetting = { - sort: T.FS.SortSetting.NameAsc, -} - -export const defaultTlfListPathUserSetting: T.FS.PathUserSetting = { - sort: T.FS.SortSetting.TimeAsc, -} - -export const emptyDownloadState: T.FS.DownloadState = { - canceled: false, - done: false, - endEstimate: 0, - error: '', - localPath: '', - progress: 0, -} - -export const emptyDownloadInfo: T.FS.DownloadInfo = { - filename: '', - isRegularDownload: false, - path: defaultPath, - startTime: 0, -} - -export const emptyPathItemActionMenu: T.FS.PathItemActionMenu = { - downloadID: undefined, - downloadIntent: undefined, - previousView: T.FS.PathItemActionMenuView.Root, - view: T.FS.PathItemActionMenuView.Root, -} - -export const driverStatusUnknown: T.FS.DriverStatusUnknown = { - type: T.FS.DriverStatusType.Unknown, -} as const - -export const emptyDriverStatusEnabled: T.FS.DriverStatusEnabled = { - dokanOutdated: false, - dokanUninstallExecPath: undefined, - isDisabling: false, - type: T.FS.DriverStatusType.Enabled, -} as const - -export const emptyDriverStatusDisabled: T.FS.DriverStatusDisabled = { - isEnabling: false, - kextPermissionError: false, - type: T.FS.DriverStatusType.Disabled, -} as const - -export const defaultDriverStatus: T.FS.DriverStatus = isLinux ? emptyDriverStatusEnabled : driverStatusUnknown - -export const unknownKbfsDaemonStatus: T.FS.KbfsDaemonStatus = { - onlineStatus: T.FS.KbfsDaemonOnlineStatus.Unknown, - rpcStatus: T.FS.KbfsDaemonRpcStatus.Waiting, -} - -export const emptySettings: T.FS.Settings = { - isLoading: false, - loaded: false, - sfmiBannerDismissed: false, - spaceAvailableNotificationThreshold: 0, - syncOnCellular: false, -} - -export const emptyPathInfo: T.FS.PathInfo = { - deeplinkPath: '', - platformAfterMountPath: '', -} - -export const emptyFileContext: T.FS.FileContext = { - contentType: '', - url: '', - viewType: T.RPCGen.GUIViewType.default, +const rpcFolderTypeToTlfType = (rpcFolderType: T.RPCGen.FolderType) => { + switch (rpcFolderType) { + case T.RPCGen.FolderType.private: + return T.FS.TlfType.Private + case T.RPCGen.FolderType.public: + return T.FS.TlfType.Public + case T.RPCGen.FolderType.team: + return T.FS.TlfType.Team + default: + return null + } } -export const getPathItem = ( - pathItems: T.Immutable>, - path: T.Immutable -): T.Immutable => pathItems.get(path) || (unknownPathItem as T.FS.PathItem) +const rpcPathToPath = (rpcPath: T.RPCGen.KBFSPath) => T.FS.pathConcat(Constants.defaultPath, rpcPath.path) -// RPC expects a string that's interpreted as [16]byte on Go side and it has to -// be unique among all ongoing ops at any given time. uuidv1 may exceed 16 -// bytes, so just roll something simple that's seeded with time. -// -// MAX_SAFE_INTEGER after toString(36) is 11 characters, so this should take <= -// 12 chars -const uuidSeed = Date.now().toString(36) + '-' -let counter = 0 -// We have 36^4=1,679,616 of space to work with in order to not exceed 16 -// bytes. -const counterMod = 36 * 36 * 36 * 36 -export const makeUUID = () => { - counter = (counter + 1) % counterMod - return uuidSeed + counter.toString(36) +const pathFromFolderRPC = (folder: T.RPCGen.Folder): T.FS.Path => { + const visibility = T.FS.getVisibilityFromRPCFolderType(folder.folderType) + if (!visibility) return T.FS.stringToPath('') + return T.FS.stringToPath(`/keybase/${visibility}/${folder.name}`) } -export const clientID = makeUUID() -export const pathToRPCPath = ( - path: T.FS.Path -): {PathType: T.RPCGen.PathType.kbfs; kbfs: T.RPCGen.KBFSPath} => ({ - PathType: T.RPCGen.PathType.kbfs, - kbfs: { - identifyBehavior: T.RPCGen.TLFIdentifyBehavior.fsGui, - path: T.FS.pathToString(path).substring('/keybase'.length) || '/', - }, -}) - -export const rpcPathToPath = (rpcPath: T.RPCGen.KBFSPath) => T.FS.pathConcat(defaultPath, rpcPath.path) +const folderRPCFromPath = (path: T.FS.Path): T.RPCGen.FolderHandle | undefined => { + const pathElems = T.FS.getPathElements(path) + if (pathElems.length === 0) return undefined -export const pathTypeToTextType = (type: T.FS.PathType) => - type === T.FS.PathType.Folder ? 'BodySemibold' : 'Body' + const visibility = T.FS.getVisibilityFromElems(pathElems) + if (visibility === undefined) return undefined -export const splitTlfIntoUsernames = (tlf: string): ReadonlyArray => - tlf.split(' ')[0]?.replace(/#/g, ',').split(',') ?? [] + const name = T.FS.getPathNameFromElems(pathElems) + if (name === '') return undefined -export const getUsernamesFromPath = (path: T.FS.Path): ReadonlyArray => { - const elems = T.FS.getPathElements(path) - return elems.length < 3 ? [] : splitTlfIntoUsernames(elems[2]!) + return { + created: false, + folderType: T.FS.getRPCFolderTypeFromVisibility(visibility), + name, + } } -export const humanReadableFileSize = (size: number) => { - const kib = 1024 - const mib = kib * kib - const gib = mib * kib - const tib = gib * kib - - if (!size) return '' - if (size >= tib) return `${Math.round(size / tib)} TB` - if (size >= gib) return `${Math.round(size / gib)} GB` - if (size >= mib) return `${Math.round(size / mib)} MB` - if (size >= kib) return `${Math.round(size / kib)} KB` - return `${size} B` +const rpcConflictStateToConflictState = (rpcConflictState?: T.RPCGen.ConflictState): T.FS.ConflictState => { + if (rpcConflictState) { + if (rpcConflictState.conflictStateType === T.RPCGen.ConflictStateType.normalview) { + const nv = rpcConflictState.normalview + return makeConflictStateNormalView({ + localViewTlfPaths: (nv.localViews || []).reduce>((arr, p) => { + p.PathType === T.RPCGen.PathType.kbfs && arr.push(rpcPathToPath(p.kbfs)) + return arr + }, []), + resolvingConflict: nv.resolvingConflict, + stuckInConflict: nv.stuckInConflict, + }) + } else { + const nv = rpcConflictState.manualresolvinglocalview.normalView + return makeConflictStateManualResolvingLocalView({ + normalViewTlfPath: + nv.PathType === T.RPCGen.PathType.kbfs ? rpcPathToPath(nv.kbfs) : Constants.defaultPath, + }) + } + } else { + return tlfNormalViewWithNoConflict + } } -export const downloadIsOngoing = (dlState: T.FS.DownloadState) => - dlState !== emptyDownloadState && !dlState.error && !dlState.done && !dlState.canceled - -export const getDownloadIntent = ( - path: T.FS.Path, - downloads: T.FS.Downloads, - pathItemActionMenu: T.FS.PathItemActionMenu -): T.FS.DownloadIntent | undefined => { - const found = [...downloads.info].find(([_, info]) => info.path === path) - if (!found) { - return undefined - } - const [downloadID] = found - const dlState = downloads.state.get(downloadID) || emptyDownloadState - if (!downloadIsOngoing(dlState)) { - return undefined +const getSyncConfigFromRPC = ( + tlfName: string, + tlfType: T.FS.TlfType, + config?: T.RPCGen.FolderSyncConfig +): T.FS.TlfSyncConfig => { + if (!config) { + return tlfSyncDisabled } - if (pathItemActionMenu.downloadID === downloadID) { - return pathItemActionMenu.downloadIntent + switch (config.mode) { + case T.RPCGen.FolderSyncMode.disabled: + return tlfSyncDisabled + case T.RPCGen.FolderSyncMode.enabled: + return tlfSyncEnabled + case T.RPCGen.FolderSyncMode.partial: + return makeTlfSyncPartial({ + enabledPaths: config.paths + ? config.paths.map(str => T.FS.getPathFromRelative(tlfName, tlfType, str)) + : [], + }) + default: + return tlfSyncDisabled } - return T.FS.DownloadIntent.None -} - -export const emptyTlfUpdate: T.FS.TlfUpdate = { - history: [], - path: T.FS.stringToPath(''), - serverTime: 0, - writer: '', -} - -export const emptyTlfEdit: T.FS.TlfEdit = { - editType: T.FS.FileEditType.Unknown, - filename: '', - serverTime: 0, } const fsNotificationTypeToEditType = ( @@ -403,7 +179,7 @@ const fsNotificationTypeToEditType = ( } } -export const userTlfHistoryRPCToState = ( +const userTlfHistoryRPCToState = ( history: ReadonlyArray ): T.FS.UserTlfUpdates => { let updates: Array = [] @@ -429,567 +205,46 @@ export const userTlfHistoryRPCToState = ( return updates } -export const canSaveMedia = (pathItem: T.FS.PathItem, fileContext: T.FS.FileContext): boolean => { - if (pathItem.type !== T.FS.PathType.File || fileContext === emptyFileContext) { - return false - } - return ( - fileContext.viewType === T.RPCGen.GUIViewType.image || fileContext.viewType === T.RPCGen.GUIViewType.video - ) -} - -export const folderRPCFromPath = (path: T.FS.Path): T.RPCGen.FolderHandle | undefined => { - const pathElems = T.FS.getPathElements(path) - if (pathElems.length === 0) return undefined - - const visibility = T.FS.getVisibilityFromElems(pathElems) - if (visibility === undefined) return undefined - - const name = T.FS.getPathNameFromElems(pathElems) - if (name === '') return undefined - - return { - created: false, - folderType: T.FS.getRPCFolderTypeFromVisibility(visibility), - name, - } -} - -export const pathFromFolderRPC = (folder: T.RPCGen.Folder): T.FS.Path => { - const visibility = T.FS.getVisibilityFromRPCFolderType(folder.folderType) - if (!visibility) return T.FS.stringToPath('') - return T.FS.stringToPath(`/keybase/${visibility}/${folder.name}`) -} +const subscriptionDeduplicateIntervalSecond = 1 -export const showIgnoreFolder = (path: T.FS.Path, username?: string): boolean => { - const elems = T.FS.getPathElements(path) - if (elems.length !== 3) { - return false - } - return ['public', 'private'].includes(elems[1]!) && elems[2]! !== username +// RPC expects a string that's interpreted as [16]byte on Go side and it has to +// be unique among all ongoing ops at any given time. uuidv1 may exceed 16 +// bytes, so just roll something simple that's seeded with time. +// +// MAX_SAFE_INTEGER after toString(36) is 11 characters, so this should take <= +// 12 chars +const uuidSeed = Date.now().toString(36) + '-' +let counter = 0 +// We have 36^4=1,679,616 of space to work with in order to not exceed 16 +// bytes. +const counterMod = 36 * 36 * 36 * 36 +export const makeUUID = () => { + counter = (counter + 1) % counterMod + return uuidSeed + counter.toString(36) } -export const syntheticEventToTargetRect = (evt?: React.SyntheticEvent): DOMRect | undefined => - isMobile ? undefined : evt ? (evt.target as HTMLElement).getBoundingClientRect() : undefined - -export const invalidTokenError = new Error('invalid token') -export const notFoundError = new Error('not found') +export const clientID = makeUUID() export const makeEditID = (): T.FS.EditID => T.FS.stringToEditID(makeUUID()) -export const getTlfListFromType = ( - tlfs: T.Immutable, - tlfType: T.Immutable -): T.Immutable => { - switch (tlfType) { - case T.FS.TlfType.Private: - return tlfs.private - case T.FS.TlfType.Public: - return tlfs.public - case T.FS.TlfType.Team: - return tlfs.team - default: - return new Map() - } -} - -export const computeBadgeNumberForTlfList = (tlfList: T.Immutable): number => - [...tlfList.values()].reduce((accumulator, tlf) => (tlfIsBadged(tlf) ? accumulator + 1 : accumulator), 0) - -export const computeBadgeNumberForAll = (tlfs: T.Immutable): number => - [T.FS.TlfType.Private, T.FS.TlfType.Public, T.FS.TlfType.Team] - .map(tlfType => computeBadgeNumberForTlfList(getTlfListFromType(tlfs, tlfType))) - .reduce((sum, count) => sum + count, 0) - -export const getTlfPath = (path: T.FS.Path): T.FS.Path => { - const elems = T.FS.getPathElements(path) - return elems.length > 2 ? T.FS.pathConcat(T.FS.pathConcat(defaultPath, elems[1]!), elems[2]!) : undefined -} - -export const getTlfListAndTypeFromPath = ( - tlfs: T.Immutable, - path: T.Immutable -): T.Immutable<{ - tlfList: T.FS.TlfList - tlfType: T.FS.TlfType -}> => { - const visibility = T.FS.getPathVisibility(path) - switch (visibility) { - case T.FS.TlfType.Private: - case T.FS.TlfType.Public: - case T.FS.TlfType.Team: { - const tlfType: T.FS.TlfType = visibility - return {tlfList: getTlfListFromType(tlfs, tlfType), tlfType} - } - default: - return {tlfList: new Map(), tlfType: T.FS.TlfType.Private} - } -} - -export const unknownTlf = makeTlf({}) -export const getTlfFromPathInFavoritesOnly = (tlfs: T.Immutable, path: T.FS.Path): T.FS.Tlf => { - const elems = T.FS.getPathElements(path) - if (elems.length < 3) { - return unknownTlf - } - const {tlfList} = getTlfListAndTypeFromPath(tlfs, path) - return tlfList.get(elems[2]!) || unknownTlf -} - -export const getTlfFromPath = (tlfs: T.Immutable, path: T.FS.Path): T.FS.Tlf => { - const fromFavorites = getTlfFromPathInFavoritesOnly(tlfs, path) - return fromFavorites !== unknownTlf - ? fromFavorites - : tlfs.additionalTlfs.get(getTlfPath(path)) || unknownTlf -} - -export const getTlfFromTlfs = (tlfs: T.FS.Tlfs, tlfType: T.FS.TlfType, name: string): T.FS.Tlf => { - switch (tlfType) { - case T.FS.TlfType.Private: - return tlfs.private.get(name) || unknownTlf - case T.FS.TlfType.Public: - return tlfs.public.get(name) || unknownTlf - case T.FS.TlfType.Team: - return tlfs.team.get(name) || unknownTlf - default: - return unknownTlf - } -} - -export const tlfTypeAndNameToPath = (tlfType: T.FS.TlfType, name: string): T.FS.Path => - T.FS.stringToPath(`/keybase/${tlfType}/${name}`) - export const resetBannerType = (s: State, path: T.FS.Path): T.FS.ResetBannerType => { - const resetParticipants = getTlfFromPath(s.tlfs, path).resetParticipants + const resetParticipants = Constants.getTlfFromPath(s.tlfs, path).resetParticipants if (resetParticipants.length === 0) { return T.FS.ResetBannerNoOthersType.None } - const you = storeRegistry.getState('current-user').username + const you = useCurrentUserState.getState().username if (resetParticipants.findIndex(username => username === you) >= 0) { return T.FS.ResetBannerNoOthersType.Self } return resetParticipants.length } -export const getUploadedPath = (parentPath: T.FS.Path, localPath: string) => - T.FS.pathConcat(parentPath, T.FS.getLocalPathName(localPath)) - -export const usernameInPath = (username: string, path: T.FS.Path) => { - const elems = T.FS.getPathElements(path) - return elems.length >= 3 && elems[2]!.split(',').includes(username) -} - -export const getUsernamesFromTlfName = (tlfName: string): Array => { - const split = splitTlfIntoReadersAndWriters(tlfName) - return split.writers.concat(split.readers || []) -} - -export const isOfflineUnsynced = ( - daemonStatus: T.FS.KbfsDaemonStatus, - pathItem: T.FS.PathItem, - path: T.FS.Path -) => - daemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline && - T.FS.getPathLevel(path) > 2 && - pathItem.prefetchStatus !== prefetchComplete - -// To make sure we have consistent badging, all badging related stuff should go -// through this function. That is: -// * When calculating number of TLFs being badged, a TLF should be counted if -// and only if this function returns true. -// * When an individual TLF is shown (e.g. as a row), it should be badged if -// and only if this funciton returns true. -// -// If we add more badges, this function should be updated. -export const tlfIsBadged = (tlf: T.FS.Tlf) => !tlf.isIgnored && tlf.isNew - -export const pathsInSameTlf = (a: T.FS.Path, b: T.FS.Path): boolean => { - const elemsA = T.FS.getPathElements(a) - const elemsB = T.FS.getPathElements(b) - return elemsA.length >= 3 && elemsB.length >= 3 && elemsA[1] === elemsB[1] && elemsA[2] === elemsB[2] -} - -const slashKeybaseSlashLength = '/keybase/'.length -// TODO: move this to Go -export const escapePath = (path: T.FS.Path): string => - 'keybase://' + - encodeURIComponent(T.FS.pathToString(path).slice(slashKeybaseSlashLength)).replace( - // We need to do this because otherwise encodeURIComponent would encode - // "/"s. - /%2F/g, - '/' - ) - -export const parsedPathRoot: T.FS.ParsedPathRoot = {kind: T.FS.PathKind.Root} - -export const parsedPathPrivateList: T.FS.ParsedPathTlfList = { - kind: T.FS.PathKind.TlfList, - tlfType: T.FS.TlfType.Private, -} - -export const parsedPathPublicList: T.FS.ParsedPathTlfList = { - kind: T.FS.PathKind.TlfList, - tlfType: T.FS.TlfType.Public, -} - -export const parsedPathTeamList: T.FS.ParsedPathTlfList = { - kind: T.FS.PathKind.TlfList, - tlfType: T.FS.TlfType.Team, -} - -const splitTlfIntoReadersAndWriters = ( - tlf: string -): { - readers?: Array - writers: Array -} => { - const [w, r] = tlf.split('#') - return { - readers: r ? r.split(',').filter(i => !!i) : undefined, - writers: w?.split(',').filter(i => !!i) ?? [], - } -} - -// returns parsedPathRoot if unknown -export const parsePath = (path: T.FS.Path): T.FS.ParsedPath => { - const elems = T.FS.getPathElements(path) - if (elems.length <= 1) { - return parsedPathRoot - } - switch (elems[1]) { - case 'private': - switch (elems.length) { - case 2: - return parsedPathPrivateList - case 3: - return { - kind: T.FS.PathKind.GroupTlf, - tlfName: elems[2]!, - tlfType: T.FS.TlfType.Private, - ...splitTlfIntoReadersAndWriters(elems[2]!), - } - default: - return { - kind: T.FS.PathKind.InGroupTlf, - rest: elems.slice(3), - tlfName: elems[2] ?? '', - tlfType: T.FS.TlfType.Private, - ...splitTlfIntoReadersAndWriters(elems[2] ?? ''), - } - } - case 'public': - switch (elems.length) { - case 2: - return parsedPathPublicList - case 3: - return { - kind: T.FS.PathKind.GroupTlf, - tlfName: elems[2]!, - tlfType: T.FS.TlfType.Public, - ...splitTlfIntoReadersAndWriters(elems[2]!), - } - default: - return { - kind: T.FS.PathKind.InGroupTlf, - rest: elems.slice(3), - tlfName: elems[2] ?? '', - tlfType: T.FS.TlfType.Public, - ...splitTlfIntoReadersAndWriters(elems[2] ?? ''), - } - } - case 'team': - switch (elems.length) { - case 2: - return parsedPathTeamList - case 3: - return { - kind: T.FS.PathKind.TeamTlf, - team: elems[2]!, - tlfName: elems[2]!, - tlfType: T.FS.TlfType.Team, - } - default: - return { - kind: T.FS.PathKind.InTeamTlf, - rest: elems.slice(3), - team: elems[2] ?? '', - tlfName: elems[2] ?? '', - tlfType: T.FS.TlfType.Team, - } - } - default: - return parsedPathRoot - } -} - -export const rebasePathToDifferentTlf = (path: T.FS.Path, newTlfPath: T.FS.Path) => - T.FS.pathConcat(newTlfPath, T.FS.getPathElements(path).slice(3).join('/')) - -export const canChat = (path: T.FS.Path) => { - const parsedPath = parsePath(path) - switch (parsedPath.kind) { - case T.FS.PathKind.Root: - case T.FS.PathKind.TlfList: - return false - case T.FS.PathKind.GroupTlf: - case T.FS.PathKind.TeamTlf: - return true - case T.FS.PathKind.InGroupTlf: - case T.FS.PathKind.InTeamTlf: - return true - default: - return false - } -} - -export const isTeamPath = (path: T.FS.Path): boolean => { - const parsedPath = parsePath(path) - return parsedPath.kind !== T.FS.PathKind.Root && parsedPath.tlfType === T.FS.TlfType.Team -} - -export const getChatTarget = (path: T.FS.Path, me: string): string => { - const parsedPath = parsePath(path) - if (parsedPath.kind !== T.FS.PathKind.Root && parsedPath.tlfType === T.FS.TlfType.Team) { - return 'team conversation' - } - if (parsedPath.kind === T.FS.PathKind.GroupTlf || parsedPath.kind === T.FS.PathKind.InGroupTlf) { - if (parsedPath.writers.length === 1 && !parsedPath.readers && parsedPath.writers[0] === me) { - return 'yourself' - } - if (parsedPath.writers.length + (parsedPath.readers ? parsedPath.readers.length : 0) === 2) { - const notMe = parsedPath.writers.concat(parsedPath.readers || []).filter(u => u !== me) - if (notMe.length === 1) { - return notMe[0]! - } - } - return 'group conversation' - } - return 'conversation' -} - -export const getSharePathArrayDescription = (paths: ReadonlyArray): string => { - return !paths.length ? '' : paths.length === 1 ? T.FS.getPathName(paths[0]) : `${paths.length} items` -} - -export const getDestinationPickerPathName = (picker: T.FS.DestinationPicker): string => - picker.source.type === T.FS.DestinationPickerSource.MoveOrCopy - ? T.FS.getPathName(picker.source.path) - : picker.source.type === T.FS.DestinationPickerSource.IncomingShare - ? getSharePathArrayDescription( - picker.source.source - .map(({originalPath}) => (originalPath ? T.FS.getLocalPathName(originalPath) : '')) - .filter(Boolean) - ) - : '' - -const isPathEnabledForSync = (syncConfig: T.FS.TlfSyncConfig, path: T.FS.Path): boolean => { - switch (syncConfig.mode) { - case T.FS.TlfSyncMode.Disabled: - return false - case T.FS.TlfSyncMode.Enabled: - return true - case T.FS.TlfSyncMode.Partial: - // TODO: when we enable partial sync lookup, remember to deal with - // potential ".." traversal as well. - return syncConfig.enabledPaths.includes(path) - default: - return false - } -} - -export const getUploadIconForTlfType = ( - kbfsDaemonStatus: T.FS.KbfsDaemonStatus, - uploads: T.FS.Uploads, - tlfList: T.FS.TlfList, - tlfType: T.FS.TlfType -): T.FS.UploadIcon | undefined => { - if ( - [...tlfList].some( - ([_, tlf]) => - tlf.conflictState.type === T.FS.ConflictStateType.NormalView && tlf.conflictState.stuckInConflict - ) - ) { - return T.FS.UploadIcon.UploadingStuck - } - - const prefix = T.FS.pathToString(T.FS.getTlfTypePathFromTlfType(tlfType)) - if ( - [...uploads.syncingPaths].some(p => T.FS.pathToString(p).startsWith(prefix)) || - [...uploads.writingToJournal.keys()].some(p => T.FS.pathToString(p).startsWith(prefix)) - ) { - return kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline - ? T.FS.UploadIcon.AwaitingToUpload - : T.FS.UploadIcon.Uploading - } - - return undefined -} - -export const tlfIsStuckInConflict = (tlf: T.FS.Tlf) => - tlf.conflictState.type === T.FS.ConflictStateType.NormalView && tlf.conflictState.stuckInConflict - -export const getPathStatusIconInMergeProps = ( - kbfsDaemonStatus: T.FS.KbfsDaemonStatus, - tlf: T.Immutable, - pathItem: T.Immutable, - uploadingPaths: T.Immutable>, - path: T.Immutable -): T.FS.PathStatusIcon => { - // There's no upload or sync for local conflict view. - if (tlf.conflictState.type === T.FS.ConflictStateType.ManualResolvingLocalView) { - return T.FS.LocalConflictStatus - } - - // uploading state has higher priority - if (uploadingPaths.has(path)) { - // eslint-disable-next-line - return tlf.conflictState.type === T.FS.ConflictStateType.NormalView && tlf.conflictState.stuckInConflict - ? T.FS.UploadIcon.UploadingStuck - : kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline - ? T.FS.UploadIcon.AwaitingToUpload - : T.FS.UploadIcon.Uploading - } - if (!isPathEnabledForSync(tlf.syncConfig, path)) { - return T.FS.NonUploadStaticSyncStatus.OnlineOnly - } - - if (pathItem === unknownPathItem && tlf.syncConfig.mode !== T.FS.TlfSyncMode.Disabled) { - return T.FS.NonUploadStaticSyncStatus.Unknown - } - - // TODO: what about 'sync-error'? - - // We don't have an upload state, and sync is enabled for this path. - switch (pathItem.prefetchStatus.state) { - case T.FS.PrefetchState.NotStarted: - return T.FS.NonUploadStaticSyncStatus.AwaitingToSync - case T.FS.PrefetchState.Complete: - return T.FS.NonUploadStaticSyncStatus.Synced - case T.FS.PrefetchState.InProgress: { - if (kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline) { - return T.FS.NonUploadStaticSyncStatus.AwaitingToSync - } - const inProgress: T.FS.PrefetchInProgress = pathItem.prefetchStatus - if (inProgress.bytesTotal === 0) { - return T.FS.NonUploadStaticSyncStatus.AwaitingToSync - } - return inProgress.bytesFetched / inProgress.bytesTotal - } - default: - return T.FS.NonUploadStaticSyncStatus.Unknown - } -} - export const makeActionsForDestinationPickerOpen = (index: number, path: T.FS.Path) => { useFSState.getState().dispatch.setDestinationPickerParentPath(index, path) navigateAppend({props: {index}, selected: 'destinationPicker'}) } -export const fsRootRouteForNav1 = isMobile ? [Tabs.settingsTab, settingsFsTab] : [Tabs.fsTab] - -export const getMainBannerType = ( - kbfsDaemonStatus: T.FS.KbfsDaemonStatus, - overallSyncStatus: T.FS.OverallSyncStatus -): T.FS.MainBannerType => { - if (kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Offline) { - return T.FS.MainBannerType.Offline - } else if (kbfsDaemonStatus.onlineStatus === T.FS.KbfsDaemonOnlineStatus.Trying) { - return T.FS.MainBannerType.TryingToConnect - } else if (overallSyncStatus.diskSpaceStatus === T.FS.DiskSpaceStatus.Error) { - return T.FS.MainBannerType.OutOfSpace - } else { - return T.FS.MainBannerType.None - } -} - -export const isFolder = (path: T.FS.Path, pathItem: T.FS.PathItem) => - T.FS.getPathLevel(path) <= 3 || pathItem.type === T.FS.PathType.Folder - -export const isInTlf = (path: T.FS.Path) => T.FS.getPathLevel(path) > 2 - -export const humanizeBytes = (n: number, numDecimals: number): string => { - const kb = 1024 - const mb = kb * 1024 - const gb = mb * 1024 - - if (n < kb) { - return `${n} bytes` - } else if (n < mb) { - return `${(n / kb).toFixed(numDecimals)} KB` - } else if (n < gb) { - return `${(n / mb).toFixed(numDecimals)} MB` - } - return `${(n / gb).toFixed(numDecimals)} GB` -} - -export const humanizeBytesOfTotal = (n: number, d: number): string => { - const kb = 1024 - const mb = kb * 1024 - const gb = mb * 1024 - - if (d < kb) { - return `${n} of ${d} bytes` - } else if (d < mb) { - return `${(n / kb).toFixed(2)} of ${(d / kb).toFixed(2)} KB` - } else if (d < gb) { - return `${(n / mb).toFixed(2)} of ${(d / mb).toFixed(2)} MB` - } - return `${(n / gb).toFixed(2)} of ${(d / gb).toFixed(2)} GB` -} - -export const hasPublicTag = (path: T.FS.Path): boolean => { - const publicPrefix = '/keybase/public/' - // The slash after public in `publicPrefix` prevents /keybase/public from counting. - return T.FS.pathToString(path).startsWith(publicPrefix) -} - -export const getPathUserSetting = ( - pathUserSettings: T.Immutable>, - path: T.Immutable -): T.FS.PathUserSetting => - pathUserSettings.get(path) || - (T.FS.getPathLevel(path) < 3 ? defaultTlfListPathUserSetting : defaultPathUserSetting) - -export const showSortSetting = ( - path: T.FS.Path, - pathItem: T.FS.PathItem, - kbfsDaemonStatus: T.FS.KbfsDaemonStatus -) => - !isMobile && - path !== defaultPath && - (T.FS.getPathLevel(path) === 2 || (pathItem.type === T.FS.PathType.Folder && !!pathItem.children.size)) && - !isOfflineUnsynced(kbfsDaemonStatus, pathItem, path) - -export const getSoftError = (softErrors: T.FS.SoftErrors, path: T.FS.Path): T.FS.SoftError | undefined => { - const pathError = softErrors.pathErrors.get(path) - if (pathError) { - return pathError - } - if (!softErrors.tlfErrors.size) { - return undefined - } - const tlfPath = getTlfPath(path) - return (tlfPath && softErrors.tlfErrors.get(tlfPath)) || undefined -} - -export const hasSpecialFileElement = (path: T.FS.Path): boolean => - T.FS.getPathElements(path).some(elem => elem.startsWith('.kbfs')) - -export const sfmiInfoLoaded = (settings: T.FS.Settings, driverStatus: T.FS.DriverStatus): boolean => - settings.loaded && driverStatus !== driverStatusUnknown - -// This isn't perfect since it doesn't cover the case of multi-writer public -// TLFs or where a team TLF is readonly to the user. But to do that we'd need -// some new caching in KBFS to plumb it into the Tlfs structure without -// awful overhead. -export const hideOrDisableInDestinationPicker = ( - tlfType: T.FS.TlfType, - name: string, - username: string, - destinationPickerIndex?: number -) => typeof destinationPickerIndex === 'number' && tlfType === T.FS.TlfType.Public && name !== username - const noAccessErrorCodes: Array = [ T.RPCGen.StatusCode.scsimplefsnoaccess, T.RPCGen.StatusCode.scteamnotfound, @@ -1021,7 +276,7 @@ export const errorToActionOrThrow = (error: unknown, path?: T.FS.Path) => { return } if (path && code && noAccessErrorCodes.includes(code)) { - const tlfPath = getTlfPath(path) + const tlfPath = Constants.getTlfPath(path) if (tlfPath) { useFSState.getState().dispatch.setTlfSoftError(tlfPath, T.FS.SoftError.NoAccess) return @@ -1076,17 +331,17 @@ const initialStore: Store = { errors: [], fileContext: new Map(), folderViewFilter: undefined, - kbfsDaemonStatus: unknownKbfsDaemonStatus, + kbfsDaemonStatus: Constants.unknownKbfsDaemonStatus, lastPublicBannerClosedTlf: '', - overallSyncStatus: emptyOverallSyncStatus, + overallSyncStatus: Constants.emptyOverallSyncStatus, pathInfos: new Map(), - pathItemActionMenu: emptyPathItemActionMenu, + pathItemActionMenu: Constants.emptyPathItemActionMenu, pathItems: new Map(), pathUserSettings: new Map(), - settings: emptySettings, + settings: Constants.emptySettings, sfmi: { directMountDir: '', - driverStatus: defaultDriverStatus, + driverStatus: Constants.defaultDriverStatus, preferredMountDirs: [], }, softErrors: { @@ -1124,7 +379,7 @@ export interface State extends Store { driverDisabling: () => void driverEnable: (isRetry?: boolean) => void driverKextPermissionError: () => void - dynamic: { + defer: { afterDriverDisable?: () => void afterDriverDisabling?: () => void afterDriverEnabled?: (isRetry: boolean) => void @@ -1145,6 +400,8 @@ export interface State extends Store { refreshMountDirsDesktop?: () => void setSfmiBannerDismissedDesktop?: (dismissed: boolean) => void uploadFromDragAndDropDesktop?: (parentPath: T.FS.Path, localPaths: Array) => void + onBadgeApp?: (key: 'kbfsUploading' | 'outOfSpace', on: boolean) => void + onSetBadgeCounts?: (counts: Map) => void } editError: (editID: T.FS.EditID, error: string) => void editSuccess: (editID: T.FS.EditID) => void @@ -1200,7 +457,6 @@ export interface State extends Store { setTlfsAsUnloaded: () => void setTlfSyncConfig: (tlfPath: T.FS.Path, enabled: boolean) => void setSorting: (path: T.FS.Path, sortSetting: T.FS.SortSetting) => void - setupSubscriptions: () => Promise showIncomingShare: (initialDestinationParentPath: T.FS.Path) => void showMoveOrCopy: (initialDestinationParentPath: T.FS.Path) => void startManualConflictResolution: (tlfPath: T.FS.Path) => void @@ -1218,13 +474,21 @@ export interface State extends Store { getUploadIconForFilesTab: () => T.FS.UploadIcon | undefined } +const emptyPrefetchInProgress: T.FS.PrefetchInProgress = { + bytesFetched: 0, + bytesTotal: 0, + endEstimate: 0, + startTime: 0, + state: T.FS.PrefetchState.InProgress, +} + const getPrefetchStatusFromRPC = ( prefetchStatus: T.RPCGen.PrefetchStatus, prefetchProgress: T.RPCGen.PrefetchProgress ) => { switch (prefetchStatus) { case T.RPCGen.PrefetchStatus.notStarted: - return prefetchNotStarted + return Constants.prefetchNotStarted case T.RPCGen.PrefetchStatus.inProgress: return { ...emptyPrefetchInProgress, @@ -1234,9 +498,9 @@ const getPrefetchStatusFromRPC = ( startTime: prefetchProgress.start, } case T.RPCGen.PrefetchStatus.complete: - return prefetchComplete + return Constants.prefetchComplete default: - return prefetchNotStarted + return Constants.prefetchNotStarted } } @@ -1253,21 +517,21 @@ const makeEntry = (d: T.RPCGen.Dirent, children?: Set): T.FS.PathItem => switch (d.direntType) { case T.RPCGen.DirentType.dir: return { - ...emptyFolder, + ...Constants.emptyFolder, ...direntToMetadata(d), children: new Set(children || []), progress: children ? T.FS.ProgressType.Loaded : T.FS.ProgressType.Pending, } as T.FS.PathItem case T.RPCGen.DirentType.sym: return { - ...emptySymlink, + ...Constants.emptySymlink, ...direntToMetadata(d), // TODO: plumb link target } as T.FS.PathItem case T.RPCGen.DirentType.file: case T.RPCGen.DirentType.exec: return { - ...emptyFile, + ...Constants.emptyFile, ...direntToMetadata(d), } as T.FS.PathItem } @@ -1379,7 +643,7 @@ export const useFSState = Z.createZustand((set, get) => { try { await T.RPCGen.SimpleFSSimpleFSOpenRpcPromise( { - dest: pathToRPCPath(T.FS.pathConcat(edit.parentPath, edit.name)), + dest: Constants.pathToRPCPath(T.FS.pathConcat(edit.parentPath, edit.name)), flags: T.RPCGen.OpenFlags.directory, opID: makeUUID(), }, @@ -1395,10 +659,10 @@ export const useFSState = Z.createZustand((set, get) => { try { const opID = makeUUID() await T.RPCGen.SimpleFSSimpleFSMoveRpcPromise({ - dest: pathToRPCPath(T.FS.pathConcat(edit.parentPath, edit.name)), + dest: Constants.pathToRPCPath(T.FS.pathConcat(edit.parentPath, edit.name)), opID, overwriteExistingFiles: false, - src: pathToRPCPath(T.FS.pathConcat(edit.parentPath, edit.originalName)), + src: Constants.pathToRPCPath(T.FS.pathConcat(edit.parentPath, edit.originalName)), }) await T.RPCGen.SimpleFSSimpleFSWaitRpcPromise({opID}, S.waitingKeyFSCommitEdit) get().dispatch.editSuccess(editID) @@ -1422,13 +686,37 @@ export const useFSState = Z.createZustand((set, get) => { } ignorePromise(f()) }, + defer: { + afterDriverDisable: undefined, + afterDriverDisabling: undefined, + afterDriverEnabled: undefined, + afterKbfsDaemonRpcStatusChanged: undefined, + finishedDownloadWithIntentMobile: undefined, + finishedRegularDownloadMobile: undefined, + onBadgeApp: () => { + throw new Error('onBadgeApp not implemented') + }, + onSetBadgeCounts: () => { + throw new Error('onSetBadgeCounts not implemented') + }, + openAndUploadDesktop: undefined, + openFilesFromWidgetDesktop: undefined, + openLocalPathInSystemFileManagerDesktop: undefined, + openPathInSystemFileManagerDesktop: undefined, + openSecurityPreferencesDesktop: undefined, + pickAndUploadMobile: undefined, + refreshDriverStatusDesktop: undefined, + refreshMountDirsDesktop: undefined, + setSfmiBannerDismissedDesktop: undefined, + uploadFromDragAndDropDesktop: undefined, + }, deleteFile: path => { const f = async () => { const opID = makeUUID() try { await T.RPCGen.SimpleFSSimpleFSRemoveRpcPromise({ opID, - path: pathToRPCPath(path), + path: Constants.pathToRPCPath(path), recursive: true, }) await T.RPCGen.SimpleFSSimpleFSWaitRpcPromise({opID}) @@ -1467,7 +755,7 @@ export const useFSState = Z.createZustand((set, get) => { await requestPermissionsToWrite() const downloadID = await T.RPCGen.SimpleFSSimpleFSStartDownloadRpcPromise({ isRegularDownload: type === 'download', - path: pathToRPCPath(path).kbfs, + path: Constants.pathToRPCPath(path).kbfs, }) if (type !== 'download') { get().dispatch.setPathItemActionMenuDownload( @@ -1479,7 +767,7 @@ export const useFSState = Z.createZustand((set, get) => { ignorePromise(f()) }, driverDisable: () => { - get().dispatch.dynamic.afterDriverDisable?.() + get().dispatch.defer.afterDriverDisable?.() }, driverDisabling: () => { set(s => { @@ -1487,7 +775,7 @@ export const useFSState = Z.createZustand((set, get) => { s.sfmi.driverStatus.isDisabling = true } }) - get().dispatch.dynamic.afterDriverDisabling?.() + get().dispatch.defer.afterDriverDisabling?.() }, driverEnable: isRetry => { set(s => { @@ -1495,7 +783,7 @@ export const useFSState = Z.createZustand((set, get) => { s.sfmi.driverStatus.isEnabling = true } }) - get().dispatch.dynamic.afterDriverEnabled?.(!!isRetry) + get().dispatch.defer.afterDriverEnabled?.(!!isRetry) }, driverKextPermissionError: () => { set(s => { @@ -1505,24 +793,6 @@ export const useFSState = Z.createZustand((set, get) => { } }) }, - dynamic: { - afterDriverDisable: undefined, - afterDriverDisabling: undefined, - afterDriverEnabled: undefined, - afterKbfsDaemonRpcStatusChanged: undefined, - finishedDownloadWithIntentMobile: undefined, - finishedRegularDownloadMobile: undefined, - openAndUploadDesktop: undefined, - openFilesFromWidgetDesktop: undefined, - openLocalPathInSystemFileManagerDesktop: undefined, - openPathInSystemFileManagerDesktop: undefined, - openSecurityPreferencesDesktop: undefined, - pickAndUploadMobile: undefined, - refreshDriverStatusDesktop: undefined, - refreshMountDirsDesktop: undefined, - setSfmiBannerDismissedDesktop: undefined, - uploadFromDragAndDropDesktop: undefined, - }, editError: (editID, error) => { set(s => { const e = s.edits.get(editID) @@ -1554,7 +824,7 @@ export const useFSState = Z.createZustand((set, get) => { s.tlfs[visibility].set( elems[2] ?? '', T.castDraft({ - ...(s.tlfs[visibility].get(elems[2] ?? '') || unknownTlf), + ...(s.tlfs[visibility].get(elems[2] ?? '') || Constants.unknownTlf), isIgnored: false, }) ) @@ -1571,7 +841,7 @@ export const useFSState = Z.createZustand((set, get) => { s.tlfs[visibility].set( elems[2] ?? '', T.castDraft({ - ...(s.tlfs[visibility].get(elems[2] ?? '') || unknownTlf), + ...(s.tlfs[visibility].get(elems[2] ?? '') || Constants.unknownTlf), isIgnored: true, }) ) @@ -1581,7 +851,7 @@ export const useFSState = Z.createZustand((set, get) => { favoritesLoad: () => { const f = async () => { try { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } const results = await T.RPCGen.SimpleFSSimpleFSListFavoritesRpcPromise() @@ -1606,7 +876,7 @@ export const useFSState = Z.createZustand((set, get) => { const tlfType = rpcFolderTypeToTlfType(folder.folderType) const tlfName = tlfType === T.FS.TlfType.Private || tlfType === T.FS.TlfType.Public - ? tlfToPreferredOrder(folder.name, storeRegistry.getState('current-user').username) + ? tlfToPreferredOrder(folder.name, useCurrentUserState.getState().username) : folder.name tlfType && payload[tlfType].set( @@ -1634,8 +904,8 @@ export const useFSState = Z.createZustand((set, get) => { s.tlfs.loaded = true }) const counts = new Map() - counts.set(Tabs.fsTab, computeBadgeNumberForAll(get().tlfs)) - storeRegistry.getState('notifications').dispatch.setBadgeCounts(counts) + counts.set(Tabs.fsTab, Constants.computeBadgeNumberForAll(get().tlfs)) + get().dispatch.defer.onSetBadgeCounts?.(counts) } } catch (e) { errorToActionOrThrow(e) @@ -1647,7 +917,7 @@ export const useFSState = Z.createZustand((set, get) => { finishManualConflictResolution: localViewTlfPath => { const f = async () => { await T.RPCGen.SimpleFSSimpleFSFinishResolvingConflictRpcPromise({ - path: pathToRPCPath(localViewTlfPath), + path: Constants.pathToRPCPath(localViewTlfPath), }) get().dispatch.favoritesLoad() } @@ -1662,14 +932,14 @@ export const useFSState = Z.createZustand((set, get) => { depth: 1, filter: T.RPCGen.ListFilter.filterSystemHidden, opID, - path: pathToRPCPath(rootPath), + path: Constants.pathToRPCPath(rootPath), refreshSubscription: false, }) } else { await T.RPCGen.SimpleFSSimpleFSListRpcPromise({ filter: T.RPCGen.ListFilter.filterSystemHidden, opID, - path: pathToRPCPath(rootPath), + path: Constants.pathToRPCPath(rootPath), refreshSubscription: false, }) } @@ -1719,11 +989,11 @@ export const useFSState = Z.createZustand((set, get) => { // Get metadata fields of the directory that we just loaded from state to // avoid overriding them. - const rootPathItem = getPathItem(get().pathItems, rootPath) + const rootPathItem = Constants.getPathItem(get().pathItems, rootPath) const rootFolder: T.FS.FolderPathItem = { ...(rootPathItem.type === T.FS.PathType.Folder ? rootPathItem - : {...emptyFolder, name: T.FS.getPathName(rootPath)}), + : {...Constants.emptyFolder, name: T.FS.getPathName(rootPath)}), children: new Set(childMap.get(rootPath)), progress: T.FS.ProgressType.Loaded, } @@ -1734,7 +1004,7 @@ export const useFSState = Z.createZustand((set, get) => { ] as const) set(s => { pathItems.forEach((pathItemFromAction, path) => { - const oldPathItem = getPathItem(s.pathItems, path) + const oldPathItem = Constants.getPathItem(s.pathItems, path) const newPathItem = updatePathItem(oldPathItem, pathItemFromAction) oldPathItem.type === T.FS.PathType.Folder && oldPathItem.children.forEach( @@ -1751,7 +1021,7 @@ export const useFSState = Z.createZustand((set, get) => { if (edit.type !== T.FS.EditType.Rename) { return true } - const parent = getPathItem(s.pathItems, edit.parentPath) + const parent = Constants.getPathItem(s.pathItems, edit.parentPath) if (parent.type === T.FS.PathType.Folder && parent.children.has(edit.name)) { return true } @@ -1878,7 +1148,7 @@ export const useFSState = Z.createZustand((set, get) => { } subscribeAndLoadJournalStatus() // how this works isn't great. This function gets called way early before we set this - get().dispatch.dynamic.afterKbfsDaemonRpcStatusChanged?.() + get().dispatch.defer.afterKbfsDaemonRpcStatusChanged?.() }, letResetUserBackIn: (id, username) => { const f = async () => { @@ -1898,12 +1168,12 @@ export const useFSState = Z.createZustand((set, get) => { } try { const {folder, isFavorite, isIgnored, isNew} = await T.RPCGen.SimpleFSSimpleFSGetFolderRpcPromise({ - path: pathToRPCPath(tlfPath).kbfs, + path: Constants.pathToRPCPath(tlfPath).kbfs, }) const tlfType = rpcFolderTypeToTlfType(folder.folderType) const tlfName = tlfType === T.FS.TlfType.Private || tlfType === T.FS.TlfType.Public - ? tlfToPreferredOrder(folder.name, storeRegistry.getState('current-user').username) + ? tlfToPreferredOrder(folder.name, useCurrentUserState.getState().username) : folder.name if (tlfType) { @@ -2005,7 +1275,7 @@ export const useFSState = Z.createZustand((set, get) => { const f = async () => { try { const res = await T.RPCGen.SimpleFSSimpleFSGetGUIFileContextRpcPromise({ - path: pathToRPCPath(path).kbfs, + path: Constants.pathToRPCPath(path).kbfs, }) set(s => { @@ -2058,7 +1328,7 @@ export const useFSState = Z.createZustand((set, get) => { try { const dirent = await T.RPCGen.SimpleFSSimpleFSStatRpcPromise( { - path: pathToRPCPath(path), + path: Constants.pathToRPCPath(path), refreshSubscription: false, }, S.waitingKeyFSStat @@ -2066,7 +1336,7 @@ export const useFSState = Z.createZustand((set, get) => { const pathItem = makeEntry(dirent) set(s => { - const oldPathItem = getPathItem(s.pathItems, path) + const oldPathItem = Constants.getPathItem(s.pathItems, path) s.pathItems.set(path, T.castDraft(updatePathItem(oldPathItem, pathItem))) s.softErrors.pathErrors.delete(path) s.softErrors.tlfErrors.delete(path) @@ -2103,13 +1373,13 @@ export const useFSState = Z.createZustand((set, get) => { }, loadTlfSyncConfig: tlfPath => { const f = async () => { - const parsedPath = parsePath(tlfPath) + const parsedPath = Constants.parsePath(tlfPath) if (parsedPath.kind !== T.FS.PathKind.GroupTlf && parsedPath.kind !== T.FS.PathKind.TeamTlf) { return } try { const result = await T.RPCGen.SimpleFSSimpleFSFolderSyncConfigAndStatusRpcPromise({ - path: pathToRPCPath(tlfPath), + path: Constants.pathToRPCPath(tlfPath), }) const syncConfig = getSyncConfigFromRPC(parsedPath.tlfName, parsedPath.tlfType, result.config) const tlfName = parsedPath.tlfName @@ -2117,17 +1387,17 @@ export const useFSState = Z.createZustand((set, get) => { set(s => { const oldTlfList = s.tlfs[tlfType] - const oldTlfFromFavorites = oldTlfList.get(tlfName) || unknownTlf - if (oldTlfFromFavorites !== unknownTlf) { + const oldTlfFromFavorites = oldTlfList.get(tlfName) || Constants.unknownTlf + if (oldTlfFromFavorites !== Constants.unknownTlf) { s.tlfs[tlfType] = T.castDraft( new Map([...oldTlfList, [tlfName, {...oldTlfFromFavorites, syncConfig}]]) ) return } - const tlfPath = T.FS.pathConcat(T.FS.pathConcat(defaultPath, tlfType), tlfName) - const oldTlfFromAdditional = s.tlfs.additionalTlfs.get(tlfPath) || unknownTlf - if (oldTlfFromAdditional !== unknownTlf) { + const tlfPath = T.FS.pathConcat(T.FS.pathConcat(Constants.defaultPath, tlfType), tlfName) + const oldTlfFromAdditional = s.tlfs.additionalTlfs.get(tlfPath) || Constants.unknownTlf + if (oldTlfFromAdditional !== Constants.unknownTlf) { s.tlfs.additionalTlfs = T.castDraft( new Map([...s.tlfs.additionalTlfs, [tlfPath, {...oldTlfFromAdditional, syncConfig}]]) ) @@ -2189,7 +1459,7 @@ export const useFSState = Z.createZustand((set, get) => { zState.destinationPicker.source.type === T.FS.DestinationPickerSource.MoveOrCopy ? [ { - dest: pathToRPCPath( + dest: Constants.pathToRPCPath( T.FS.pathConcat( destinationParentPath, T.FS.getPathName(zState.destinationPicker.source.path) @@ -2197,14 +1467,14 @@ export const useFSState = Z.createZustand((set, get) => { ), opID: makeUUID(), overwriteExistingFiles: false, - src: pathToRPCPath(zState.destinationPicker.source.path), + src: Constants.pathToRPCPath(zState.destinationPicker.source.path), }, ] : zState.destinationPicker.source.source .map(item => ({originalPath: item.originalPath ?? '', scaledPath: item.scaledPath})) .filter(({originalPath}) => !!originalPath) .map(({originalPath, scaledPath}) => ({ - dest: pathToRPCPath( + dest: Constants.pathToRPCPath( T.FS.pathConcat( destinationParentPath, T.FS.getLocalPathName(originalPath) @@ -2216,7 +1486,7 @@ export const useFSState = Z.createZustand((set, get) => { src: { PathType: T.RPCGen.PathType.local, local: T.FS.getNormalizedLocalPath( - storeRegistry.getState('config').incomingShareUseOriginal + useConfigState.getState().incomingShareUseOriginal ? originalPath : scaledPath || originalPath ), @@ -2241,7 +1511,7 @@ export const useFSState = Z.createZustand((set, get) => { ignorePromise(f()) }, newFolderRow: parentPath => { - const parentPathItem = getPathItem(get().pathItems, parentPath) + const parentPathItem = Constants.getPathItem(get().pathItems, parentPath) if (parentPathItem.type !== T.FS.PathType.Folder) { console.warn(`bad parentPath: ${parentPathItem.type}`) return @@ -2258,7 +1528,7 @@ export const useFSState = Z.createZustand((set, get) => { set(s => { s.edits.set(makeEditID(), { - ...emptyNewFolder, + ...Constants.emptyNewFolder, name: newFolderName, originalName: newFolderName, parentPath, @@ -2375,12 +1645,12 @@ export const useFSState = Z.createZustand((set, get) => { if (totalSyncingBytes <= 0 && !syncingPaths?.length) { break } - storeRegistry.getState('notifications').dispatch.badgeApp('kbfsUploading', true) + get().dispatch.defer.onBadgeApp?.('kbfsUploading', true) await timeoutPromise(getWaitDuration(endEstimate || undefined, 100, 4000)) // 0.1s to 4s } } finally { pollJournalStatusPolling = false - storeRegistry.getState('notifications').dispatch.badgeApp('kbfsUploading', false) + get().dispatch.defer.onBadgeApp?.('kbfsUploading', false) get().dispatch.checkKbfsDaemonRpcStatus() } } @@ -2423,7 +1693,7 @@ export const useFSState = Z.createZustand((set, get) => { set(s => { s.sfmi.driverStatus = driverStatus }) - get().dispatch.dynamic.refreshMountDirsDesktop?.() + get().dispatch.defer.refreshMountDirsDesktop?.() }, setEditName: (editID, name) => { set(s => { @@ -2488,7 +1758,7 @@ export const useFSState = Z.createZustand((set, get) => { if (old) { old.sort = sortSetting } else { - s.pathUserSettings.set(path, {...defaultPathUserSetting, sort: sortSetting}) + s.pathUserSettings.set(path, {...Constants.defaultPathUserSetting, sort: sortSetting}) } }) }, @@ -2515,7 +1785,7 @@ export const useFSState = Z.createZustand((set, get) => { await T.RPCGen.SimpleFSSimpleFSSetFolderSyncConfigRpcPromise( { config: {mode: enabled ? T.RPCGen.FolderSyncMode.enabled : T.RPCGen.FolderSyncMode.disabled}, - path: pathToRPCPath(tlfPath), + path: Constants.pathToRPCPath(tlfPath), }, S.waitingKeyFSSyncToggle ) @@ -2528,10 +1798,6 @@ export const useFSState = Z.createZustand((set, get) => { s.tlfs.loaded = false }) }, - setupSubscriptions: async () => { - const initPlatformSpecific = await import('./platform-specific') - initPlatformSpecific.default() - }, showIncomingShare: initialDestinationParentPath => { set(s => { if (s.destinationPicker.source.type !== T.FS.DestinationPickerSource.IncomingShare) { @@ -2539,9 +1805,7 @@ export const useFSState = Z.createZustand((set, get) => { } s.destinationPicker.destinationParentPath = [initialDestinationParentPath] }) - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {index: 0}, selected: 'destinationPicker'}) + navigateAppend({props: {index: 0}, selected: 'destinationPicker'}) }, showMoveOrCopy: initialDestinationParentPath => { set(s => { @@ -2549,21 +1813,19 @@ export const useFSState = Z.createZustand((set, get) => { s.destinationPicker.source.type === T.FS.DestinationPickerSource.MoveOrCopy ? s.destinationPicker.source : { - path: defaultPath, + path: Constants.defaultPath, type: T.FS.DestinationPickerSource.MoveOrCopy, } s.destinationPicker.destinationParentPath = [initialDestinationParentPath] }) - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {index: 0}, selected: 'destinationPicker'}) + navigateAppend({props: {index: 0}, selected: 'destinationPicker'}) }, startManualConflictResolution: tlfPath => { const f = async () => { await T.RPCGen.SimpleFSSimpleFSClearConflictStateRpcPromise({ - path: pathToRPCPath(tlfPath), + path: Constants.pathToRPCPath(tlfPath), }) get().dispatch.favoritesLoad() } @@ -2641,12 +1903,12 @@ export const useFSState = Z.createZustand((set, get) => { body: 'You are out of disk space. Some folders could not be synced.', sound: true, }) - storeRegistry.getState('notifications').dispatch.badgeApp('outOfSpace', status.outOfSyncSpace) + get().dispatch.defer.onBadgeApp?.('outOfSpace', status.outOfSyncSpace) break } case T.FS.DiskSpaceStatus.Warning: { - const threshold = humanizeBytes(get().settings.spaceAvailableNotificationThreshold, 0) + const threshold = Constants.humanizeBytes(get().settings.spaceAvailableNotificationThreshold, 0) NotifyPopup('Disk Space Low', { body: `You have less than ${threshold} of storage space left.`, }) @@ -2682,7 +1944,7 @@ export const useFSState = Z.createZustand((set, get) => { try { await T.RPCGen.SimpleFSSimpleFSStartUploadRpcPromise({ sourceLocalPath: T.FS.getNormalizedLocalPath(localPath), - targetParentPath: pathToRPCPath(parentPath).kbfs, + targetParentPath: Constants.pathToRPCPath(parentPath).kbfs, }) } catch (err) { errorToActionOrThrow(err) diff --git a/shared/constants/git/index.tsx b/shared/stores/git.tsx similarity index 91% rename from shared/constants/git/index.tsx rename to shared/stores/git.tsx index ffe070ae3f3f..6e7854c3ab9c 100644 --- a/shared/constants/git/index.tsx +++ b/shared/stores/git.tsx @@ -1,11 +1,18 @@ -import * as S from '../strings' -import * as T from '../types' -import {ignorePromise} from '../utils' +import * as S from '@/constants/strings' +import * as T from '@/constants/types' +import {ignorePromise} from '@/constants/utils' import * as EngineGen from '@/actions/engine-gen-gen' import * as dateFns from 'date-fns' import * as Z from '@/util/zustand' import debounce from 'lodash/debounce' -import {storeRegistry} from '../store-registry' +import {navigateAppend} from '@/constants/router2' +import {useConfigState} from '@/stores/config' + +type Store = T.Immutable<{ + readonly error?: Error + readonly idToInfo: Map + readonly isNew?: Set +}> const parseRepos = (results: ReadonlyArray) => { const errors: Array = [] @@ -56,13 +63,13 @@ const parseRepoError = (result: T.RPCGen.GitRepoResult): Error => { return new Error(`Git repo error: ${errStr}`) } -const initialStore: T.Git.State = { +const initialStore: Store = { error: undefined, idToInfo: new Map(), isNew: new Set(), } -export interface State extends T.Git.State { +export interface State extends Store { dispatch: { setError: (err?: Error) => void clearBadges: () => void @@ -105,7 +112,7 @@ export const useGitState = Z.createZustand((set, get) => { async () => { const results = await T.RPCGen.gitGetAllGitMetadataRpcPromise(undefined, S.waitingKeyGitLoading) const {errors, repos} = parseRepos(results || []) - const {setGlobalError} = storeRegistry.getState('config').dispatch + const {setGlobalError} = useConfigState.getState().dispatch errors.forEach(e => setGlobalError(e)) set(s => { s.idToInfo = repos @@ -151,9 +158,7 @@ export const useGitState = Z.createZustand((set, get) => { await _load() for (const [, info] of get().idToInfo) { if (info.repoID === repoID && info.teamname === teamname) { - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {expanded: info.id}, selected: 'gitRoot'}) + navigateAppend({props: {expanded: info.id}, selected: 'gitRoot'}) break } } diff --git a/shared/constants/logout/index.tsx b/shared/stores/logout.tsx similarity index 88% rename from shared/constants/logout/index.tsx rename to shared/stores/logout.tsx index c1e85a767cad..1348cca9b47b 100644 --- a/shared/constants/logout/index.tsx +++ b/shared/stores/logout.tsx @@ -1,13 +1,14 @@ import logger from '@/logger' -import {ignorePromise, timeoutPromise} from '../utils' +import {ignorePromise, timeoutPromise} from '@/constants/utils' import * as T from '@/constants/types' // normally util.container but it re-exports from us so break the cycle import * as Z from '@/util/zustand' -import {settingsPasswordTab} from '../settings' -import {navigateAppend} from '../router2/util' -import {isMobile} from '../platform' -import * as Tabs from '../tabs' +import {settingsPasswordTab} from '@/constants/settings' +import {navigateAppend} from '@/constants/router2' +import {isMobile} from '@/constants/platform' +import * as Tabs from '@/constants/tabs' +// This store has no dependencies on other stores and is safe to import directly from other stores. type Store = T.Immutable<{ waiters: Map // if we ever restart handshake up this so we can ignore any waiters for old things diff --git a/shared/constants/notifications/index.tsx b/shared/stores/notifications.tsx similarity index 89% rename from shared/constants/notifications/index.tsx rename to shared/stores/notifications.tsx index 6d9881b80fa1..ba83285d4302 100644 --- a/shared/constants/notifications/index.tsx +++ b/shared/stores/notifications.tsx @@ -1,10 +1,11 @@ import * as Z from '@/util/zustand' import * as EngineGen from '@/actions/engine-gen-gen' -import type * as T from '../types' -import {isMobile} from '../platform' +import type * as T from '@/constants/types' +import {isMobile} from '@/constants/platform' import isEqual from 'lodash/isEqual' -import * as Tabs from '../tabs' -import {storeRegistry} from '../store-registry' +import * as Tabs from '@/constants/tabs' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' export type BadgeType = 'regular' | 'update' | 'error' | 'uploading' export type NotificationKeys = 'kbfsUploading' | 'outOfSpace' @@ -28,6 +29,9 @@ const initialStore: Store = { export interface State extends Store { dispatch: { + defer: { + onFavoritesLoad?: () => void + } onEngineIncomingImpl: (action: EngineGen.Actions) => void resetState: 'default' badgeApp: (key: NotificationKeys, on: boolean) => void @@ -67,7 +71,7 @@ const badgeStateToBadgeCounts = (bs: T.RPCGen.BadgeState) => { revokedDevices.forEach(d => allDeviceChanges.add(d)) // don't see badges related to this device - const deviceID = storeRegistry.getState('current-user').deviceID + const deviceID = useCurrentUserState.getState().deviceID counts.set(Tabs.devicesTab, allDeviceChanges.size - (allDeviceChanges.has(deviceID) ? 1 : 0)) counts.set(Tabs.chatTab, bs.smallTeamBadgeCount + bs.bigTeamBadgeCount) counts.set(Tabs.gitTab, newGitRepoGlobalUniqueIDs.length) @@ -99,19 +103,24 @@ export const useNotifState = Z.createZustand((set, get) => { updateWidgetBadge(s) }) }, + defer: { + onFavoritesLoad: () => { + throw new Error('onFavoritesLoad not implemented') + }, + }, onEngineIncomingImpl: action => { switch (action.type) { case EngineGen.keybase1NotifyAuditRootAuditError: - storeRegistry - .getState('config') + useConfigState + .getState() .dispatch.setGlobalError( new Error(`Keybase is buggy, please report this: ${action.payload.params.message}`) ) break case EngineGen.keybase1NotifyAuditBoxAuditError: - storeRegistry - .getState('config') + useConfigState + .getState() .dispatch.setGlobalError( new Error( `Keybase had a problem loading a team, please report this with \`keybase log send\`: ${action.payload.params.message}` @@ -120,11 +129,11 @@ export const useNotifState = Z.createZustand((set, get) => { break case EngineGen.keybase1NotifyBadgesBadgeState: { const badgeState = action.payload.params.badgeState - storeRegistry.getState('config').dispatch.setBadgeState(badgeState) + useConfigState.getState().dispatch.setBadgeState(badgeState) const counts = badgeStateToBadgeCounts(badgeState) if (!isMobile && shouldTriggerTlfLoad(badgeState)) { - storeRegistry.getState('fs').dispatch.favoritesLoad() + get().dispatch.defer.onFavoritesLoad?.() } if (counts) { get().dispatch.setBadgeCounts(counts) diff --git a/shared/constants/people/index.tsx b/shared/stores/people.tsx similarity index 97% rename from shared/constants/people/index.tsx rename to shared/stores/people.tsx index ba8f0024f463..27a6a2746e2d 100644 --- a/shared/constants/people/index.tsx +++ b/shared/stores/people.tsx @@ -1,16 +1,17 @@ import * as EngineGen from '@/actions/engine-gen-gen' -import {ignorePromise} from '../utils' +import {ignorePromise} from '@/constants/utils' import * as Z from '@/util/zustand' import invert from 'lodash/invert' import isEqual from 'lodash/isEqual' import logger from '@/logger' -import * as T from '../types' +import * as T from '@/constants/types' import type {IconType} from '@/common-adapters/icon.constants-gen' // do NOT pull in all of common-adapters -import {isMobile} from '../platform' +import {isMobile} from '@/constants/platform' import type {e164ToDisplay as e164ToDisplayType} from '@/util/phone-numbers' import debounce from 'lodash/debounce' -import {storeRegistry} from '../store-registry' -import {RPCError, isNetworkErr} from '../utils' +import {useConfigState} from '@/stores/config' +import {useFollowerState} from '@/stores/followers' +import {RPCError, isNetworkErr} from '@/constants/utils' // set this to true to have all todo items + a contact joined notification show up all the time const debugTodo = false as boolean @@ -387,7 +388,7 @@ export const usePeopleState = Z.createZustand((set, get) => { logger.info( 'getPeopleData: appFocused:', 'loggedIn', - storeRegistry.getState('config').loggedIn, + useConfigState.getState().loggedIn, 'action', {markViewed, numFollowSuggestionsWanted} ) @@ -397,7 +398,7 @@ export const usePeopleState = Z.createZustand((set, get) => { {markViewed, numFollowSuggestionsWanted}, getPeopleDataWaitingKey ) - const {following, followers} = storeRegistry.getState('followers') + const {following, followers} = useFollowerState.getState() const oldItems: Array = (data.items ?? []) .filter(item => !item.badged && item.data.t !== T.RPCGen.HomeScreenItemType.todo) .reduce(reduceRPCItemToPeopleItem, []) diff --git a/shared/constants/pinentry/index.tsx b/shared/stores/pinentry.tsx similarity index 92% rename from shared/constants/pinentry/index.tsx rename to shared/stores/pinentry.tsx index 1dae7f694344..8dc26ad61ace 100644 --- a/shared/constants/pinentry/index.tsx +++ b/shared/stores/pinentry.tsx @@ -1,10 +1,11 @@ import * as Z from '@/util/zustand' import * as EngineGen from '@/actions/engine-gen-gen' -import * as T from '../types' +import * as T from '@/constants/types' import logger from '@/logger' -import {invalidPasswordErrorString} from '@/constants/config/util' -import {wrapErrors} from '../utils' +import {invalidPasswordErrorString} from '@/constants/config' +import {wrapErrors} from '@/constants/utils' +// This store has no dependencies on other stores and is safe to import directly from other stores. export type Store = T.Immutable<{ cancelLabel?: string prompt: string diff --git a/shared/constants/profile/index.tsx b/shared/stores/profile.tsx similarity index 89% rename from shared/constants/profile/index.tsx rename to shared/stores/profile.tsx index 0d01f9582e9c..154178603e1c 100644 --- a/shared/constants/profile/index.tsx +++ b/shared/stores/profile.tsx @@ -1,15 +1,16 @@ -import * as T from '../types' -import {generateGUIID, ignorePromise, wrapErrors} from '../utils' -import * as S from '../strings' +import * as T from '@/constants/types' +import {generateGUIID, ignorePromise, wrapErrors} from '@/constants/utils' +import * as S from '@/constants/strings' import * as Validators from '@/util/simple-validators' import * as Z from '@/util/zustand' import logger from '@/logger' import openURL from '@/util/open-url' import {RPCError} from '@/util/errors' import {fixCrop} from '@/util/crop' -import {clearModals, navigateAppend, navigateUp} from '../router2/util' -import {storeRegistry} from '../store-registry' -import {navToProfile} from '../router2' +import {clearModals, navigateAppend, navigateUp} from '@/constants/router2' +import {useCurrentUserState} from '@/stores/current-user' +import {navToProfile} from '@/constants/router2' +import type {useTrackerState} from '@/stores/tracker2' type ProveGenericParams = { logoBlack: T.Tracker.SiteIconSet @@ -97,6 +98,14 @@ const initialStore: Store = { export interface State extends Store { dispatch: { + defer: { + onTracker2GetDetails?: (username: string) => T.Tracker.Details | undefined + onTracker2Load?: ( + params: Parameters['dispatch']['load']>[0] + ) => void + onTracker2ShowUser?: (username: string, asTracker: boolean, skipNav?: boolean) => void + onTracker2UpdateResult?: (guiID: string, result: T.Tracker.DetailsState, reason?: string) => void + } dynamic: { afterCheckProof?: () => void cancelAddProof?: () => void @@ -265,8 +274,8 @@ export const useProfileState = Z.createZustand((set, get) => { let canceled = false const loadAfter = () => - storeRegistry.getState('tracker2').dispatch.load({ - assertion: storeRegistry.getState('current-user').username, + get().dispatch.defer.onTracker2Load?.({ + assertion: useCurrentUserState.getState().username, guiID: generateGUIID(), inTracker: false, reason: '', @@ -407,12 +416,13 @@ export const useProfileState = Z.createZustand((set, get) => { s.errorCode = error.code }) if (error.code === T.RPCGen.StatusCode.scgeneric && reason === 'appLink') { - storeRegistry - .getState('deeplinks') - .dispatch.setLinkError( - "We couldn't find a valid service for proofs in this link. The link might be bad, or your Keybase app might be out of date and need to be updated." - ) - navigateAppend('keybaseLinkError') + navigateAppend({ + props: { + error: + "We couldn't find a valid service for proofs in this link. The link might be bad, or your Keybase app might be out of date and need to be updated.", + }, + selected: 'keybaseLinkError', + }) } } if (genericService) { @@ -435,7 +445,7 @@ export const useProfileState = Z.createZustand((set, get) => { backToProfile: () => { clearModals() setTimeout(() => { - get().dispatch.showUserProfile(storeRegistry.getState('current-user').username) + get().dispatch.showUserProfile(useCurrentUserState.getState().username) }, 100) }, checkProof: () => { @@ -483,6 +493,20 @@ export const useProfileState = Z.createZustand((set, get) => { clearErrors(s) }) }, + defer: { + onTracker2GetDetails: () => { + throw new Error('onTracker2GetDetails not implemented') + }, + onTracker2Load: () => { + throw new Error('onTracker2Load not implemented') + }, + onTracker2ShowUser: () => { + throw new Error('onTracker2ShowUser not implemented') + }, + onTracker2UpdateResult: () => { + throw new Error('onTracker2UpdateResult not implemented') + }, + }, dynamic: { cancelAddProof: _cancelAddProof, cancelPgpGen: undefined, @@ -494,15 +518,15 @@ export const useProfileState = Z.createZustand((set, get) => { editProfile: (bio, fullName, location) => { const f = async () => { await T.RPCGen.userProfileEditRpcPromise({bio, fullName, location}, S.waitingKeyTracker) - get().dispatch.showUserProfile(storeRegistry.getState('current-user').username) + get().dispatch.showUserProfile(useCurrentUserState.getState().username) } ignorePromise(f()) }, finishRevoking: () => { - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username get().dispatch.showUserProfile(username) - storeRegistry.getState('tracker2').dispatch.load({ - assertion: storeRegistry.getState('current-user').username, + get().dispatch.defer.onTracker2Load?.({ + assertion: useCurrentUserState.getState().username, guiID: generateGUIID(), inTracker: false, reason: '', @@ -597,9 +621,7 @@ export const useProfileState = Z.createZustand((set, get) => { }) const f = async () => { await T.RPCGen.proveCheckProofRpcPromise({sigID}, S.waitingKeyProfile) - storeRegistry - .getState('tracker2') - .dispatch.showUser(storeRegistry.getState('current-user').username, false) + get().dispatch.defer.onTracker2ShowUser?.(useCurrentUserState.getState().username, false) } ignorePromise(f()) }, @@ -626,7 +648,7 @@ export const useProfileState = Z.createZustand((set, get) => { set(s => { s.blockUserModal = undefined }) - storeRegistry.getState('tracker2').dispatch.load({ + get().dispatch.defer.onTracker2Load?.({ assertion: username, guiID: generateGUIID(), inTracker: false, @@ -647,10 +669,8 @@ export const useProfileState = Z.createZustand((set, get) => { }, submitRevokeProof: proofId => { const f = async () => { - const you = storeRegistry - .getState('tracker2') - .getDetails(storeRegistry.getState('current-user').username) - if (!you.assertions) return + const you = get().dispatch.defer.onTracker2GetDetails?.(useCurrentUserState.getState().username) + if (!you?.assertions) return const proof = [...you.assertions.values()].find(a => a.sigID === proofId) if (!proof) return @@ -681,7 +701,7 @@ export const useProfileState = Z.createZustand((set, get) => { const f = async () => { try { await T.RPCGen.userUnblockUserRpcPromise({username}) - storeRegistry.getState('tracker2').dispatch.load({ + get().dispatch.defer.onTracker2Load?.({ assertion: username, guiID: generateGUIID(), inTracker: false, @@ -693,9 +713,11 @@ export const useProfileState = Z.createZustand((set, get) => { } const error = _error logger.warn(`Error unblocking user ${username}`, error) - storeRegistry - .getState('tracker2') - .dispatch.updateResult(guiID, 'error', `Failed to unblock ${username}: ${error.desc}`) + get().dispatch.defer.onTracker2UpdateResult?.( + guiID, + 'error', + `Failed to unblock ${username}: ${error.desc}` + ) } } ignorePromise(f()) diff --git a/shared/constants/provision/index.tsx b/shared/stores/provision.tsx similarity index 86% rename from shared/constants/provision/index.tsx rename to shared/stores/provision.tsx index c49fcbf3c1d7..cf3f5c95f5ad 100644 --- a/shared/constants/provision/index.tsx +++ b/shared/stores/provision.tsx @@ -1,15 +1,15 @@ -import * as C from '..' -import * as T from '../types' -import {ignorePromise} from '../utils' +import * as T from '@/constants/types' +import {ignorePromise, wrapErrors} from '@/constants/utils' +import {waitingKeyProvision, waitingKeyProvisionForgotUsername} from '@/constants/strings' import * as Z from '@/util/zustand' import {RPCError} from '@/util/errors' -import {isMobile} from '../platform' +import {isMobile} from '@/constants/platform' import {type CommonResponseHandler} from '@/engine/types' import isEqual from 'lodash/isEqual' -import {rpcDeviceToDevice} from '../rpc-utils' -import {invalidPasswordErrorString} from '@/constants/config/util' -import {clearModals, navigateAppend} from '../router2/util' -import {storeRegistry} from '../store-registry' +import {rpcDeviceToDevice} from '@/constants/rpc-utils' +import {invalidPasswordErrorString} from '@/constants/config' +import {clearModals, navigateAppend} from '@/constants/router2' +import {useWaitingState} from '@/stores/waiting' export type Device = { deviceNumberOfType: number @@ -82,6 +82,7 @@ type Store = T.Immutable<{ inlineError?: RPCError passphrase: string provisionStep: number + startProvisionTrigger: number username: string }> const initialStore: Store = { @@ -99,6 +100,7 @@ const initialStore: Store = { inlineError: undefined, passphrase: '', provisionStep: 0, + startProvisionTrigger: 0, username: '', } @@ -121,15 +123,15 @@ export interface State extends Store { } export const useProvisionState = Z.createZustand((set, get) => { - const _cancel = C.wrapErrors((ignoreWarning?: boolean) => { - storeRegistry.getState('waiting').dispatch.clear(C.waitingKeyProvision) + const _cancel = wrapErrors((ignoreWarning?: boolean) => { + useWaitingState.getState().dispatch.clear(waitingKeyProvision) if (!ignoreWarning) { console.log('Provision: cancel called while not overloaded') } }) // add a new value to submit and clear things behind - const _updateAutoSubmit = C.wrapErrors((step: Store['autoSubmit'][0]) => { + const _updateAutoSubmit = wrapErrors((step: Store['autoSubmit'][0]) => { set(s => { const idx = s.autoSubmit.findIndex(a => a.type === step.type) if (idx !== -1) { @@ -139,16 +141,7 @@ export const useProvisionState = Z.createZustand((set, get) => { }) }) - const _setUsername = C.wrapErrors((username: string, restart: boolean = true) => { - set(s => { - s.username = username - s.autoSubmit = [{type: 'username'}] - }) - if (restart) { - get().dispatch.restartProvisioning() - } - }) - const _setPassphrase = C.wrapErrors((passphrase: string, restart: boolean = true) => { + const _setPassphrase = wrapErrors((passphrase: string, restart: boolean = true) => { set(s => { s.passphrase = passphrase }) @@ -158,7 +151,7 @@ export const useProvisionState = Z.createZustand((set, get) => { } }) - const _setDeviceName = C.wrapErrors((name: string, restart: boolean = true) => { + const _setDeviceName = wrapErrors((name: string, restart: boolean = true) => { set(s => { s.deviceName = name }) @@ -168,7 +161,7 @@ export const useProvisionState = Z.createZustand((set, get) => { } }) - const _submitDeviceSelect = C.wrapErrors((name: string, restart: boolean = true) => { + const _submitDeviceSelect = wrapErrors((name: string, restart: boolean = true) => { const devices = get().devices const selectedDevice = devices.find(d => d.name === name) if (!selectedDevice) { @@ -183,7 +176,7 @@ export const useProvisionState = Z.createZustand((set, get) => { } }) - const _submitTextCode = C.wrapErrors((_code: string) => { + const _submitTextCode = wrapErrors((_code: string) => { console.log('Provision, unwatched submitTextCode called') get().dispatch.restartProvisioning() }) @@ -204,7 +197,7 @@ export const useProvisionState = Z.createZustand((set, get) => { let cancelled = false const setupCancel = (response: CommonResponseHandler) => { set(s => { - s.dispatch.dynamic.cancel = C.wrapErrors(() => { + s.dispatch.dynamic.cancel = wrapErrors(() => { set(s => { s.dispatch.dynamic.cancel = _cancel }) @@ -231,7 +224,7 @@ export const useProvisionState = Z.createZustand((set, get) => { set(s => { s.error = previousErr s.codePageIncomingTextCode = phrase - s.dispatch.dynamic.submitTextCode = C.wrapErrors((code: string) => { + s.dispatch.dynamic.submitTextCode = wrapErrors((code: string) => { set(s => { s.dispatch.dynamic.submitTextCode = _submitTextCode }) @@ -259,13 +252,13 @@ export const useProvisionState = Z.createZustand((set, get) => { }, incomingCallMap: { 'keybase.1.provisionUi.DisplaySecretExchanged': () => { - storeRegistry.getState('waiting').dispatch.increment(C.waitingKeyProvision) + useWaitingState.getState().dispatch.increment(waitingKeyProvision) }, 'keybase.1.provisionUi.ProvisioneeSuccess': () => {}, 'keybase.1.provisionUi.ProvisionerSuccess': () => {}, }, params: undefined, - waitingKey: C.waitingKeyProvision, + waitingKey: waitingKeyProvision, }) } catch { } finally { @@ -273,7 +266,6 @@ export const useProvisionState = Z.createZustand((set, get) => { s.dispatch.dynamic.cancel = _cancel s.dispatch.dynamic.setDeviceName = _setDeviceName s.dispatch.dynamic.setPassphrase = _setPassphrase - s.dispatch.dynamic.setUsername = _setUsername s.dispatch.dynamic.submitDeviceSelect = _submitDeviceSelect s.dispatch.dynamic.submitTextCode = _submitTextCode }) @@ -286,7 +278,15 @@ export const useProvisionState = Z.createZustand((set, get) => { cancel: _cancel, setDeviceName: _setDeviceName, setPassphrase: _setPassphrase, - setUsername: _setUsername, + setUsername: wrapErrors((username: string, restart: boolean = true) => { + set(s => { + s.username = username + s.autoSubmit = [{type: 'username'}] + }) + if (restart) { + get().dispatch.restartProvisioning() + } + }), submitDeviceSelect: _submitDeviceSelect, submitTextCode: _submitTextCode, }, @@ -296,7 +296,7 @@ export const useProvisionState = Z.createZustand((set, get) => { try { await T.RPCGen.accountRecoverUsernameWithEmailRpcPromise( {email}, - C.waitingKeyProvisionForgotUsername + waitingKeyProvisionForgotUsername ) set(s => { s.forgotUsernameResult = 'success' @@ -314,7 +314,7 @@ export const useProvisionState = Z.createZustand((set, get) => { try { await T.RPCGen.accountRecoverUsernameWithPhoneRpcPromise( {phone}, - C.waitingKeyProvisionForgotUsername + waitingKeyProvisionForgotUsername ) set(s => { s.forgotUsernameResult = 'success' @@ -366,7 +366,7 @@ export const useProvisionState = Z.createZustand((set, get) => { // Make cancel set the flag and cancel the current rpc const setupCancel = (response: CommonResponseHandler) => { set(s => { - s.dispatch.dynamic.cancel = C.wrapErrors(() => { + s.dispatch.dynamic.cancel = wrapErrors(() => { set(s => { s.dispatch.dynamic.cancel = _cancel }) @@ -397,7 +397,7 @@ export const useProvisionState = Z.createZustand((set, get) => { set(s => { s.error = previousErr s.codePageIncomingTextCode = phrase - s.dispatch.dynamic.submitTextCode = C.wrapErrors((code: string) => { + s.dispatch.dynamic.submitTextCode = wrapErrors((code: string) => { set(s => { s.dispatch.dynamic.submitTextCode = _submitTextCode }) @@ -418,7 +418,7 @@ export const useProvisionState = Z.createZustand((set, get) => { set(s => { s.error = errorMessage s.existingDevices = T.castDraft(existingDevices ?? []) - s.dispatch.dynamic.setDeviceName = C.wrapErrors((name: string) => { + s.dispatch.dynamic.setDeviceName = wrapErrors((name: string) => { set(s => { s.dispatch.dynamic.setDeviceName = _setDeviceName }) @@ -443,7 +443,7 @@ export const useProvisionState = Z.createZustand((set, get) => { set(s => { s.error = '' s.devices = devices - s.dispatch.dynamic.submitDeviceSelect = C.wrapErrors((device: string) => { + s.dispatch.dynamic.submitDeviceSelect = wrapErrors((device: string) => { set(s => { s.dispatch.dynamic.submitDeviceSelect = _submitDeviceSelect }) @@ -472,7 +472,7 @@ export const useProvisionState = Z.createZustand((set, get) => { // Service asking us again due to an error? set(s => { s.error = retryLabel === invalidPasswordErrorString ? 'Incorrect password.' : retryLabel - s.dispatch.dynamic.setPassphrase = C.wrapErrors((passphrase: string) => { + s.dispatch.dynamic.setPassphrase = wrapErrors((passphrase: string) => { set(s => { s.dispatch.dynamic.setPassphrase = _setPassphrase }) @@ -502,7 +502,7 @@ export const useProvisionState = Z.createZustand((set, get) => { incomingCallMap: { 'keybase.1.loginUi.displayPrimaryPaperKey': () => {}, 'keybase.1.provisionUi.DisplaySecretExchanged': () => { - storeRegistry.getState('waiting').dispatch.increment(C.waitingKeyProvision) + useWaitingState.getState().dispatch.increment(waitingKeyProvision) }, 'keybase.1.provisionUi.ProvisioneeSuccess': () => {}, 'keybase.1.provisionUi.ProvisionerSuccess': () => {}, @@ -515,7 +515,7 @@ export const useProvisionState = Z.createZustand((set, get) => { paperKey: '', username, }, - waitingKey: C.waitingKeyProvision, + waitingKey: waitingKeyProvision, }) get().dispatch.resetState() } catch (_finalError) { @@ -551,23 +551,11 @@ export const useProvisionState = Z.createZustand((set, get) => { }, startProvision: (name = '', fromReset = false) => { get().dispatch.dynamic.cancel?.(true) - storeRegistry.getState('config').dispatch.setLoginError() - storeRegistry.getState('config').dispatch.resetRevokedSelf() - set(s => { + s.startProvisionTrigger++ s.username = name }) - const f = async () => { - // If we're logged in, we're coming from the user switcher; log out first to prevent the service from getting out of sync with the GUI about our logged-in-ness - if (storeRegistry.getState('config').loggedIn) { - await T.RPCGen.loginLogoutRpcPromise( - {force: false, keepSecrets: true}, - C.waitingKeyConfigLoginAsOther - ) - } - navigateAppend({props: {fromReset}, selected: 'username'}) - } - ignorePromise(f()) + navigateAppend({props: {fromReset}, selected: 'username'}) }, } diff --git a/shared/constants/push.d.ts b/shared/stores/push.d.ts similarity index 70% rename from shared/constants/push.d.ts rename to shared/stores/push.d.ts index ae8fd435e57a..29e8b1ff366f 100644 --- a/shared/constants/push.d.ts +++ b/shared/stores/push.d.ts @@ -1,4 +1,4 @@ -import type * as T from './types' +import type * as T from '@/constants/types' import type {UseBoundStore, StoreApi} from 'zustand' type Store = T.Immutable<{ @@ -10,6 +10,15 @@ type Store = T.Immutable<{ export type State = Store & { dispatch: { + defer: { + onGetDaemonHandshakeState?: () => T.Config.DaemonHandshakeState + onNavigateToThread?: ( + conversationIDKey: T.Chat.ConversationIDKey, + reason: 'push' | 'extension', + pushBody?: string + ) => void + onShowUserProfile?: (username: string) => void + } checkPermissions: () => Promise deleteToken: (version: number) => void handlePush: (notification: T.Push.PushNotification) => void diff --git a/shared/constants/push.desktop.tsx b/shared/stores/push.desktop.tsx similarity index 75% rename from shared/constants/push.desktop.tsx rename to shared/stores/push.desktop.tsx index b3fed7e84595..4a59a43a8a35 100644 --- a/shared/constants/push.desktop.tsx +++ b/shared/stores/push.desktop.tsx @@ -1,5 +1,5 @@ import * as Z from '@/util/zustand' -import {type Store, type State} from './push' +import {type Store, type State} from '@/stores/push' export const tokenType = '' @@ -15,6 +15,13 @@ export const usePushState = Z.createZustand(() => { checkPermissions: async () => { return Promise.resolve(false) }, + defer: { + onGetDaemonHandshakeState: () => { + return 'done' + }, + onNavigateToThread: () => {}, + onShowUserProfile: () => {}, + }, deleteToken: () => {}, handlePush: () => {}, initialPermissionsCheck: () => {}, diff --git a/shared/constants/push.native.tsx b/shared/stores/push.native.tsx similarity index 84% rename from shared/constants/push.native.tsx rename to shared/stores/push.native.tsx index 605401cfb53f..d780cd2cb941 100644 --- a/shared/constants/push.native.tsx +++ b/shared/stores/push.native.tsx @@ -1,13 +1,16 @@ -import * as Tabs from './tabs' -import * as S from './strings' -import {ignorePromise, neverThrowPromiseFunc, timeoutPromise} from './utils' -import {navigateAppend, navUpToScreen, switchTab} from './router2/util' -import {storeRegistry} from './store-registry' +import * as Tabs from '@/constants/tabs' +import * as S from '@/constants/strings' +import {ignorePromise, neverThrowPromiseFunc, timeoutPromise} from '@/constants/utils' +import {navigateAppend, navUpToScreen, switchTab} from '@/constants/router2' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' +import {useLogoutState} from '@/stores/logout' +import {useWaitingState} from '@/stores/waiting' import * as Z from '@/util/zustand' import logger from '@/logger' -import * as T from './types' +import * as T from '@/constants/types' import {isDevApplePushToken} from '@/local-debug' -import {isIOS} from './platform' +import {isIOS} from '@/constants/platform' import { checkPushPermissions, getRegistrationToken, @@ -15,7 +18,7 @@ import { requestPushPermissions, removeAllPendingNotificationRequests, } from 'react-native-kb' -import {type Store, type State} from './push' +import {type Store, type State} from '@/stores/push' export const tokenType = isIOS ? (isDevApplePushToken ? 'appledev' : 'apple') : 'androidplay' @@ -69,7 +72,7 @@ export const usePushState = Z.createZustand((set, get) => { const {conversationIDKey, unboxPayload, membersType} = notification - storeRegistry.getConvoState(conversationIDKey).dispatch.navigateToThread('push', undefined, unboxPayload) + get().dispatch.defer.onNavigateToThread?.(conversationIDKey, 'push', unboxPayload) if (unboxPayload && membersType && !isIOS) { try { await T.RPCChat.localUnboxMobilePushNotificationRpcPromise({ @@ -112,12 +115,23 @@ export const usePushState = Z.createZustand((set, get) => { return false } }, + defer: { + onGetDaemonHandshakeState: () => { + throw new Error('onGetDaemonHandshakeState not implemented') + }, + onNavigateToThread: () => { + throw new Error('onNavigateToThread not implemented') + }, + onShowUserProfile: () => { + throw new Error('onShowUserProfile not implemented') + }, + }, deleteToken: version => { const f = async () => { const waitKey = 'push:deleteToken' - storeRegistry.getState('logout').dispatch.wait(waitKey, version, true) + useLogoutState.getState().dispatch.wait(waitKey, version, true) try { - const deviceID = storeRegistry.getState('current-user').deviceID + const deviceID = useCurrentUserState.getState().deviceID if (!deviceID) { logger.info('[PushToken] no device id') return @@ -133,7 +147,7 @@ export const usePushState = Z.createZustand((set, get) => { } catch (e) { logger.error('[PushToken] delete failed', e) } finally { - storeRegistry.getState('logout').dispatch.wait(waitKey, version, false) + useLogoutState.getState().dispatch.wait(waitKey, version, false) } } ignorePromise(f()) @@ -156,17 +170,17 @@ export const usePushState = Z.createZustand((set, get) => { // We only care if the user clicked while in session if (notification.userInteraction) { const {username} = notification - storeRegistry.getState('profile').dispatch.showUserProfile(username) + get().dispatch.defer.onShowUserProfile?.(username) } break case 'chat.extension': { const {conversationIDKey} = notification - storeRegistry.getConvoState(conversationIDKey).dispatch.navigateToThread('extension') + get().dispatch.defer.onNavigateToThread?.(conversationIDKey, 'extension') } break case 'settings.contacts': - if (storeRegistry.getState('config').loggedIn) { + if (useConfigState.getState().loggedIn) { switchTab(Tabs.peopleTab) navUpToScreen('peopleRoot') } @@ -230,14 +244,14 @@ export const usePushState = Z.createZustand((set, get) => { const shownPushPrompt = await askNativeIfSystemPushPromptHasBeenShown() if (shownPushPrompt) { // we've already shown the prompt, take them to settings - storeRegistry.getState('config').dispatch.dynamic.openAppSettings?.() + useConfigState.getState().dispatch.defer.openAppSettings?.() get().dispatch.showPermissionsPrompt({persistSkip: true, show: false}) return } } try { - storeRegistry.getState('config').dispatch.dynamic.openAppSettings?.() - const {increment} = storeRegistry.getState('waiting').dispatch + useConfigState.getState().dispatch.defer.openAppSettings?.() + const {increment} = useWaitingState.getState().dispatch increment(S.waitingKeyPushPermissionsRequesting) await requestPermissionsFromNative() const permissions = await checkPermissionsFromNative() @@ -253,7 +267,7 @@ export const usePushState = Z.createZustand((set, get) => { }) } } finally { - const {decrement} = storeRegistry.getState('waiting').dispatch + const {decrement} = useWaitingState.getState().dispatch decrement(S.waitingKeyPushPermissionsRequesting) get().dispatch.showPermissionsPrompt({persistSkip: true, show: false}) } @@ -267,7 +281,7 @@ export const usePushState = Z.createZustand((set, get) => { }) const uploadPushToken = async () => { - const {deviceID, username} = storeRegistry.getState('current-user') + const {deviceID, username} = useCurrentUserState.getState() if (!username || !deviceID) { return } @@ -303,8 +317,8 @@ export const usePushState = Z.createZustand((set, get) => { // permissions checker finishes after the routeToInitialScreen is done. if ( p.show && - storeRegistry.getState('config').loggedIn && - storeRegistry.getState('daemon').handshakeState === 'done' && + useConfigState.getState().loggedIn && + get().dispatch.defer.onGetDaemonHandshakeState?.() === 'done' && !get().justSignedUp && !get().hasPermissions ) { diff --git a/shared/constants/recover-password/index.tsx b/shared/stores/recover-password.tsx similarity index 87% rename from shared/constants/recover-password/index.tsx rename to shared/stores/recover-password.tsx index 47d6eec523fa..6136a01a06ec 100644 --- a/shared/constants/recover-password/index.tsx +++ b/shared/stores/recover-password.tsx @@ -1,13 +1,13 @@ -import * as T from '../types' -import {ignorePromise, wrapErrors} from '../utils' -import {waitingKeyRecoverPassword} from '../strings' +import * as T from '@/constants/types' +import {ignorePromise, wrapErrors} from '@/constants/utils' +import {waitingKeyRecoverPassword} from '@/constants/strings' import * as Z from '@/util/zustand' import logger from '@/logger' import {RPCError} from '@/util/errors' -import {type Device} from '../provision' -import {rpcDeviceToDevice} from '../rpc-utils' -import {clearModals, navigateAppend, navigateUp} from '../router2/util' -import {storeRegistry} from '../store-registry' +import {type Device} from '@/stores/provision' +import {rpcDeviceToDevice} from '@/constants/rpc-utils' +import {clearModals, navigateAppend, navigateUp} from '@/constants/router2' +import {useConfigState} from '@/stores/config' type Store = T.Immutable<{ devices: Array @@ -34,6 +34,10 @@ const initialStore: Store = { export interface State extends Store { dispatch: { + defer: { + onProvisionCancel?: (ignoreWarning?: boolean) => void + onStartAccountReset?: (skipPassword: boolean, username: string) => void + } dynamic: { cancel?: () => void submitDeviceSelect?: (name: string) => void @@ -48,6 +52,14 @@ export interface State extends Store { export const useState = Z.createZustand((set, get) => { const dispatch: State['dispatch'] = { + defer: { + onProvisionCancel: () => { + throw new Error('onProvisionCancel not implemented') + }, + onStartAccountReset: () => { + throw new Error('onStartAccountReset not implemented') + }, + }, dynamic: { cancel: undefined, submitDeviceSelect: undefined, @@ -74,7 +86,7 @@ export const useState = Z.createZustand((set, get) => { const f = async () => { if (p.abortProvisioning) { - storeRegistry.getState('provision').dispatch.dynamic.cancel?.() + get().dispatch.defer.onProvisionCancel?.() } let hadError = false try { @@ -107,17 +119,13 @@ export const useState = Z.createZustand((set, get) => { } }) }) - storeRegistry - .getState('router') - .dispatch.navigateAppend('recoverPasswordDeviceSelector', !!replaceRoute) + navigateAppend('recoverPasswordDeviceSelector', !!replaceRoute) }, 'keybase.1.loginUi.promptPassphraseRecovery': () => {}, // This same RPC is called at the beginning and end of the 7-day wait by the service. 'keybase.1.loginUi.promptResetAccount': (params, response) => { if (params.prompt.t === T.RPCGen.ResetPromptType.enterResetPw) { - storeRegistry - .getState('router') - .dispatch.navigateAppend('recoverPasswordPromptResetPassword') + navigateAppend('recoverPasswordPromptResetPassword') const clear = () => { set(s => { s.dispatch.dynamic.submitResetPassword = undefined @@ -142,8 +150,7 @@ export const useState = Z.createZustand((set, get) => { }) }) } else { - const {startAccountReset} = storeRegistry.getState('autoreset').dispatch - startAccountReset(true, '') + get().dispatch.defer.onStartAccountReset?.(true, '') response.result(T.RPCGen.ResetPromptResponse.nothing) } }, @@ -227,14 +234,10 @@ export const useState = Z.createZustand((set, get) => { set(s => { s.error = msg }) - storeRegistry - .getState('router') - .dispatch.navigateAppend( - storeRegistry.getState('config').loggedIn - ? 'recoverPasswordErrorModal' - : 'recoverPasswordError', - true - ) + navigateAppend( + useConfigState.getState().loggedIn ? 'recoverPasswordErrorModal' : 'recoverPasswordError', + true + ) } } finally { set(s => { diff --git a/shared/stores/router2.tsx b/shared/stores/router2.tsx new file mode 100644 index 000000000000..0a1cf4cc0804 --- /dev/null +++ b/shared/stores/router2.tsx @@ -0,0 +1,90 @@ +import type * as T from '@/constants/types' +import * as Z from '@/util/zustand' +import type * as Tabs from '@/constants/tabs' +import type {RouteKeys} from '@/router-v2/route-params' +import * as Util from '@/constants/router2' + +export { + type NavState, + getTab, + navigationRef, + getRootState, + _getNavigator, + logState, + getVisiblePath, + getModalStack, + getVisibleScreen, + navToProfile, + navToThread, + getRouteTab, + getRouteLoggedIn, + useSafeFocusEffect, + makeScreen, +} from '@/constants/router2' +export type {PathParam, Navigator} from '@/constants/router2' + +type Store = T.Immutable<{ + navState?: unknown +}> + +const initialStore: Store = { + navState: undefined, +} + +export interface State extends Store { + dispatch: { + clearModals: () => void + defer: { + tabLongPress?: (tab: string) => void + } + navigateAppend: (path: Util.PathParam, replace?: boolean) => void + navigateUp: () => void + navUpToScreen: (name: RouteKeys) => void + popStack: () => void + resetState: () => void + setNavState: (ns: Util.NavState) => void + switchTab: (tab: Tabs.AppTab) => void + } + appendEncryptRecipientsBuilder: () => void + appendNewChatBuilder: () => void + appendNewTeamBuilder: (teamID: T.Teams.TeamID) => void + appendPeopleBuilder: () => void +} + +export const useRouterState = Z.createZustand((set, get) => { + const dispatch: State['dispatch'] = { + clearModals: Util.clearModals, + defer: { + tabLongPress: undefined, + }, + navUpToScreen: Util.navUpToScreen, + navigateAppend: Util.navigateAppend, + navigateUp: Util.navigateUp, + popStack: Util.popStack, + resetState: () => { + set(s => ({ + ...s, + dispatch: s.dispatch, + })) + }, + setNavState: next => { + const DEBUG_NAV = __DEV__ && (false as boolean) + DEBUG_NAV && console.log('[Nav] setNavState') + const prev = get().navState as Util.NavState + if (prev === next) return + set(s => { + s.navState = next + }) + }, + switchTab: Util.switchTab, + } + + return { + ...initialStore, + appendEncryptRecipientsBuilder: Util.appendEncryptRecipientsBuilder, + appendNewChatBuilder: Util.appendNewChatBuilder, + appendNewTeamBuilder: Util.appendNewTeamBuilder, + appendPeopleBuilder: Util.appendPeopleBuilder, + dispatch, + } +}) diff --git a/shared/constants/settings-chat/index.tsx b/shared/stores/settings-chat.tsx similarity index 91% rename from shared/constants/settings-chat/index.tsx rename to shared/stores/settings-chat.tsx index 24b2b4a1024f..71e6af5aca23 100644 --- a/shared/constants/settings-chat/index.tsx +++ b/shared/stores/settings-chat.tsx @@ -1,8 +1,8 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' -import * as S from '../strings' +import * as T from '@/constants/types' +import {ignorePromise} from '@/constants/utils' +import * as S from '@/constants/strings' import * as Z from '@/util/zustand' -import {storeRegistry} from '../store-registry' +import {useConfigState} from '@/stores/config' export type ChatUnfurlState = { unfurlMode?: T.RPCChat.UnfurlMode @@ -49,7 +49,7 @@ export const useSettingsChatState = Z.createZustand((set, get) => { const dispatch: State['dispatch'] = { contactSettingsRefresh: () => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } try { @@ -67,7 +67,7 @@ export const useSettingsChatState = Z.createZustand((set, get) => { }, contactSettingsSaved: (enabled, indirectFollowees, teamsEnabled, teamsList) => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } @@ -100,7 +100,7 @@ export const useSettingsChatState = Z.createZustand((set, get) => { resetState: 'default', unfurlSettingsRefresh: () => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } try { @@ -128,7 +128,7 @@ export const useSettingsChatState = Z.createZustand((set, get) => { s.unfurl = T.castDraft({unfurlError: undefined, unfurlMode, unfurlWhitelist}) }) const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } try { diff --git a/shared/constants/settings-contacts.d.ts b/shared/stores/settings-contacts.d.ts similarity index 95% rename from shared/constants/settings-contacts.d.ts rename to shared/stores/settings-contacts.d.ts index 3f48c273c201..2920c6643055 100644 --- a/shared/constants/settings-contacts.d.ts +++ b/shared/stores/settings-contacts.d.ts @@ -1,4 +1,4 @@ -import type * as T from './types' +import type * as T from '@/constants/types' import type {UseBoundStore, StoreApi} from 'zustand' type PermissionStatus = 'granted' | 'denied' | 'undetermined' | 'unknown' diff --git a/shared/constants/settings-contacts.desktop.tsx b/shared/stores/settings-contacts.desktop.tsx similarity index 100% rename from shared/constants/settings-contacts.desktop.tsx rename to shared/stores/settings-contacts.desktop.tsx diff --git a/shared/constants/settings-contacts.native.tsx b/shared/stores/settings-contacts.native.tsx similarity index 90% rename from shared/constants/settings-contacts.native.tsx rename to shared/stores/settings-contacts.native.tsx index 3d0f7da0407e..5043bf04d1f3 100644 --- a/shared/constants/settings-contacts.native.tsx +++ b/shared/stores/settings-contacts.native.tsx @@ -1,17 +1,19 @@ -import * as C from '.' import * as Contacts from 'expo-contacts' -import {ignorePromise} from './utils' -import * as T from './types' +import {ignorePromise} from '@/constants/utils' +import {importContactsWaitingKey} from '@/constants/strings' +import * as T from '@/constants/types' import * as Z from '@/util/zustand' import {addNotificationRequest} from 'react-native-kb' import logger from '@/logger' import type {Store, State} from './settings-contacts' import {RPCError} from '@/util/errors' import {getDefaultCountryCode} from 'react-native-kb' -import {getE164} from './settings-phone' +import {getE164} from '@/util/phone-numbers' import {pluralize} from '@/util/string' -import {navigateAppend} from './router2/util' -import {storeRegistry} from './store-registry' +import {navigateAppend} from '@/constants/router2' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' +import {useWaitingState} from '@/stores/waiting' const importContactsConfigKey = (username: string) => `ui.importContacts.${username}` @@ -80,14 +82,14 @@ export const useSettingsContactsState = Z.createZustand((set, get) => { }) } const f = async () => { - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username if (!username) { logger.warn('no username') return } await T.RPCGen.configGuiSetValueRpcPromise( {path: importContactsConfigKey(username), value: {b: enable, isNull: false}}, - C.importContactsWaitingKey + importContactsWaitingKey ) get().dispatch.loadContactImportEnabled() } @@ -100,10 +102,10 @@ export const useSettingsContactsState = Z.createZustand((set, get) => { }, loadContactImportEnabled: () => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username if (!username) { logger.warn('no username') return @@ -112,7 +114,7 @@ export const useSettingsContactsState = Z.createZustand((set, get) => { try { const value = await T.RPCGen.configGuiGetValueRpcPromise( {path: importContactsConfigKey(username)}, - C.importContactsWaitingKey + importContactsWaitingKey ) enabled = !!value.b && !value.isNull } catch (error) { @@ -241,8 +243,8 @@ export const useSettingsContactsState = Z.createZustand((set, get) => { }, requestPermissions: (thenToggleImportOn?: boolean, fromSettings?: boolean) => { const f = async () => { - const {decrement, increment} = storeRegistry.getState('waiting').dispatch - increment(C.importContactsWaitingKey) + const {decrement, increment} = useWaitingState.getState().dispatch + increment(importContactsWaitingKey) const status = (await Contacts.requestPermissionsAsync()).status if (status === Contacts.PermissionStatus.GRANTED && thenToggleImportOn) { @@ -251,7 +253,7 @@ export const useSettingsContactsState = Z.createZustand((set, get) => { set(s => { s.permissionStatus = status }) - decrement(C.importContactsWaitingKey) + decrement(importContactsWaitingKey) } ignorePromise(f()) }, diff --git a/shared/constants/settings-email/index.tsx b/shared/stores/settings-email.tsx similarity index 97% rename from shared/constants/settings-email/index.tsx rename to shared/stores/settings-email.tsx index 7cb1d649961a..935f43208173 100644 --- a/shared/constants/settings-email/index.tsx +++ b/shared/stores/settings-email.tsx @@ -1,7 +1,7 @@ import * as Z from '@/util/zustand' -import {addEmailWaitingKey} from '../strings' -import {ignorePromise} from '../utils' -import * as T from '../types' +import {addEmailWaitingKey} from '@/constants/strings' +import {ignorePromise} from '@/constants/utils' +import * as T from '@/constants/types' import {isValidEmail} from '@/util/simple-validators' import {RPCError} from '@/util/errors' import logger from '@/logger' diff --git a/shared/constants/settings-invites/index.tsx b/shared/stores/settings-invites.tsx similarity index 95% rename from shared/constants/settings-invites/index.tsx rename to shared/stores/settings-invites.tsx index c6b6edc8e582..93e5df2833bf 100644 --- a/shared/constants/settings-invites/index.tsx +++ b/shared/stores/settings-invites.tsx @@ -1,11 +1,11 @@ import * as Z from '@/util/zustand' -import {ignorePromise} from '../utils' -import {waitingKeySettingsGeneric} from '../strings' +import {ignorePromise} from '@/constants/utils' +import {waitingKeySettingsGeneric} from '@/constants/strings' import {RPCError} from '@/util/errors' import logger from '@/logger' import trim from 'lodash/trim' -import * as T from '../types' -import {navigateAppend} from '../router2/util' +import * as T from '@/constants/types' +import {navigateAppend} from '@/constants/router2' type InviteBase = { id: string diff --git a/shared/constants/settings-notifications/index.tsx b/shared/stores/settings-notifications.tsx similarity index 97% rename from shared/constants/settings-notifications/index.tsx rename to shared/stores/settings-notifications.tsx index f93adaae9fdc..c22a90a587f0 100644 --- a/shared/constants/settings-notifications/index.tsx +++ b/shared/stores/settings-notifications.tsx @@ -1,10 +1,10 @@ import * as Z from '@/util/zustand' -import * as S from '../strings' -import {isAndroidNewerThanN} from '../platform' -import {ignorePromise, timeoutPromise} from '../utils' +import * as S from '@/constants/strings' +import {isAndroidNewerThanN} from '@/constants/platform' +import {ignorePromise, timeoutPromise} from '@/constants/utils' import {RPCError} from '@/util/errors' import logger from '@/logger' -import * as T from '../types' +import * as T from '@/constants/types' const securityGroup = 'security' const soundGroup = 'sound' diff --git a/shared/constants/settings-password/index.tsx b/shared/stores/settings-password.tsx similarity index 93% rename from shared/constants/settings-password/index.tsx rename to shared/stores/settings-password.tsx index ac305c0f6159..0b3c441f2422 100644 --- a/shared/constants/settings-password/index.tsx +++ b/shared/stores/settings-password.tsx @@ -1,11 +1,11 @@ import * as Z from '@/util/zustand' -import {ignorePromise} from '../utils' -import {waitingKeySettingsGeneric} from '../strings' +import {ignorePromise} from '@/constants/utils' +import {waitingKeySettingsGeneric} from '@/constants/strings' import logger from '@/logger' import {RPCError} from '@/util/errors' -import * as T from '../types' -import {navigateUp} from '../router2/util' -import {storeRegistry} from '../store-registry' +import * as T from '@/constants/types' +import {navigateUp} from '@/constants/router2' +import {useLogoutState} from '@/stores/logout' type Store = T.Immutable<{ error: string @@ -141,7 +141,7 @@ export const usePWState = Z.createZustand((set, get) => { ) if (thenLogout) { - storeRegistry.getState('logout').dispatch.requestLogout() + useLogoutState.getState().dispatch.requestLogout() } navigateUp() } catch (error) { diff --git a/shared/constants/settings-phone/index.tsx b/shared/stores/settings-phone.tsx similarity index 88% rename from shared/constants/settings-phone/index.tsx rename to shared/stores/settings-phone.tsx index cc19e82aa27d..eefe6932475d 100644 --- a/shared/constants/settings-phone/index.tsx +++ b/shared/stores/settings-phone.tsx @@ -1,15 +1,10 @@ -import * as T from '../types' -import * as S from '../strings' -import {ignorePromise} from '../utils' +import * as T from '@/constants/types' +import * as S from '@/constants/strings' +import {ignorePromise} from '@/constants/utils' import * as Z from '@/util/zustand' import logger from '@/logger' import {RPCError} from '@/util/errors' -import type { - e164ToDisplay as e164ToDisplayType, - phoneUtil as phoneUtilType, - ValidationResult as ValidationResultType, - PhoneNumberFormat as PhoneNumberFormatType, -} from '@/util/phone-numbers' +import type {e164ToDisplay as e164ToDisplayType} from '@/util/phone-numbers' export const makePhoneRow = (): PhoneRow => ({ displayNumber: '', @@ -19,25 +14,6 @@ export const makePhoneRow = (): PhoneRow => ({ verified: false, }) -// Get phone number in e.164, or null if we can't parse it. -export const getE164 = (phoneNumber: string, countryCode?: string) => { - const {phoneUtil, ValidationResult, PhoneNumberFormat} = require('@/util/phone-numbers') as { - phoneUtil: typeof phoneUtilType - ValidationResult: typeof ValidationResultType - PhoneNumberFormat: typeof PhoneNumberFormatType - } - try { - const parsed = countryCode ? phoneUtil.parse(phoneNumber, countryCode) : phoneUtil.parse(phoneNumber) - const reason = phoneUtil.isPossibleNumberWithReason(parsed) - if (reason !== ValidationResult.IS_POSSIBLE) { - return null - } - return phoneUtil.format(parsed, PhoneNumberFormat.E164) - } catch { - return null - } -} - const toPhoneRow = (p: T.RPCGen.UserPhoneNumber) => { const {e164ToDisplay} = require('@/util/phone-numbers') as {e164ToDisplay: typeof e164ToDisplayType} return { @@ -67,7 +43,7 @@ export const makePhoneError = (e: RPCError) => { } } -type PhoneRow = { +export type PhoneRow = { displayNumber: string e164: string searchable: boolean diff --git a/shared/constants/settings/index.tsx b/shared/stores/settings.tsx similarity index 70% rename from shared/constants/settings/index.tsx rename to shared/stores/settings.tsx index ce2327c8888d..6c9ed5b1a946 100644 --- a/shared/constants/settings/index.tsx +++ b/shared/stores/settings.tsx @@ -1,18 +1,20 @@ -import * as T from '../types' -import {ignorePromise, timeoutPromise} from '../utils' -import * as S from '../strings' -import {androidIsTestDevice, pprofDir} from '../platform' -import * as EngineGen from '@/actions/engine-gen-gen' +import * as T from '@/constants/types' +import {ignorePromise, timeoutPromise} from '@/constants/utils' +import * as S from '@/constants/strings' +import {androidIsTestDevice, pprofDir} from '@/constants/platform' import openURL from '@/util/open-url' import * as Z from '@/util/zustand' import {RPCError} from '@/util/errors' -import * as Tabs from '../tabs' +import * as Tabs from '@/constants/tabs' import logger from '@/logger' -import {clearModals, navigateAppend, switchTab} from '../router2/util' -import {storeRegistry} from '../store-registry' -import {processorProfileInProgressKey, traceInProgressKey} from './util' +import {clearModals, navigateAppend, switchTab} from '@/constants/router2' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' +import {useWaitingState} from '@/stores/waiting' +import {processorProfileInProgressKey, traceInProgressKey} from '@/constants/settings' +import type {PhoneRow} from '@/stores/settings-phone' -export * from './util' +export * from '@/constants/settings' type Store = T.Immutable<{ checkPasswordIsCorrect?: boolean @@ -31,14 +33,18 @@ const initialStore: Store = { export interface State extends Store { dispatch: { checkPassword: (password: string) => void - dbNuke: () => void clearLogs: () => void + dbNuke: () => void + defer: { + getSettingsPhonePhones: () => undefined | ReadonlyMap + onSettingsEmailNotifyEmailsChanged: (list: ReadonlyArray) => void + onSettingsPhoneSetNumbers: (phoneNumbers?: ReadonlyArray) => void + } deleteAccountForever: (passphrase?: string) => void loadLockdownMode: () => void loadProxyData: () => void loadSettings: () => void loginBrowserViaWebAuthToken: () => void - onEngineIncomingImpl: (action: EngineGen.Actions) => void processorProfile: (durationSeconds: number) => void resetCheckPassword: () => void resetState: 'default' @@ -51,14 +57,14 @@ export interface State extends Store { } let maybeLoadAppLinkOnce = false -export const useSettingsState = Z.createZustand(set => { +export const useSettingsState = Z.createZustand((set, get) => { const maybeLoadAppLink = () => { - const phones = storeRegistry.getState('settings-phone').phones + const phones = get().dispatch.defer.getSettingsPhonePhones() if (!phones || phones.size > 0) { return } - if (maybeLoadAppLinkOnce || !storeRegistry.getState('config').startup.link.endsWith('/phone-app')) { + if (maybeLoadAppLinkOnce || !useConfigState.getState().startup.link.endsWith('/phone-app')) { return } maybeLoadAppLinkOnce = true @@ -95,9 +101,20 @@ export const useSettingsState = Z.createZustand(set => { } ignorePromise(f()) }, + defer: { + getSettingsPhonePhones: () => { + throw new Error('getSettingsPhonePhones not implemented') + }, + onSettingsEmailNotifyEmailsChanged: () => { + throw new Error('onSettingsEmailNotifyEmailsChanged not implemented') + }, + onSettingsPhoneSetNumbers: () => { + throw new Error('onSettingsPhoneSetNumbers not implemented') + }, + }, deleteAccountForever: passphrase => { const f = async () => { - const username = storeRegistry.getState('current-user').username + const username = useCurrentUserState.getState().username if (!username) { throw new Error('Unable to delete account: no username set') @@ -108,7 +125,7 @@ export const useSettingsState = Z.createZustand(set => { } await T.RPCGen.loginAccountDeleteRpcPromise({passphrase}, S.waitingKeySettingsGeneric) - storeRegistry.getState('config').dispatch.setJustDeletedSelf(username) + useConfigState.getState().dispatch.setJustDeletedSelf(username) clearModals() navigateAppend(Tabs.loginTab) } @@ -116,7 +133,7 @@ export const useSettingsState = Z.createZustand(set => { }, loadLockdownMode: () => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } try { @@ -148,7 +165,7 @@ export const useSettingsState = Z.createZustand(set => { }, loadSettings: () => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } try { @@ -156,10 +173,8 @@ export const useSettingsState = Z.createZustand(set => { undefined, S.waitingKeySettingsLoadSettings ) - storeRegistry - .getState('settings-email') - .dispatch.notifyEmailAddressEmailsChanged(settings.emails ?? []) - storeRegistry.getState('settings-phone').dispatch.setNumbers(settings.phoneNumbers ?? undefined) + get().dispatch.defer.onSettingsEmailNotifyEmailsChanged(settings.emails ?? []) + get().dispatch.defer.onSettingsPhoneSetNumbers(settings.phoneNumbers ?? undefined) maybeLoadAppLink() } catch (error) { if (!(error instanceof RPCError)) { @@ -178,41 +193,13 @@ export const useSettingsState = Z.createZustand(set => { } ignorePromise(f()) }, - onEngineIncomingImpl: action => { - switch (action.type) { - case EngineGen.keybase1NotifyEmailAddressEmailAddressVerified: - logger.info('email verified') - storeRegistry - .getState('settings-email') - .dispatch.notifyEmailVerified(action.payload.params.emailAddress) - break - case EngineGen.keybase1NotifyUsersPasswordChanged: { - const randomPW = action.payload.params.state === T.RPCGen.PassphraseState.random - storeRegistry.getState('settings-password').dispatch.notifyUsersPasswordChanged(randomPW) - break - } - case EngineGen.keybase1NotifyPhoneNumberPhoneNumbersChanged: { - const {list} = action.payload.params - storeRegistry - .getState('settings-phone') - .dispatch.notifyPhoneNumberPhoneNumbersChanged(list ?? undefined) - break - } - case EngineGen.keybase1NotifyEmailAddressEmailsChanged: { - const list = action.payload.params.list ?? [] - storeRegistry.getState('settings-email').dispatch.notifyEmailAddressEmailsChanged(list) - break - } - default: - } - }, processorProfile: profileDurationSeconds => { const f = async () => { await T.RPCGen.pprofLogProcessorProfileRpcPromise({ logDirForMobile: pprofDir, profileDurationSeconds, }) - const {decrement, increment} = storeRegistry.getState('waiting').dispatch + const {decrement, increment} = useWaitingState.getState().dispatch increment(processorProfileInProgressKey) await timeoutPromise(profileDurationSeconds * 1_000) decrement(processorProfileInProgressKey) @@ -232,7 +219,7 @@ export const useSettingsState = Z.createZustand(set => { }, setLockdownMode: enabled => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } try { @@ -270,7 +257,7 @@ export const useSettingsState = Z.createZustand(set => { logDirForMobile: pprofDir, traceDurationSeconds: durationSeconds, }) - const {decrement, increment} = storeRegistry.getState('waiting').dispatch + const {decrement, increment} = useWaitingState.getState().dispatch increment(traceInProgressKey) await timeoutPromise(durationSeconds * 1_000) decrement(traceInProgressKey) diff --git a/shared/constants/signup/index.tsx b/shared/stores/signup.tsx similarity index 90% rename from shared/constants/signup/index.tsx rename to shared/stores/signup.tsx index d5526f28da51..3d35852a6978 100644 --- a/shared/constants/signup/index.tsx +++ b/shared/stores/signup.tsx @@ -1,15 +1,15 @@ -import * as Platforms from '../platform' -import {ignorePromise} from '../utils' -import * as S from '../strings' +import * as Platforms from '@/constants/platform' +import {ignorePromise} from '@/constants/utils' +import * as S from '@/constants/strings' import * as EngineGen from '@/actions/engine-gen-gen' -import * as T from '../types' +import * as T from '@/constants/types' import * as Z from '@/util/zustand' import logger from '@/logger' import trim from 'lodash/trim' import {RPCError} from '@/util/errors' import {isValidEmail, isValidName, isValidUsername} from '@/util/simple-validators' -import {navigateAppend, navigateUp} from '../router2/util' -import {storeRegistry} from '../store-registry' +import {navigateAppend, navigateUp} from '@/constants/router2' +import {useConfigState} from '@/stores/config' type Store = T.Immutable<{ devicename: string @@ -45,6 +45,10 @@ const initialStore: Store = { export interface State extends Store { dispatch: { + defer: { + onEditEmail?: (p: {email: string; makeSearchable: boolean}) => void + onShowPermissionsPrompt?: (p: {justSignedUp?: boolean}) => void + } checkDeviceName: (devicename: string) => void checkInviteCode: () => void checkUsername: (username: string) => void @@ -80,7 +84,7 @@ export const useSignupState = Z.createZustand((set, get) => { } try { - storeRegistry.getState('push').dispatch.showPermissionsPrompt({justSignedUp: true}) + get().dispatch.defer.onShowPermissionsPrompt?.({justSignedUp: true}) await T.RPCGen.signupSignupRpcListener({ customResponseIncomingCallMap: { @@ -121,7 +125,7 @@ export const useSignupState = Z.createZustand((set, get) => { } // If the email was set to be visible during signup, we need to set that with a separate RPC. if (noErrors() && get().emailVisible) { - storeRegistry.getState('settings-email').dispatch.editEmail({email: get().email, makeSearchable: true}) + get().dispatch.defer.onEditEmail?.({email: get().email, makeSearchable: true}) } } catch (_error) { if (_error instanceof RPCError) { @@ -130,7 +134,7 @@ export const useSignupState = Z.createZustand((set, get) => { s.signupError = error }) navigateAppend('signupError') - storeRegistry.getState('push').dispatch.showPermissionsPrompt({justSignedUp: false}) + get().dispatch.defer.onShowPermissionsPrompt?.({justSignedUp: false}) } } } @@ -228,6 +232,14 @@ export const useSignupState = Z.createZustand((set, get) => { s.justSignedUpEmail = '' }) }, + defer: { + onEditEmail: () => { + throw new Error('onEditEmail not implemented') + }, + onShowPermissionsPrompt: () => { + throw new Error('onShowPermissionsPrompt not implemented') + }, + }, goBackAndClearErrors: () => { set(s => { s.devicenameError = '' @@ -255,7 +267,7 @@ export const useSignupState = Z.createZustand((set, get) => { }) const f = async () => { // If we're logged in, we're coming from the user switcher; log out first to prevent the service from getting out of sync with the GUI about our logged-in-ness - if (storeRegistry.getState('config').loggedIn) { + if (useConfigState.getState().loggedIn) { await T.RPCGen.loginLogoutRpcPromise({force: false, keepSecrets: true}) } try { diff --git a/shared/stores/store-registry.tsx b/shared/stores/store-registry.tsx new file mode 100644 index 000000000000..dbae6e710f48 --- /dev/null +++ b/shared/stores/store-registry.tsx @@ -0,0 +1,157 @@ +// used to allow non-circular cross-calls between stores +// ONLY for zustand stores +import type * as T from '@/constants/types' +import type * as ConvoStateType from '@/stores/convostate' +import type {ConvoState} from '@/stores/convostate' +import type {State as ChatState, useChatState} from '@/stores/chat2' +import type {State as DaemonState, useDaemonState} from '@/stores/daemon' +import type {State as FSState, useFSState} from '@/stores/fs' +import type {State as PeopleState, usePeopleState} from '@/stores/people' +import type {State as ProfileState, useProfileState} from '@/stores/profile' +import type {State as ProvisionState, useProvisionState} from '@/stores/provision' +import type {State as PushState, usePushState} from '@/stores/push' +import type { + State as RecoverPasswordState, + useState as useRecoverPasswordState, +} from '@/stores/recover-password' +import type {State as SettingsState, useSettingsState} from '@/stores/settings' +import type {State as SettingsEmailState, useSettingsEmailState} from '@/stores/settings-email' +import type {State as SettingsPhoneState, useSettingsPhoneState} from '@/stores/settings-phone' +import type {State as SignupState, useSignupState} from '@/stores/signup' +import type {State as TeamsState, useTeamsState} from '@/stores/teams' +import type {State as Tracker2State, useTrackerState} from '@/stores/tracker2' +import type {State as UsersState, useUsersState} from '@/stores/users' + +type StoreName = + | 'chat' + | 'daemon' + | 'fs' + | 'people' + | 'profile' + | 'provision' + | 'push' + | 'recover-password' + | 'settings' + | 'settings-email' + | 'settings-phone' + | 'signup' + | 'teams' + | 'tracker2' + | 'users' + +type StoreStates = { + chat: ChatState + daemon: DaemonState + fs: FSState + people: PeopleState + profile: ProfileState + provision: ProvisionState + push: PushState + 'recover-password': RecoverPasswordState + settings: SettingsState + 'settings-email': SettingsEmailState + 'settings-phone': SettingsPhoneState + signup: SignupState + teams: TeamsState + tracker2: Tracker2State + users: UsersState +} + +type StoreHooks = { + chat: typeof useChatState + daemon: typeof useDaemonState + fs: typeof useFSState + people: typeof usePeopleState + profile: typeof useProfileState + provision: typeof useProvisionState + push: typeof usePushState + 'recover-password': typeof useRecoverPasswordState + settings: typeof useSettingsState + 'settings-email': typeof useSettingsEmailState + 'settings-phone': typeof useSettingsPhoneState + signup: typeof useSignupState + teams: typeof useTeamsState + tracker2: typeof useTrackerState + users: typeof useUsersState +} + +class StoreRegistry { + getStore(storeName: T): StoreHooks[T] { + /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return */ + switch (storeName) { + case 'chat': { + const {useChatState} = require('@/stores/chat2') + return useChatState + } + case 'daemon': { + const {useDaemonState} = require('@/stores/daemon') + return useDaemonState + } + case 'fs': { + const {useFSState} = require('@/stores/fs') + return useFSState + } + case 'people': { + const {usePeopleState} = require('@/stores/people') + return usePeopleState + } + case 'profile': { + const {useProfileState} = require('@/stores/profile') + return useProfileState + } + case 'provision': { + const {useProvisionState} = require('@/stores/provision') + return useProvisionState + } + case 'push': { + const {usePushState} = require('@/stores/push') + return usePushState + } + case 'recover-password': { + const {useState} = require('@/stores/recover-password') + return useState + } + case 'settings': { + const {useSettingsState} = require('@/stores/settings') + return useSettingsState + } + case 'settings-email': { + const {useSettingsEmailState} = require('@/stores/settings-email') + return useSettingsEmailState + } + case 'settings-phone': { + const {useSettingsPhoneState} = require('@/stores/settings-phone') + return useSettingsPhoneState + } + case 'signup': { + const {useSignupState} = require('@/stores/signup') + return useSignupState + } + case 'teams': { + const {useTeamsState} = require('@/stores/teams') + return useTeamsState + } + case 'tracker2': { + const {useTrackerState} = require('@/stores/tracker2') + return useTrackerState + } + case 'users': { + const {useUsersState} = require('@/stores/users') + return useUsersState + } + default: + throw new Error(`Unknown store: ${storeName}`) + } + } + + getState(storeName: T): StoreStates[T] { + return this.getStore(storeName).getState() as StoreStates[T] + } + + getConvoState(id: T.Chat.ConversationIDKey): ConvoState { + const {getConvoState} = require('@/stores/convostate') as typeof ConvoStateType + return getConvoState(id) + } +} + +export const storeRegistry = new StoreRegistry() diff --git a/shared/constants/team-building/index.tsx b/shared/stores/team-building.tsx similarity index 82% rename from shared/constants/team-building/index.tsx rename to shared/stores/team-building.tsx index 0d838f7c2332..fb68799d16c0 100644 --- a/shared/constants/team-building/index.tsx +++ b/shared/stores/team-building.tsx @@ -1,6 +1,5 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' -import * as Router2 from '../router2' +import * as T from '@/constants/types' +import {ignorePromise} from '@/constants/utils' import * as React from 'react' import * as Z from '@/util/zustand' import logger from '@/logger' @@ -11,10 +10,10 @@ import {serviceIdFromString} from '@/util/platforms' import {type StoreApi, type UseBoundStore, useStore} from 'zustand' import {validateEmailAddress} from '@/util/email-address' import {registerDebugClear} from '@/util/debug' -import {searchWaitingKey} from './utils' -import {navigateUp} from '../router2/util' -import {storeRegistry} from '../store-registry' -export {allServices, selfToUser, searchWaitingKey} from './utils' +import {searchWaitingKey} from '@/constants/strings' +import {navigateUp, getModalStack} from '@/constants/router2' +export {allServices, selfToUser} from '@/constants/team-building' +export {searchWaitingKey} from '@/constants/strings' type Store = T.Immutable<{ namespace: T.TB.AllowedNamespace @@ -54,6 +53,16 @@ export interface State extends Store { cancelTeamBuilding: () => void changeSendNotification: (sendNotification: boolean) => void closeTeamBuilding: () => void + defer: { + onAddMembersWizardPushMembers: (members: Array) => void + onFinishedTeamBuildingChat: (users: ReadonlySet) => void + onFinishedTeamBuildingCrypto: (users: ReadonlySet) => void + onGetSettingsContactsImportEnabled: () => boolean | undefined + onGetSettingsContactsUserCountryCode: () => string | undefined + onShowUserProfile: (username: string) => void + onUsersGetBlockState: (usernames: ReadonlyArray) => void + onUsersUpdates: (infos: ReadonlyArray<{name: string; info: Partial}>) => void + } fetchUserRecs: () => void finishTeamBuilding: () => void finishedTeamBuilding: () => void @@ -271,7 +280,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { // we want the first item for (const user of teamSoFar) { const username = user.serviceMap.keybase || user.id - storeRegistry.getState('profile').dispatch.showUserProfile(username) + get().dispatch.defer.onShowUserProfile(username) break } }, 100) @@ -291,13 +300,39 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { }) }, closeTeamBuilding: () => { - const modals = Router2.getModalStack() + const modals = getModalStack() const routeNames = [...namespaceToRoute.values()] const routeName = modals.at(-1)?.name if (routeNames.includes(routeName ?? '')) { navigateUp() } }, + defer: { + onAddMembersWizardPushMembers: (_members: Array) => { + throw new Error('onAddMembersWizardPushMembers not properly initialized') + }, + onFinishedTeamBuildingChat: (_users: ReadonlySet) => { + throw new Error('onFinishedTeamBuildingChat not properly initialized') + }, + onFinishedTeamBuildingCrypto: (_users: ReadonlySet) => { + throw new Error('onFinishedTeamBuildingCrypto not properly initialized') + }, + onGetSettingsContactsImportEnabled: () => { + throw new Error('onGetSettingsContactsImportEnabled not properly initialized') + }, + onGetSettingsContactsUserCountryCode: () => { + throw new Error('onGetSettingsContactsUserCountryCode not properly initialized') + }, + onShowUserProfile: (_username: string) => { + throw new Error('onShowUserProfile not properly initialized') + }, + onUsersGetBlockState: (_usernames: ReadonlyArray) => { + throw new Error('onUsersGetBlockState not properly initialized') + }, + onUsersUpdates: (_infos: ReadonlyArray<{name: string; info: Partial}>) => { + throw new Error('onUsersUpdates not properly initialized') + }, + }, fetchUserRecs: () => { const includeContacts = get().namespace === 'chat2' const f = async () => { @@ -313,7 +348,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { const contacts = contactRes.map(contactToUser) let suggestions = suggestionRes.map(interestingPersonToUser) const expectingContacts = - storeRegistry.getState('settings-contacts').importEnabled && includeContacts + get().dispatch.defer.onGetSettingsContactsImportEnabled() && includeContacts if (expectingContacts) { suggestions = suggestions.slice(0, 10) } @@ -336,11 +371,9 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { get().dispatch.closeTeamBuilding() const {teamSoFar} = get() if (get().namespace === 'teams') { - storeRegistry - .getState('teams') - .dispatch.addMembersWizardPushMembers( - [...teamSoFar].map(user => ({assertion: user.id, role: 'writer'})) - ) + get().dispatch.defer.onAddMembersWizardPushMembers( + [...teamSoFar].map(user => ({assertion: user.id, role: 'writer'})) + ) get().dispatch.finishedTeamBuilding() } }, @@ -360,11 +393,11 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { const {finishedTeam, namespace} = get() switch (namespace) { case 'crypto': { - storeRegistry.getState('crypto').dispatch.onTeamBuildingFinished(finishedTeam) + get().dispatch.defer.onFinishedTeamBuildingCrypto(finishedTeam) break } case 'chat2': { - storeRegistry.getState('chat').dispatch.onTeamBuildingFinished(finishedTeam) + get().dispatch.defer.onFinishedTeamBuildingChat(finishedTeam) break } default: @@ -415,7 +448,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { let users: typeof _users if (selectedService === 'keybase') { // If we are on Keybase tab, do additional search if query is phone/email. - const userRegion = storeRegistry.getState('settings-contacts').userCountryCode + const userRegion = get().dispatch.defer.onGetSettingsContactsUserCountryCode() users = await specialContactSearch(_users, searchQuery, userRegion) } else { users = _users @@ -432,7 +465,7 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } return arr }, new Array<{info: {fullname: string}; name: string}>()) - storeRegistry.getState('users').dispatch.updates(updates) + get().dispatch.defer.onUsersUpdates(updates) const blocks = users.reduce((arr, {serviceMap}) => { const {keybase} = serviceMap if (keybase) { @@ -440,7 +473,9 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } return arr }, new Array()) - blocks.length && storeRegistry.getState('users').dispatch.getBlockState(blocks) + if (blocks.length) { + get().dispatch.defer.onUsersGetBlockState(blocks) + } } ignorePromise(f()) }, @@ -477,6 +512,9 @@ export const createTBStore = (namespace: T.TB.AllowedNamespace) => { return next } +export const getTBStore = (namespace: T.TB.AllowedNamespace): State => + createTBStore(namespace).getState() + const Context = React.createContext(null) type TBProviderProps = React.PropsWithChildren<{namespace: T.TB.AllowedNamespace}> diff --git a/shared/constants/teams/index.tsx b/shared/stores/teams.tsx similarity index 96% rename from shared/constants/teams/index.tsx rename to shared/stores/teams.tsx index d221afa1fddd..ac4b61953748 100644 --- a/shared/constants/teams/index.tsx +++ b/shared/stores/teams.tsx @@ -1,6 +1,6 @@ -import * as S from '../strings' -import {ignorePromise, wrapErrors} from '../utils' -import * as T from '../types' +import * as S from '@/constants/strings' +import {ignorePromise, wrapErrors} from '@/constants/utils' +import * as T from '@/constants/types' import * as EngineGen from '@/actions/engine-gen-gen' import { getVisibleScreen, @@ -9,19 +9,23 @@ import { navigateUp, navUpToScreen, navToProfile, -} from '../router2/util' +} from '@/constants/router2' import * as Z from '@/util/zustand' import invert from 'lodash/invert' import logger from '@/logger' import openSMS from '@/util/sms' import {RPCError, logError} from '@/util/errors' -import {isMobile, isPhone} from '../platform' +import {isMobile, isPhone} from '@/constants/platform' import {mapGetEnsureValue} from '@/util/map' -import {bodyToJSON} from '../rpc-utils' +import {bodyToJSON} from '@/constants/rpc-utils' import {fixCrop} from '@/util/crop' -import {storeRegistry} from '../store-registry' -import * as Util from './util' -import {getTab} from '../router2/util' +import {getTBStore} from '@/stores/team-building' +import {storeRegistry} from '@/stores/store-registry' +import {useConfigState} from '@/stores/config' +import {type useChatState} from '@/stores/chat2' +import {useCurrentUserState} from '@/stores/current-user' +import * as Util from '@/constants/teams' +import {getTab} from '@/constants/router2' export { baseRetentionPolicies, @@ -31,7 +35,7 @@ export { teamRoleByEnum, retentionPolicyToServiceRetentionPolicy, userIsRoleInTeamWithInfo, -} from './util' +} from '@/constants/teams' export const teamRoleTypes = ['reader', 'writer', 'admin', 'owner'] as const @@ -304,7 +308,7 @@ export const getDisabledReasonsForRolePicker = ( theyAreOwner = membersToModify.some(username => members.get(username)?.type === 'owner') } - const myUsername = storeRegistry.getState('current-user').username + const myUsername = useCurrentUserState.getState().username const you = members.get(myUsername) // Fallback to the lowest role, although this shouldn't happen const yourRole = you?.type ?? 'reader' @@ -862,6 +866,13 @@ const initialStore: Store = { export interface State extends Store { dispatch: { + defer: { + onChatNavigateToInbox?: (allowSwitchTab?: boolean) => void + onChatPreviewConversation?: ( + p: Parameters['dispatch']['previewConversation']>[0] + ) => void + onUsersUpdates?: (updates: ReadonlyArray<{name: string; info: Partial}>) => void + } dynamic: { respondToInviteLink?: (accept: boolean) => void } @@ -1044,7 +1055,7 @@ export interface State extends Store { sendChatNotification: boolean, crop?: T.RPCGen.ImageCropRect ) => void - updateTeamRetentionPolicy: (metas: Array) => void + updateTeamRetentionPolicy: (metas: ReadonlyArray) => void } } @@ -1220,7 +1231,7 @@ export const useTeamsState = Z.createZustand((set, get) => { ) if (res.notAdded && res.notAdded.length > 0) { const usernames = res.notAdded.map(elem => elem.username) - storeRegistry.getTBStore('teams').dispatch.finishedTeamBuilding() + getTBStore('teams').dispatch.finishedTeamBuilding() navigateAppend({ props: {source: 'teamAddSomeFailed', usernames}, selected: 'contactRestricted', @@ -1232,7 +1243,7 @@ export const useTeamsState = Z.createZustand((set, get) => { s.errorInAddToTeam = '' }) if (fromTeamBuilder) { - storeRegistry.getTBStore('teams').dispatch.finishedTeamBuilding() + getTBStore('teams').dispatch.finishedTeamBuilding() } } catch (error) { if (!(error instanceof RPCError)) { @@ -1244,7 +1255,7 @@ export const useTeamsState = Z.createZustand((set, get) => { ?.filter(elem => elem?.key === 'usernames') .map(elem => elem?.value) const usernames = users?.[0]?.split(',') ?? [] - storeRegistry.getTBStore('teams').dispatch.finishedTeamBuilding() + getTBStore('teams').dispatch.finishedTeamBuilding() navigateAppend({ props: {source: 'teamAddAllFailed', usernames}, selected: 'contactRestricted', @@ -1258,7 +1269,7 @@ export const useTeamsState = Z.createZustand((set, get) => { }) // TODO this should not error on member already in team if (fromTeamBuilder) { - storeRegistry.getTBStore('teams').dispatch.setError(msg) + getTBStore('teams').dispatch.setError(msg) } } } @@ -1423,7 +1434,7 @@ export const useTeamsState = Z.createZustand((set, get) => { get().dispatch.loadTeamChannelList(teamID) // Select the new channel, and switch to the chat tab. if (navToChatOnSuccess) { - storeRegistry.getState('chat').dispatch.previewConversation({ + get().dispatch.defer.onChatPreviewConversation?.({ channelname, conversationIDKey: newConversationIDKey, reason: 'newChannel', @@ -1493,16 +1504,17 @@ export const useTeamsState = Z.createZustand((set, get) => { if (fromChat) { clearModals() - const {previewConversation, navigateToInbox} = storeRegistry.getState('chat').dispatch - navigateToInbox() - previewConversation({channelname: 'general', reason: 'convertAdHoc', teamname}) + get().dispatch.defer.onChatNavigateToInbox?.() + get().dispatch.defer.onChatPreviewConversation?.({ + channelname: 'general', + reason: 'convertAdHoc', + teamname, + }) } else { clearModals() navigateAppend({props: {teamID}, selected: 'team'}) if (isMobile) { - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {createdTeam: true, teamID}, selected: 'profileEditAvatar'}) + navigateAppend({props: {createdTeam: true, teamID}, selected: 'profileEditAvatar'}) } } } catch (error) { @@ -1519,7 +1531,7 @@ export const useTeamsState = Z.createZustand((set, get) => { set(s => { s.errorInTeamCreation = '' }) - const me = storeRegistry.getState('current-user').username + const me = useCurrentUserState.getState().username const participantInfo = storeRegistry.getConvoState(conversationIDKey).participants // exclude bots from the newly created team, they can be added back later. const participants = participantInfo.name.filter(p => p !== me) // we will already be in as 'owner' @@ -1529,6 +1541,22 @@ export const useTeamsState = Z.createZustand((set, get) => { })) get().dispatch.createNewTeam(teamname, false, true, {sendChatNotification: true, users}) }, + defer: { + onChatNavigateToInbox: (_allowSwitchTab?: boolean) => { + throw new Error('onChatNavigateToInbox not implemented') + }, + onChatPreviewConversation: (_p: { + channelname?: string + conversationIDKey?: T.Chat.ConversationIDKey + reason?: string + teamname?: string + }) => { + throw new Error('onChatPreviewConversation not implemented') + }, + onUsersUpdates: (_updates: ReadonlyArray<{name: string; info: Partial}>) => { + throw new Error('onUsersUpdates not implemented') + }, + }, deleteChannelConfirmed: (teamID, conversationIDKey) => { const f = async () => { // channelName is only needed for confirmation, so since we handle @@ -1725,7 +1753,7 @@ export const useTeamsState = Z.createZustand((set, get) => { set(s => { s.teamIDToMembers.set(teamID, members) }) - storeRegistry.getState('users').dispatch.updates( + get().dispatch.defer.onUsersUpdates?.( [...members.values()].map(m => ({ info: {fullname: m.fullName}, name: m.username, @@ -1790,8 +1818,8 @@ export const useTeamsState = Z.createZustand((set, get) => { } const f = async () => { - const username = storeRegistry.getState('current-user').username - const loggedIn = storeRegistry.getState('config').loggedIn + const username = useCurrentUserState.getState().username + const loggedIn = useConfigState.getState().loggedIn if (!username || !loggedIn) { logger.warn('getTeams while logged out') return @@ -2029,9 +2057,7 @@ export const useTeamsState = Z.createZustand((set, get) => { ) logger.info(`leaveTeam: left ${teamname} successfully`) clearModals() - storeRegistry - .getState('router') - .dispatch.navUpToScreen(context === 'chat' ? 'chatRoot' : 'teamsRoot') + navUpToScreen(context === 'chat' ? 'chatRoot' : 'teamsRoot') get().dispatch.getTeams() } catch (error) { if (error instanceof RPCError) { @@ -2163,9 +2189,7 @@ export const useTeamsState = Z.createZustand((set, get) => { }) }, manageChatChannels: teamID => { - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {teamID}, selected: 'teamAddToChannels'}) + navigateAppend({props: {teamID}, selected: 'teamAddToChannels'}) }, notifyTeamTeamRoleMapChanged: (newVersion: number) => { const loadedVersion = get().teamRoleMap.loadedVersion @@ -2307,7 +2331,7 @@ export const useTeamsState = Z.createZustand((set, get) => { break case EngineGen.keybase1NotifyBadgesBadgeState: { const {badgeState} = action.payload.params - const loggedIn = storeRegistry.getState('config').loggedIn + const loggedIn = useConfigState.getState().loggedIn if (loggedIn) { const deletedTeams = badgeState.deletedTeams || [] const newTeams = new Set(badgeState.newTeams || []) @@ -2538,14 +2562,14 @@ export const useTeamsState = Z.createZustand((set, get) => { const convID = T.Chat.keyToConversationID(conversationIDKey) await T.RPCChat.localJoinConversationByIDLocalRpcPromise({convID}, waitingKey) } catch (error) { - storeRegistry.getState('config').dispatch.setGlobalError(error) + useConfigState.getState().dispatch.setGlobalError(error) } } else { try { const convID = T.Chat.keyToConversationID(conversationIDKey) await T.RPCChat.localLeaveConversationLocalRpcPromise({convID}, waitingKey) } catch (error) { - storeRegistry.getState('config').dispatch.setGlobalError(error) + useConfigState.getState().dispatch.setGlobalError(error) } } } @@ -2658,14 +2682,14 @@ export const useTeamsState = Z.createZustand((set, get) => { teamID, }) } catch (payload) { - storeRegistry.getState('config').dispatch.setGlobalError(payload) + useConfigState.getState().dispatch.setGlobalError(payload) } } if (ignoreAccessRequests !== settings.ignoreAccessRequests) { try { await T.RPCGen.teamsSetTarsDisabledRpcPromise({disabled: settings.ignoreAccessRequests, teamID}) } catch (payload) { - storeRegistry.getState('config').dispatch.setGlobalError(payload) + useConfigState.getState().dispatch.setGlobalError(payload) } } if (publicityAnyMember !== settings.publicityAnyMember) { @@ -2675,7 +2699,7 @@ export const useTeamsState = Z.createZustand((set, get) => { teamID, }) } catch (payload) { - storeRegistry.getState('config').dispatch.setGlobalError(payload) + useConfigState.getState().dispatch.setGlobalError(payload) } } if (publicityMember !== settings.publicityMember) { @@ -2685,14 +2709,14 @@ export const useTeamsState = Z.createZustand((set, get) => { teamID, }) } catch (payload) { - storeRegistry.getState('config').dispatch.setGlobalError(payload) + useConfigState.getState().dispatch.setGlobalError(payload) } } if (publicityTeam !== settings.publicityTeam) { try { await T.RPCGen.teamsSetTeamShowcaseRpcPromise({isShowcased: settings.publicityTeam, teamID}) } catch (payload) { - storeRegistry.getState('config').dispatch.setGlobalError(payload) + useConfigState.getState().dispatch.setGlobalError(payload) } } } @@ -2871,13 +2895,9 @@ export const useTeamsState = Z.createZustand((set, get) => { logger.info(`team="${teamname}" cannot be loaded:`, err) // navigate to team page for team we're not in logger.info(`showing external team page, join=${join}`) - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {teamname}, selected: 'teamExternalTeam'}) + navigateAppend({props: {teamname}, selected: 'teamExternalTeam'}) if (join) { - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {initialTeamname: teamname}, selected: 'teamJoinTeamDialog'}) + navigateAppend({props: {initialTeamname: teamname}, selected: 'teamJoinTeamDialog'}) } return } @@ -2899,9 +2919,7 @@ export const useTeamsState = Z.createZustand((set, get) => { return } } - storeRegistry - .getState('router') - .dispatch.navigateAppend({props: {initialTab, teamID}, selected: 'team'}) + navigateAppend({props: {initialTab, teamID}, selected: 'team'}) if (addMembers) { navigateAppend({ props: {namespace: 'teams', teamID, title: ''}, diff --git a/shared/constants/tracker2/index.tsx b/shared/stores/tracker2.tsx similarity index 93% rename from shared/constants/tracker2/index.tsx rename to shared/stores/tracker2.tsx index 8ea35a1b511e..8b9475cb04a8 100644 --- a/shared/constants/tracker2/index.tsx +++ b/shared/stores/tracker2.tsx @@ -1,13 +1,13 @@ -import * as S from '../strings' +import * as S from '@/constants/strings' import * as EngineGen from '@/actions/engine-gen-gen' -import {generateGUIID, ignorePromise} from '../utils' +import {generateGUIID, ignorePromise} from '@/constants/utils' import * as Z from '@/util/zustand' import logger from '@/logger' -import * as T from '../types' +import * as T from '@/constants/types' import {RPCError} from '@/util/errors' import {mapGetEnsureValue} from '@/util/map' -import {navigateAppend, navigateUp} from '../router2/util' -import {storeRegistry} from '../store-registry' +import {navigateAppend, navigateUp} from '@/constants/router2' +import {useCurrentUserState} from '@/stores/current-user' export const noDetails: T.Tracker.Details = { assertions: new Map(), @@ -163,6 +163,10 @@ const initialStore: Store = { export interface State extends Store { dispatch: { + defer: { + onShowUserProfile?: (username: string) => void + onUsersUpdates?: (updates: ReadonlyArray<{name: string; info: Partial}>) => void + } changeFollow: (guiID: string, follow: boolean) => void closeTracker: (guiID: string) => void getProofSuggestions: () => void @@ -227,6 +231,14 @@ export const useTrackerState = Z.createZustand((set, get) => { s.showTrackerSet.delete(username) }) }, + defer: { + onShowUserProfile: () => { + throw new Error('onShowUserProfile not implemented') + }, + onUsersUpdates: () => { + throw new Error('onUsersUpdates not implemented') + }, + }, getProofSuggestions: () => { const f = async () => { try { @@ -290,13 +302,13 @@ export const useTrackerState = Z.createZustand((set, get) => { } else if (error.code === T.RPCGen.StatusCode.scnotfound) { // we're on the profile page for a user that does not exist. Currently the only way // to get here is with an invalid link or deeplink. - storeRegistry - .getState('deeplinks') - .dispatch.setLinkError( - `You followed a profile link for a user (${assertion}) that does not exist.` - ) navigateUp() - navigateAppend('keybaseLinkError') + navigateAppend({ + props: { + error: `You followed a profile link for a user (${assertion}) that does not exist.`, + }, + selected: 'keybaseLinkError', + }) } // hooked into reloadable logger.error(`Error loading profile: ${error.message}`) @@ -319,9 +331,9 @@ export const useTrackerState = Z.createZustand((set, get) => { d.followersCount = d.followers.size }) if (fs.users) { - storeRegistry - .getState('users') - .dispatch.updates(fs.users.map(u => ({info: {fullname: u.fullName}, name: u.username}))) + get().dispatch.defer.onUsersUpdates?.( + fs.users.map(u => ({info: {fullname: u.fullName}, name: u.username})) + ) } } catch (error) { if (error instanceof RPCError) { @@ -345,9 +357,9 @@ export const useTrackerState = Z.createZustand((set, get) => { d.followingCount = d.following.size }) if (fs.users) { - storeRegistry - .getState('users') - .dispatch.updates(fs.users.map(u => ({info: {fullname: u.fullName}, name: u.username}))) + get().dispatch.defer.onUsersUpdates?.( + fs.users.map(u => ({info: {fullname: u.fullName}, name: u.username})) + ) } } catch (error) { if (error instanceof RPCError) { @@ -435,8 +447,7 @@ export const useTrackerState = Z.createZustand((set, get) => { ) d.hidFromFollowers = hidFromFollowers }) - username && - storeRegistry.getState('users').dispatch.updates([{info: {fullname: card.fullName}, name: username}]) + username && get().dispatch.defer.onUsersUpdates?.([{info: {fullname: card.fullName}, name: username}]) }, notifyReset: guiID => { set(s => { @@ -531,11 +542,11 @@ export const useTrackerState = Z.createZustand((set, get) => { } // if we mutated somehow reload ourselves and reget the suggestions case EngineGen.keybase1NotifyUsersUserChanged: { - if (storeRegistry.getState('current-user').uid !== action.payload.params.uid) { + if (useCurrentUserState.getState().uid !== action.payload.params.uid) { return } get().dispatch.load({ - assertion: storeRegistry.getState('current-user').username, + assertion: useCurrentUserState.getState().username, forceDisplay: false, fromDaemon: false, guiID: generateGUIID(), @@ -595,7 +606,7 @@ export const useTrackerState = Z.createZustand((set, get) => { }) if (!skipNav) { // go to profile page - storeRegistry.getState('profile').dispatch.showUserProfile(username) + get().dispatch.defer.onShowUserProfile?.(username) } }, updateResult: (guiID, result, reason) => { diff --git a/shared/constants/unlock-folders/index.tsx b/shared/stores/unlock-folders.tsx similarity index 86% rename from shared/constants/unlock-folders/index.tsx rename to shared/stores/unlock-folders.tsx index dc4a1d43ca00..d8fa1d8dd573 100644 --- a/shared/constants/unlock-folders/index.tsx +++ b/shared/stores/unlock-folders.tsx @@ -1,10 +1,9 @@ import * as EngineGen from '@/actions/engine-gen-gen' -import * as T from '../types' +import * as T from '@/constants/types' import * as Z from '@/util/zustand' import logger from '@/logger' import {getEngine} from '@/engine/require' -import type {State as ConfigStore} from '../config' -import {storeRegistry} from '../store-registry' +import {useConfigState, type State as ConfigStore} from '@/stores/config' type Store = T.Immutable<{ devices: ConfigStore['unlockFoldersDevices'] @@ -39,7 +38,7 @@ export const useUnlockFoldersState = Z.createZustand((set, _get) => { case EngineGen.keybase1RekeyUIRefresh: { const {problemSetDevices} = action.payload.params logger.info('Asked for rekey') - storeRegistry.getState('config').dispatch.openUnlockFolders(problemSetDevices.devices ?? []) + useConfigState.getState().dispatch.openUnlockFolders(problemSetDevices.devices ?? []) break } case EngineGen.keybase1RekeyUIDelegateRekeyUI: { @@ -49,7 +48,7 @@ export const useUnlockFoldersState = Z.createZustand((set, _get) => { dangling: true, incomingCallMap: { 'keybase.1.rekeyUI.refresh': ({problemSetDevices}) => { - storeRegistry.getState('config').dispatch.openUnlockFolders(problemSetDevices.devices ?? []) + useConfigState.getState().dispatch.openUnlockFolders(problemSetDevices.devices ?? []) }, 'keybase.1.rekeyUI.rekeySendEvent': () => {}, // ignored debug call from daemon }, diff --git a/shared/constants/users/index.tsx b/shared/stores/users.tsx similarity index 96% rename from shared/constants/users/index.tsx rename to shared/stores/users.tsx index 642c3c0f3ec9..618264f110ac 100644 --- a/shared/constants/users/index.tsx +++ b/shared/stores/users.tsx @@ -1,11 +1,11 @@ import * as EngineGen from '@/actions/engine-gen-gen' import * as Z from '@/util/zustand' import logger from '@/logger' -import * as T from '../types' +import * as T from '@/constants/types' import {mapGetEnsureValue} from '@/util/map' -import {ignorePromise} from '../utils' -import {RPCError, isNetworkErr} from '../utils' -import * as S from '../strings' +import {ignorePromise} from '@/constants/utils' +import {RPCError, isNetworkErr} from '@/constants/utils' +import * as S from '@/constants/strings' type Store = T.Immutable<{ blockMap: Map diff --git a/shared/constants/waiting/index.tsx b/shared/stores/waiting.tsx similarity index 94% rename from shared/constants/waiting/index.tsx rename to shared/stores/waiting.tsx index 4ba0a2f8388a..effcb51b5006 100644 --- a/shared/constants/waiting/index.tsx +++ b/shared/stores/waiting.tsx @@ -1,7 +1,8 @@ import type {RPCError} from '@/util/errors' -import type * as T from '../types' +import type * as T from '@/constants/types' import * as Z from '@/util/zustand' +// This store has no dependencies on other stores and is safe to import directly from other stores. const initialStore: T.Waiting.State = { counts: new Map(), errors: new Map(), diff --git a/shared/constants/wallets/index.tsx b/shared/stores/wallets.tsx similarity index 84% rename from shared/constants/wallets/index.tsx rename to shared/stores/wallets.tsx index b6d0b28eff96..0305e954dfd6 100644 --- a/shared/constants/wallets/index.tsx +++ b/shared/stores/wallets.tsx @@ -1,10 +1,10 @@ -import * as T from '../types' -import {ignorePromise} from '../utils' +import * as T from '@/constants/types' +import {ignorePromise} from '@/constants/utils' import * as Z from '@/util/zustand' -import {loadAccountsWaitingKey} from './utils' -import {storeRegistry} from '../store-registry' +import {loadAccountsWaitingKey} from '@/constants/strings' +import {useConfigState} from '@/stores/config' -export {loadAccountsWaitingKey} from './utils' +export {loadAccountsWaitingKey} from '@/constants/strings' export type Account = { accountID: string @@ -33,7 +33,7 @@ export const useState = Z.createZustand((set, get) => { const dispatch: State['dispatch'] = { load: () => { const f = async () => { - if (!storeRegistry.getState('config').loggedIn) { + if (!useConfigState.getState().loggedIn) { return } const res = await T.RPCStellar.localGetWalletAccountsLocalRpcPromise(undefined, [ diff --git a/shared/stores/whats-new.tsx b/shared/stores/whats-new.tsx new file mode 100644 index 000000000000..c32512b33505 --- /dev/null +++ b/shared/stores/whats-new.tsx @@ -0,0 +1,118 @@ +import type * as T from '@/constants/types' +import * as Z from '@/util/zustand' +import {uint8ArrayToString} from 'uint8array-extras' +import {currentVersion, lastVersion, lastLastVersion} from '@/constants/strings' +export {currentVersion, lastVersion, lastLastVersion, keybaseFM} from '@/constants/strings' + +const noVersion: string = '0.0.0' +export {noVersion} + +// This store has no dependencies on other stores and is safe to import directly from other stores. +type SeenVersionsMap = {[key in string]: boolean} + +const semver = { + gte: (a: string, b: string) => { + const arra = a.split('.').map(i => parseInt(i)) + const [a1, a2, a3] = arra + const arrb = b.split('.').map(i => parseInt(i)) + const [b1, b2, b3] = arrb + if (arra.length === 3 && arrb.length === 3) { + return a1! >= b1! && a2! >= b2! && a3! >= b3! + } else { + return false + } + }, + valid: (v: string) => + v.split('.').reduce((cnt, i) => { + if (parseInt(i) >= 0) { + return cnt + 1 + } + return cnt + }, 0) === 3, +} + +const versions = [currentVersion, lastVersion, lastLastVersion, noVersion] as const + +const isVersionValid = (version: string) => { + return version ? semver.valid(version) : false +} + +const getSeenVersions = (lastSeenVersion: string): SeenVersionsMap => { + const initialMap: SeenVersionsMap = { + [currentVersion]: true, + [lastLastVersion]: true, + [lastVersion]: true, + [noVersion]: true, + } + + if (!lastSeenVersion || !semver.valid(lastSeenVersion)) { + return initialMap + } + if (lastSeenVersion === noVersion) { + return { + [currentVersion]: false, + [lastLastVersion]: false, + [lastVersion]: false, + [noVersion]: false, + } + } + + const validVersions = versions.filter(isVersionValid) + + const seenVersions = validVersions.reduce( + (acc, version) => ({ + ...acc, + [version]: version === noVersion ? true : semver.gte(lastSeenVersion, version), + }), + initialMap + ) + + return seenVersions +} + +type Store = T.Immutable<{ + lastSeenVersion: string + seenVersions: SeenVersionsMap +}> +const initialStore: Store = { + lastSeenVersion: '', + seenVersions: getSeenVersions(''), +} +export interface State extends Store { + dispatch: { + resetState: 'default' + updateLastSeen: (lastSeenItem?: {md: T.RPCGen.Gregor1.Metadata; item: T.RPCGen.Gregor1.Item}) => void + } + anyVersionsUnseen: () => boolean +} +export const useWhatsNewState = Z.createZustand((set, get) => { + const dispatch: State['dispatch'] = { + resetState: 'default', + updateLastSeen: lastSeenItem => { + if (lastSeenItem) { + const {body} = lastSeenItem.item + const pushStateLastSeenVersion = uint8ArrayToString(body) + const lastSeenVersion = pushStateLastSeenVersion || noVersion + // Default to 0.0.0 (noVersion) if user has never marked a version as seen + set(s => { + s.lastSeenVersion = lastSeenVersion + s.seenVersions = getSeenVersions(lastSeenVersion) + }) + } else { + set(s => { + s.lastSeenVersion = noVersion + s.seenVersions = getSeenVersions(noVersion) + }) + } + }, + } + return { + ...initialStore, + anyVersionsUnseen: () => { + const {lastSeenVersion: ver} = get() + // On first load of what's new, lastSeenVersion == noVersion so everything is unseen + return ver !== '' && ver === noVersion ? true : Object.values(getSeenVersions(ver)).some(seen => !seen) + }, + dispatch, + } +}) diff --git a/shared/styles/colors.tsx b/shared/styles/colors.tsx index f84ad80c59b3..76c0da24acae 100644 --- a/shared/styles/colors.tsx +++ b/shared/styles/colors.tsx @@ -1,5 +1,5 @@ // the _on_white are precomputed colors so we can do less blending on mobile -import {useDarkModeState} from '@/constants/darkmode' +import {useDarkModeState} from '@/stores/darkmode' import {isIOS, isAndroid} from '@/constants/platform' import type {DynamicColorIOS as DynamicColorIOSType} from 'react-native' import type {Opaque} from '@/constants/types/ts' diff --git a/shared/styles/index.native.tsx b/shared/styles/index.native.tsx index 0fc8b52b108c..a15e6e2db217 100644 --- a/shared/styles/index.native.tsx +++ b/shared/styles/index.native.tsx @@ -2,7 +2,7 @@ import * as Shared from './shared' import {colors as lightColors} from './colors' import styleSheetCreateProxy, {type MapToStyles} from './style-sheet-proxy' import {StyleSheet, Dimensions} from 'react-native' -import {useDarkModeState} from '@/constants/darkmode' +import {useDarkModeState} from '@/stores/darkmode' import {isIOS, isTablet} from '@/constants/platform' const font = isIOS diff --git a/shared/styles/style-sheet-proxy.tsx b/shared/styles/style-sheet-proxy.tsx index 37cb8404f801..a6d33b090b3b 100644 --- a/shared/styles/style-sheet-proxy.tsx +++ b/shared/styles/style-sheet-proxy.tsx @@ -1,4 +1,4 @@ -import {useDarkModeState} from '@/constants/darkmode' +import {useDarkModeState} from '@/stores/darkmode' import type {StylesCrossPlatform} from '.' // Support a closure to enable simple dark mode. diff --git a/shared/team-building/contacts.tsx b/shared/team-building/contacts.tsx index 6d39827419c6..06edc5fb79f2 100644 --- a/shared/team-building/contacts.tsx +++ b/shared/team-building/contacts.tsx @@ -1,8 +1,8 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' -import {useSettingsContactsState} from '@/constants/settings-contacts' -import {useTBContext} from '@/constants/team-building' +import {useSettingsContactsState} from '@/stores/settings-contacts' +import {useTBContext} from '@/stores/team-building' const useContactsProps = () => { const contactsImported = useSettingsContactsState(s => s.importEnabled) diff --git a/shared/team-building/email-search.tsx b/shared/team-building/email-search.tsx index 669e819c92a4..2f5f8f17930d 100644 --- a/shared/team-building/email-search.tsx +++ b/shared/team-building/email-search.tsx @@ -1,11 +1,12 @@ import * as C from '@/constants' -import * as TB from '@/constants/team-building' +import * as TB from '@/stores/team-building' import * as React from 'react' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import {validateEmailAddress} from '@/util/email-address' import {UserMatchMention} from './phone-search' import ContinueButton from './continue-button' +import {searchWaitingKey} from '@/constants/strings' type EmailSearchProps = { continueLabel: string @@ -17,7 +18,7 @@ const EmailSearch = ({continueLabel, namespace, search}: EmailSearchProps) => { const teamBuildingSearchResults = TB.useTBContext(s => s.searchResults) const [isEmailValid, setEmailValidity] = React.useState(false) const [emailString, setEmailString] = React.useState('') - const waiting = C.Waiting.useAnyWaiting(TB.searchWaitingKey) + const waiting = C.Waiting.useAnyWaiting(searchWaitingKey) const user: T.TB.User | undefined = teamBuildingSearchResults.get(emailString)?.get('email')?.[0] const canSubmit = !!user && !waiting && isEmailValid diff --git a/shared/team-building/filtered-service-tab-bar.tsx b/shared/team-building/filtered-service-tab-bar.tsx index 353ce9fa5665..b882965f5429 100644 --- a/shared/team-building/filtered-service-tab-bar.tsx +++ b/shared/team-building/filtered-service-tab-bar.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import type * as T from '@/constants/types' import {ServiceTabBar} from './service-tab-bar' -import * as TeamBuilding from '@/constants/team-building' +import * as TeamBuilding from '@/stores/team-building' export const FilteredServiceTabBar = ( props: Omit, 'services'> & { diff --git a/shared/team-building/index.tsx b/shared/team-building/index.tsx index bbc6518549f4..7b94ab175862 100644 --- a/shared/team-building/index.tsx +++ b/shared/team-building/index.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' -import * as TB from '@/constants/team-building' -import * as Teams from '@/constants/teams' +import * as TB from '@/stores/team-building' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' diff --git a/shared/team-building/list-body.tsx b/shared/team-building/list-body.tsx index d9cb0fcbeb9a..20b0bcd9dac5 100644 --- a/shared/team-building/list-body.tsx +++ b/shared/team-building/list-body.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as C from '@/constants' -import * as TB from '@/constants/team-building' -import {useTeamsState} from '@/constants/teams' +import * as TB from '@/stores/team-building' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import * as Shared from './shared' import PeopleResult from './search-result/people-result' @@ -14,9 +14,9 @@ import type {RootRouteProps} from '@/router-v2/route-params' import {RecsAndRecos, numSectionLabel} from './recs-and-recos' import {formatAnyPhoneNumbers} from '@/util/phone-numbers' import {useRoute} from '@react-navigation/native' -import {useSettingsContactsState} from '@/constants/settings-contacts' -import {useFollowerState} from '@/constants/followers' -import {useCurrentUserState} from '@/constants/current-user' +import {useSettingsContactsState} from '@/stores/settings-contacts' +import {useFollowerState} from '@/stores/followers' +import {useCurrentUserState} from '@/stores/current-user' // import {useAnimatedScrollHandler} from '@/common-adapters/reanimated' import {useColorScheme} from 'react-native' diff --git a/shared/team-building/page.tsx b/shared/team-building/page.tsx index a5f899216400..083c6b96837b 100644 --- a/shared/team-building/page.tsx +++ b/shared/team-building/page.tsx @@ -1,7 +1,7 @@ import type * as C from '@/constants' import * as Kb from '@/common-adapters' import * as React from 'react' -import {TBProvider} from '@/constants/team-building' +import {TBProvider} from '@/stores/team-building' const getOptions = ({route}: OwnProps) => { const namespace: unknown = route.params.namespace diff --git a/shared/team-building/phone-search.tsx b/shared/team-building/phone-search.tsx index 4a93fd2dd078..7c3a04a330bd 100644 --- a/shared/team-building/phone-search.tsx +++ b/shared/team-building/phone-search.tsx @@ -1,10 +1,11 @@ import * as C from '@/constants' -import * as TB from '@/constants/team-building' +import * as TB from '@/stores/team-building' import * as React from 'react' import * as Kb from '@/common-adapters/index' -import type * as T from 'constants/types' +import type * as T from '@/constants/types' import ContinueButton from './continue-button' -import {useSettingsPhoneState} from '@/constants/settings-phone' +import {useSettingsPhoneState} from '@/stores/settings-phone' +import {searchWaitingKey} from '@/constants/strings' type PhoneSearchProps = { continueLabel: string @@ -18,7 +19,7 @@ const PhoneSearch = (props: PhoneSearchProps) => { const [isPhoneValid, setPhoneValidity] = React.useState(false) const [phoneNumber, setPhoneNumber] = React.useState('') const [phoneInputKey, setPhoneInputKey] = React.useState(0) - const waiting = C.Waiting.useAnyWaiting(TB.searchWaitingKey) + const waiting = C.Waiting.useAnyWaiting(searchWaitingKey) const loadDefaultPhoneCountry = useSettingsPhoneState(s => s.dispatch.loadDefaultPhoneCountry) // trigger a default phone number country rpc if it's not already loaded const defaultCountry = useSettingsPhoneState(s => s.defaultCountry) diff --git a/shared/team-building/search-result/hellobot-result.tsx b/shared/team-building/search-result/hellobot-result.tsx index 029e730955d1..8e080121a890 100644 --- a/shared/team-building/search-result/hellobot-result.tsx +++ b/shared/team-building/search-result/hellobot-result.tsx @@ -1,6 +1,6 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import {useTBContext} from '@/constants/team-building' +import {useTBContext} from '@/stores/team-building' import * as Kb from '@/common-adapters' import CommonResult, {type ResultProps} from './common-result' diff --git a/shared/team-building/search-result/people-result.tsx b/shared/team-building/search-result/people-result.tsx index 82bf99580a2c..7925b8767ac3 100644 --- a/shared/team-building/search-result/people-result.tsx +++ b/shared/team-building/search-result/people-result.tsx @@ -1,12 +1,12 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import * as FS from '@/constants/fs' +import * as FS from '@/stores/fs' import CommonResult, {type ResultProps} from './common-result' -import {useUsersState} from '@/constants/users' -import {useCurrentUserState} from '@/constants/current-user' +import {useUsersState} from '@/stores/users' +import {useCurrentUserState} from '@/stores/current-user' /* * This component is intended to be a drop-in replacement for UserResult. @@ -32,14 +32,14 @@ const PeopleResult = React.memo(function PeopleResult(props: ResultProps) { const navigateUp = C.useRouterState(s => s.dispatch.navigateUp) const onOpenPrivateFolder = React.useCallback(() => { navigateUp() - FS.makeActionForOpenPathInFilesTab( + FS.navToPath( T.FS.stringToPath(`/keybase/private/${decoratedUsername},${myUsername}`) ) }, [navigateUp, decoratedUsername, myUsername]) const onBrowsePublicFolder = React.useCallback(() => { navigateUp() - FS.makeActionForOpenPathInFilesTab(T.FS.stringToPath(`/keybase/public/${decoratedUsername}`)) + FS.navToPath(T.FS.stringToPath(`/keybase/public/${decoratedUsername}`)) }, [navigateUp, decoratedUsername]) const onManageBlocking = React.useCallback(() => { diff --git a/shared/team-building/search-result/you-result.tsx b/shared/team-building/search-result/you-result.tsx index 0e98651cd20a..0c482c089fc9 100644 --- a/shared/team-building/search-result/you-result.tsx +++ b/shared/team-building/search-result/you-result.tsx @@ -1,6 +1,6 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import {useTBContext} from '@/constants/team-building' +import {useTBContext} from '@/stores/team-building' import * as Kb from '@/common-adapters' import CommonResult, {type ResultProps} from './common-result' diff --git a/shared/team-building/shared.tsx b/shared/team-building/shared.tsx index 6342bd06fd0b..9303fdcff0ef 100644 --- a/shared/team-building/shared.tsx +++ b/shared/team-building/shared.tsx @@ -1,7 +1,7 @@ import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import type {IconType} from '@/common-adapters/icon.constants-gen' -import * as TeamBuilding from '@/constants/team-building' +import * as TeamBuilding from '@/stores/team-building' const services: { [K in T.TB.ServiceIdWithContact]: { diff --git a/shared/teams/add-members-wizard/add-contacts.native.tsx b/shared/teams/add-members-wizard/add-contacts.native.tsx index 6808ecaea271..e5e430e0bea1 100644 --- a/shared/teams/add-members-wizard/add-contacts.native.tsx +++ b/shared/teams/add-members-wizard/add-contacts.native.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import {pluralize} from '@/util/string' diff --git a/shared/teams/add-members-wizard/add-email.tsx b/shared/teams/add-members-wizard/add-email.tsx index 26cf7893dc31..c19e84f68547 100644 --- a/shared/teams/add-members-wizard/add-email.tsx +++ b/shared/teams/add-members-wizard/add-email.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' import * as T from '@/constants/types' diff --git a/shared/teams/add-members-wizard/add-from-where.tsx b/shared/teams/add-members-wizard/add-from-where.tsx index 08c3f171a503..8bda843d03cf 100644 --- a/shared/teams/add-members-wizard/add-from-where.tsx +++ b/shared/teams/add-members-wizard/add-from-where.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as T from '@/constants/types' import {ModalTitle} from '../common' import {useSafeNavigation} from '@/util/safe-navigation' diff --git a/shared/teams/add-members-wizard/add-phone.tsx b/shared/teams/add-members-wizard/add-phone.tsx index 9a93ef6f9c4b..5ed2a3a15ae2 100644 --- a/shared/teams/add-members-wizard/add-phone.tsx +++ b/shared/teams/add-members-wizard/add-phone.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import {ModalTitle, usePhoneNumberList} from '../common' import {useSafeNavigation} from '@/util/safe-navigation' -import {useSettingsPhoneState} from '@/constants/settings-phone' +import {useSettingsPhoneState} from '@/stores/settings-phone' const waitingKey = 'phoneLookup' diff --git a/shared/teams/add-members-wizard/confirm.tsx b/shared/teams/add-members-wizard/confirm.tsx index 743ef7110357..6c4fab1bef42 100644 --- a/shared/teams/add-members-wizard/confirm.tsx +++ b/shared/teams/add-members-wizard/confirm.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import {assertionToDisplay} from '@/common-adapters/usernames' diff --git a/shared/teams/channel/create-channels.tsx b/shared/teams/channel/create-channels.tsx index 2a11aa71da5b..ab94118ac4c5 100644 --- a/shared/teams/channel/create-channels.tsx +++ b/shared/teams/channel/create-channels.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import {CreateChannelsModal} from '../new-team/wizard/create-channels' diff --git a/shared/teams/channel/header.tsx b/shared/teams/channel/header.tsx index ea4654017c5b..16a840f36f2e 100644 --- a/shared/teams/channel/header.tsx +++ b/shared/teams/channel/header.tsx @@ -1,9 +1,9 @@ import * as T from '@/constants/types' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import {pluralize} from '@/util/string' import {Activity, useChannelParticipants} from '../common' diff --git a/shared/teams/channel/index.tsx b/shared/teams/channel/index.tsx index 0bad5f015636..0af5d5e9274d 100644 --- a/shared/teams/channel/index.tsx +++ b/shared/teams/channel/index.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import { @@ -15,8 +15,8 @@ import ChannelMemberRow from './rows' import BotRow from '../team/rows/bot-row/bot' import SettingsList from '../../chat/conversation/info-panel/settings' import EmptyRow from '../team/rows/empty-row' -import {useBotsState} from '@/constants/bots' -import {useUsersState} from '@/constants/users' +import {useBotsState} from '@/stores/bots' +import {useUsersState} from '@/stores/users' export type OwnProps = { teamID: T.Teams.TeamID diff --git a/shared/teams/channel/rows.tsx b/shared/teams/channel/rows.tsx index e07037b57201..5c77b0f6a518 100644 --- a/shared/teams/channel/rows.tsx +++ b/shared/teams/channel/rows.tsx @@ -1,13 +1,13 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useProfileState} from '@/constants/profile' -import * as Teams from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import {useProfileState} from '@/stores/profile' +import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import * as React from 'react' import * as Kb from '@/common-adapters' import MenuHeader from '../team/rows/menu-header.new' -import {useUsersState} from '@/constants/users' -import {useCurrentUserState} from '@/constants/current-user' +import {useUsersState} from '@/stores/users' +import {useCurrentUserState} from '@/stores/current-user' type Props = { conversationIDKey: T.Chat.ConversationIDKey diff --git a/shared/teams/channel/tabs.tsx b/shared/teams/channel/tabs.tsx index 795cfb89ab9e..bc8d5526ea3a 100644 --- a/shared/teams/channel/tabs.tsx +++ b/shared/teams/channel/tabs.tsx @@ -1,7 +1,7 @@ import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import type {Tab as TabType} from '@/common-adapters/tabs' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' export type TabKey = 'members' | 'attachments' | 'bots' | 'settings' | 'loading' diff --git a/shared/teams/common/activity.tsx b/shared/teams/common/activity.tsx index 6ebbf1f4c098..f31a80050dff 100644 --- a/shared/teams/common/activity.tsx +++ b/shared/teams/common/activity.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' diff --git a/shared/teams/common/channel-hooks.tsx b/shared/teams/common/channel-hooks.tsx index 934b7100704b..7736f46a4e49 100644 --- a/shared/teams/common/channel-hooks.tsx +++ b/shared/teams/common/channel-hooks.tsx @@ -1,8 +1,8 @@ import * as T from '@/constants/types' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' // Filter bots out using team role info, isolate to only when related state changes export const useChannelParticipants = ( diff --git a/shared/teams/common/enable-contacts.tsx b/shared/teams/common/enable-contacts.tsx index 133d5cb95967..477d3faf51dd 100644 --- a/shared/teams/common/enable-contacts.tsx +++ b/shared/teams/common/enable-contacts.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as Kb from '@/common-adapters' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' /** * Popup explaining that Keybase doesn't have contact permissions with a link to @@ -11,7 +11,7 @@ import {useConfigState} from '@/constants/config' * popup. */ const EnableContactsPopup = ({noAccess, onClose}: {noAccess: boolean; onClose: () => void}) => { - const onOpenSettings = useConfigState(s => s.dispatch.dynamic.openAppSettings) + const onOpenSettings = useConfigState(s => s.dispatch.defer.openAppSettings) const [showingPopup, setShowingPopup] = React.useState(noAccess) React.useEffect(() => { diff --git a/shared/teams/common/selection-popup.tsx b/shared/teams/common/selection-popup.tsx index 829c6d3200c9..531efe9d707a 100644 --- a/shared/teams/common/selection-popup.tsx +++ b/shared/teams/common/selection-popup.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import * as React from 'react' import type * as T from '@/constants/types' import {FloatingRolePicker} from '../role-picker' diff --git a/shared/teams/common/use-contacts.native.tsx b/shared/teams/common/use-contacts.native.tsx index 822c30fea955..aa8329ca7130 100644 --- a/shared/teams/common/use-contacts.native.tsx +++ b/shared/teams/common/use-contacts.native.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import {e164ToDisplay} from '@/util/phone-numbers' import logger from '@/logger' import {getDefaultCountryCode} from 'react-native-kb' -import {useSettingsContactsState} from '@/constants/settings-contacts' -import {getE164} from '@/constants/settings-phone' +import {useSettingsContactsState} from '@/stores/settings-contacts' +import {getE164} from '@/util/phone-numbers' // Contact info coming from the native contacts library. export type Contact = { diff --git a/shared/teams/confirm-modals/confirm-kick-out.tsx b/shared/teams/confirm-modals/confirm-kick-out.tsx index 5a8a0d0624e1..4c9d358d20a3 100644 --- a/shared/teams/confirm-modals/confirm-kick-out.tsx +++ b/shared/teams/confirm-modals/confirm-kick-out.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as React from 'react' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import {useSafeNavigation} from '@/util/safe-navigation' diff --git a/shared/teams/confirm-modals/confirm-remove-from-channel.tsx b/shared/teams/confirm-modals/confirm-remove-from-channel.tsx index 4c346846133f..c05aa0d0d84b 100644 --- a/shared/teams/confirm-modals/confirm-remove-from-channel.tsx +++ b/shared/teams/confirm-modals/confirm-remove-from-channel.tsx @@ -1,8 +1,8 @@ import * as T from '@/constants/types' import * as C from '@/constants' import * as React from 'react' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' diff --git a/shared/teams/confirm-modals/delete-channel.tsx b/shared/teams/confirm-modals/delete-channel.tsx index 9ad085f55ab3..c2e44e152a52 100644 --- a/shared/teams/confirm-modals/delete-channel.tsx +++ b/shared/teams/confirm-modals/delete-channel.tsx @@ -2,7 +2,7 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import * as React from 'react' import type * as T from '@/constants/types' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import {pluralize} from '@/util/string' import {useAllChannelMetas} from '@/teams/common/channel-hooks' diff --git a/shared/teams/confirm-modals/really-leave-team/index.tsx b/shared/teams/confirm-modals/really-leave-team/index.tsx index 684726a1e8a4..9d4340be3b68 100644 --- a/shared/teams/confirm-modals/really-leave-team/index.tsx +++ b/shared/teams/confirm-modals/really-leave-team/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as C from '@/constants' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import {useSafeSubmit} from '@/util/safe-submit' import type * as T from '@/constants/types' diff --git a/shared/teams/container.tsx b/shared/teams/container.tsx index f3219023e020..890288fea27c 100644 --- a/shared/teams/container.tsx +++ b/shared/teams/container.tsx @@ -1,15 +1,15 @@ import * as C from '@/constants' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import * as FS from '@/constants/fs' +import * as FS from '@/stores/fs' import Main from './main' import openURL from '@/util/open-url' import {useTeamsSubscribe} from './subscriber' import {useActivityLevels} from './common' import {useSafeNavigation} from '@/util/safe-navigation' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const orderTeams = ( teams: ReadonlyMap, @@ -78,7 +78,7 @@ const Connected = () => { updateGregorCategory('sawChatBanner', 'true') } const onOpenFolder = (teamname: T.Teams.Teamname) => { - FS.makeActionForOpenPathInFilesTab(T.FS.stringToPath(`/keybase/team/${teamname}`)) + FS.navToPath(T.FS.stringToPath(`/keybase/team/${teamname}`)) } const onReadMore = () => { openURL('https://keybase.io/blog/introducing-keybase-teams') diff --git a/shared/teams/delete-team.tsx b/shared/teams/delete-team.tsx index e0dfd2750e0f..4c69295c90ac 100644 --- a/shared/teams/delete-team.tsx +++ b/shared/teams/delete-team.tsx @@ -6,8 +6,8 @@ import * as Kb from '@/common-adapters' import {pluralize} from '@/util/string' import {useTeamDetailsSubscribe} from './subscriber' import noop from 'lodash/noop' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' type OwnProps = {teamID: T.Teams.TeamID} diff --git a/shared/teams/edit-team-description.tsx b/shared/teams/edit-team-description.tsx index 7be608185f4c..71e0934031c3 100644 --- a/shared/teams/edit-team-description.tsx +++ b/shared/teams/edit-team-description.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import {ModalTitle} from './common' diff --git a/shared/teams/emojis/add-alias.tsx b/shared/teams/emojis/add-alias.tsx index cfbf1a23f600..728b3e93a2a4 100644 --- a/shared/teams/emojis/add-alias.tsx +++ b/shared/teams/emojis/add-alias.tsx @@ -1,6 +1,6 @@ import * as T from '@/constants/types' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import {EmojiPickerDesktop} from '@/chat/emoji-picker/container' diff --git a/shared/teams/emojis/add-emoji.tsx b/shared/teams/emojis/add-emoji.tsx index b1f9a3343e17..81c4e896a1f7 100644 --- a/shared/teams/emojis/add-emoji.tsx +++ b/shared/teams/emojis/add-emoji.tsx @@ -1,6 +1,6 @@ import * as T from '@/constants/types' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' import {AliasInput, Modal} from './common' diff --git a/shared/teams/external-team.tsx b/shared/teams/external-team.tsx index 642461031ab6..9fd9da9f4f66 100644 --- a/shared/teams/external-team.tsx +++ b/shared/teams/external-team.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useProfileState} from '@/constants/profile' +import * as Chat from '@/stores/chat2' +import {useProfileState} from '@/stores/profile' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import {useTeamLinkPopup} from './common' import {pluralize} from '@/util/string' import capitalize from 'lodash/capitalize' diff --git a/shared/teams/get-options.tsx b/shared/teams/get-options.tsx index e4aca9f077a2..f0cf90c88b0f 100644 --- a/shared/teams/get-options.tsx +++ b/shared/teams/get-options.tsx @@ -1,7 +1,7 @@ import * as Kb from '@/common-adapters' import {HeaderRightActions} from './main/header' import {useSafeNavigation} from '@/util/safe-navigation' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' const useHeaderActions = () => { const nav = useSafeNavigation() diff --git a/shared/teams/invite-by-contact/team-invite-by-contacts.native.tsx b/shared/teams/invite-by-contact/team-invite-by-contacts.native.tsx index c0afab8186de..cffc7f924255 100644 --- a/shared/teams/invite-by-contact/team-invite-by-contacts.native.tsx +++ b/shared/teams/invite-by-contact/team-invite-by-contacts.native.tsx @@ -1,11 +1,11 @@ import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import useContacts, {type Contact} from '../common/use-contacts.native' import {InviteByContact, type ContactRowProps} from './index.native' import {useTeamDetailsSubscribe} from '../subscriber' import {useSafeNavigation} from '@/util/safe-navigation' -import {getE164} from '@/constants/settings-phone' +import {getE164} from '@/util/phone-numbers' // Seitan invite names (labels) look like this: "[name] ([phone number])". Try // to derive E164 phone number based on seitan invite name and user's region. diff --git a/shared/teams/invite-by-email.tsx b/shared/teams/invite-by-email.tsx index eb81f145ac77..f0201964e4d5 100644 --- a/shared/teams/invite-by-email.tsx +++ b/shared/teams/invite-by-email.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import type * as T from '@/constants/types' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' import {FloatingRolePicker} from './role-picker' diff --git a/shared/teams/join-team/container.tsx b/shared/teams/join-team/container.tsx index abbdbc6f3941..7a1e2e4df322 100644 --- a/shared/teams/join-team/container.tsx +++ b/shared/teams/join-team/container.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import upperFirst from 'lodash/upperFirst' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' diff --git a/shared/teams/join-team/join-from-invite.tsx b/shared/teams/join-team/join-from-invite.tsx index fc4fa2ea5d33..f87440ceac61 100644 --- a/shared/teams/join-team/join-from-invite.tsx +++ b/shared/teams/join-team/join-from-invite.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import {Success} from './container' import {useSafeNavigation} from '@/util/safe-navigation' diff --git a/shared/teams/main/index.tsx b/shared/teams/main/index.tsx index 71ee7d1b4daa..7ef9fd136b08 100644 --- a/shared/teams/main/index.tsx +++ b/shared/teams/main/index.tsx @@ -4,7 +4,7 @@ import type * as T from '@/constants/types' import Banner from './banner' import TeamsFooter from './footer' import TeamRowNew from './team-row' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' type DeletedTeam = { teamName: string diff --git a/shared/teams/main/team-row.tsx b/shared/teams/main/team-row.tsx index a2550414088d..524f5e399dca 100644 --- a/shared/teams/main/team-row.tsx +++ b/shared/teams/main/team-row.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' import * as React from 'react' import type * as T from '@/constants/types' @@ -6,8 +6,8 @@ import TeamMenu from '../team/menu-container' import {pluralize} from '@/util/string' import {Activity} from '../common' import {useSafeNavigation} from '@/util/safe-navigation' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' type Props = { firstItem: boolean diff --git a/shared/teams/new-team/index.tsx b/shared/teams/new-team/index.tsx index 864bc75f6e6d..13d7883eaa0d 100644 --- a/shared/teams/new-team/index.tsx +++ b/shared/teams/new-team/index.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import openUrl from '@/util/open-url' diff --git a/shared/teams/new-team/wizard/add-subteam-members.tsx b/shared/teams/new-team/wizard/add-subteam-members.tsx index dc439ccc5bb5..6d9484b18811 100644 --- a/shared/teams/new-team/wizard/add-subteam-members.tsx +++ b/shared/teams/new-team/wizard/add-subteam-members.tsx @@ -1,4 +1,4 @@ -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' @@ -6,7 +6,7 @@ import {ModalTitle} from '@/teams/common' import {pluralize} from '@/util/string' import {useTeamDetailsSubscribe} from '@/teams/subscriber' import {useSafeNavigation} from '@/util/safe-navigation' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' const AddSubteamMembers = () => { const nav = useSafeNavigation() diff --git a/shared/teams/new-team/wizard/create-channels.tsx b/shared/teams/new-team/wizard/create-channels.tsx index b2c22534ece2..dda466eb062e 100644 --- a/shared/teams/new-team/wizard/create-channels.tsx +++ b/shared/teams/new-team/wizard/create-channels.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import {pluralize} from '@/util/string' diff --git a/shared/teams/new-team/wizard/create-subteams.tsx b/shared/teams/new-team/wizard/create-subteams.tsx index e45764eff76c..89a3097a83d6 100644 --- a/shared/teams/new-team/wizard/create-subteams.tsx +++ b/shared/teams/new-team/wizard/create-subteams.tsx @@ -4,7 +4,7 @@ import * as T from '@/constants/types' import {pluralize} from '@/util/string' import {ModalTitle} from '@/teams/common' import {useSafeNavigation} from '@/util/safe-navigation' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' const cleanSubteamName = (name: string) => name.replace(/[^0-9a-zA-Z_]/, '') diff --git a/shared/teams/new-team/wizard/make-big-team.tsx b/shared/teams/new-team/wizard/make-big-team.tsx index 016a8edc4b71..071a0e4420f8 100644 --- a/shared/teams/new-team/wizard/make-big-team.tsx +++ b/shared/teams/new-team/wizard/make-big-team.tsx @@ -2,7 +2,7 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import {ModalTitle} from '@/teams/common' import {useSafeNavigation} from '@/util/safe-navigation' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' const MakeBigTeam = () => { const nav = useSafeNavigation() diff --git a/shared/teams/new-team/wizard/new-team-info.tsx b/shared/teams/new-team/wizard/new-team-info.tsx index cb9661e3b0ee..0bfb4183f529 100644 --- a/shared/teams/new-team/wizard/new-team-info.tsx +++ b/shared/teams/new-team/wizard/new-team-info.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import {ModalTitle} from '@/teams/common' import * as T from '@/constants/types' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import {pluralize} from '@/util/string' import {InlineDropdown} from '@/common-adapters/dropdown' import {FloatingRolePicker} from '../../role-picker' diff --git a/shared/teams/new-team/wizard/team-purpose.tsx b/shared/teams/new-team/wizard/team-purpose.tsx index 2d75aad151e8..4fa97617bb83 100644 --- a/shared/teams/new-team/wizard/team-purpose.tsx +++ b/shared/teams/new-team/wizard/team-purpose.tsx @@ -2,7 +2,7 @@ import * as Kb from '@/common-adapters' import {ModalTitle} from '@/teams/common' import * as T from '@/constants/types' import {useSafeNavigation} from '@/util/safe-navigation' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' const TeamPurpose = () => { const nav = useSafeNavigation() diff --git a/shared/teams/rename-team.tsx b/shared/teams/rename-team.tsx index 3b080205a545..bdcb775f658d 100644 --- a/shared/teams/rename-team.tsx +++ b/shared/teams/rename-team.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' type OwnProps = {teamname: string} diff --git a/shared/teams/routes.tsx b/shared/teams/routes.tsx index 5692f5f15451..3d57aef29f3a 100644 --- a/shared/teams/routes.tsx +++ b/shared/teams/routes.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import contactRestricted from '../team-building/contact-restricted.page' import teamsTeamBuilder from '../team-building/page' import teamsRootGetOptions from './get-options' diff --git a/shared/teams/subscriber.tsx b/shared/teams/subscriber.tsx index e4dd3276faef..8ea982cb3645 100644 --- a/shared/teams/subscriber.tsx +++ b/shared/teams/subscriber.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import type * as T from '@/constants/types' // NOTE: If you are in a floating box or otherwise outside the navigation diff --git a/shared/teams/team/index.tsx b/shared/teams/team/index.tsx index 0d83779ee857..9c2ad37aa0a9 100644 --- a/shared/teams/team/index.tsx +++ b/shared/teams/team/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' @@ -18,7 +18,7 @@ import { type Section, type Item, } from './rows' -import {useBotsState} from '@/constants/bots' +import {useBotsState} from '@/stores/bots' type Props = { teamID: T.Teams.TeamID diff --git a/shared/teams/team/member/add-to-channels.tsx b/shared/teams/team/member/add-to-channels.tsx index 79922967b9e1..3e3035cb798e 100644 --- a/shared/teams/team/member/add-to-channels.tsx +++ b/shared/teams/team/member/add-to-channels.tsx @@ -1,14 +1,14 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' import * as Common from '@/teams/common' import {pluralize} from '@/util/string' import {useAllChannelMetas} from '@/teams/common/channel-hooks' import {useSafeNavigation} from '@/util/safe-navigation' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type Props = { teamID: T.Teams.TeamID diff --git a/shared/teams/team/member/edit-channel.tsx b/shared/teams/team/member/edit-channel.tsx index 95679a91a100..1f1a49e5f552 100644 --- a/shared/teams/team/member/edit-channel.tsx +++ b/shared/teams/team/member/edit-channel.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as React from 'react' import type * as T from '@/constants/types' import {ModalTitle} from '@/teams/common' diff --git a/shared/teams/team/member/index.new.tsx b/shared/teams/team/member/index.new.tsx index 3ac20ce63dfa..c5bcd10e5aa9 100644 --- a/shared/teams/team/member/index.new.tsx +++ b/shared/teams/team/member/index.new.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import {useCurrentUserState} from '@/constants/current-user' -import * as Teams from '@/constants/teams' -import {useProfileState} from '@/constants/profile' +import * as Chat from '@/stores/chat2' +import {useCurrentUserState} from '@/stores/current-user' +import * as Teams from '@/stores/teams' +import {useProfileState} from '@/stores/profile' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as React from 'react' diff --git a/shared/teams/team/menu-container.tsx b/shared/teams/team/menu-container.tsx index 4e63c738378f..5ea551600be8 100644 --- a/shared/teams/team/menu-container.tsx +++ b/shared/teams/team/menu-container.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import type * as React from 'react' -import * as FS from '@/constants/fs/util' -import * as Teams from '@/constants/teams' +import * as FS from '@/constants/fs' +import * as Teams from '@/stores/teams' import capitalize from 'lodash/capitalize' import * as T from '@/constants/types' import {pluralize} from '@/util/string' @@ -98,7 +98,7 @@ const Container = (ownProps: OwnProps) => { navigateAppend({props: {teamID}, selected: 'teamReallyLeaveTeam'}) } const onOpenFolder = (teamname: string) => { - FS.makeActionForOpenPathInFilesTab(T.FS.stringToPath(`/keybase/team/${teamname}`)) + FS.navToPath(T.FS.stringToPath(`/keybase/team/${teamname}`)) } const items: Kb.MenuItems = ['Divider'] diff --git a/shared/teams/team/new-header.tsx b/shared/teams/team/new-header.tsx index bbdf3a4bb0aa..b5f05946e03b 100644 --- a/shared/teams/team/new-header.tsx +++ b/shared/teams/team/new-header.tsx @@ -1,15 +1,15 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import TeamMenu from './menu-container' import {pluralize} from '@/util/string' import {Activity, useActivityLevels, useTeamLinkPopup} from '../common' import type * as T from '@/constants/types' import {useSafeNavigation} from '@/util/safe-navigation' -import {useCurrentUserState} from '@/constants/current-user' -import {useTeamsState} from '@/constants/teams' +import {useCurrentUserState} from '@/stores/current-user' +import {useTeamsState} from '@/stores/teams' const AddPeopleButton = ({teamID}: {teamID: T.Teams.TeamID}) => { const startAddMembersWizard = useTeamsState(s => s.dispatch.startAddMembersWizard) diff --git a/shared/teams/team/rows/bot-row/bot.tsx b/shared/teams/team/rows/bot-row/bot.tsx index a6fb1ef29367..4724d6429e39 100644 --- a/shared/teams/team/rows/bot-row/bot.tsx +++ b/shared/teams/team/rows/bot-row/bot.tsx @@ -1,12 +1,12 @@ import * as C from '@/constants' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import BotMenu from './bot-menu' -import {useBotsState} from '@/constants/bots' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' +import {useBotsState} from '@/stores/bots' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' export type Props = { botAlias: string diff --git a/shared/teams/team/rows/channel-row/channel.tsx b/shared/teams/team/rows/channel-row/channel.tsx index 846b88deb7e1..3b8b31017ac0 100644 --- a/shared/teams/team/rows/channel-row/channel.tsx +++ b/shared/teams/team/rows/channel-row/channel.tsx @@ -4,8 +4,8 @@ import type * as T from '@/constants/types' import {Activity, useChannelParticipants} from '@/teams/common' import {pluralize} from '@/util/string' import {useSafeNavigation} from '@/util/safe-navigation' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' type ChannelRowProps = { conversationIDKey: T.Chat.ConversationIDKey diff --git a/shared/teams/team/rows/emoji-row/add.tsx b/shared/teams/team/rows/emoji-row/add.tsx index b4381e54f327..b48b58e984e4 100644 --- a/shared/teams/team/rows/emoji-row/add.tsx +++ b/shared/teams/team/rows/emoji-row/add.tsx @@ -1,8 +1,8 @@ import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import {useSafeNavigation} from '@/util/safe-navigation' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' type OwnProps = { teamID: T.Teams.TeamID diff --git a/shared/teams/team/rows/emoji-row/item.tsx b/shared/teams/team/rows/emoji-row/item.tsx index dfc547d4cafc..41c72d14f68b 100644 --- a/shared/teams/team/rows/emoji-row/item.tsx +++ b/shared/teams/team/rows/emoji-row/item.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as T from '@/constants/types' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' import * as dateFns from 'date-fns' @@ -8,7 +8,7 @@ import {RPCToEmojiData} from '@/common-adapters/emoji' import EmojiMenu from './emoji-menu' import {useEmojiState} from '@/teams/emojis/use-emoji' import {useSafeNavigation} from '@/util/safe-navigation' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type OwnProps = { conversationIDKey: T.Chat.ConversationIDKey diff --git a/shared/teams/team/rows/empty-row.tsx b/shared/teams/team/rows/empty-row.tsx index b30917726587..df3f9f02eaaa 100644 --- a/shared/teams/team/rows/empty-row.tsx +++ b/shared/teams/team/rows/empty-row.tsx @@ -1,10 +1,10 @@ import type * as T from '@/constants/types' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Teams from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import {useSafeNavigation} from '@/util/safe-navigation' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type Props = { type: 'channelsEmpty' | 'channelsFew' | 'members' | 'subteams' diff --git a/shared/teams/team/rows/index.tsx b/shared/teams/team/rows/index.tsx index cef7d9413a38..ddfe2b0808fa 100644 --- a/shared/teams/team/rows/index.tsx +++ b/shared/teams/team/rows/index.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as T from '@/constants/types' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' import EmptyRow from './empty-row' @@ -14,7 +14,7 @@ import {RequestRow, InviteRow} from './invite-row' import {SubteamAddRow, SubteamInfoRow, SubteamTeamRow} from './subteam-row' import {getOrderedMemberArray, sortInvites, getOrderedBotsArray} from './helpers' import {useEmojiState} from '../../emojis/use-emoji' -import {useCurrentUserState} from '@/constants/current-user' +import {useCurrentUserState} from '@/stores/current-user' type Requests = Omit, 'firstItem' | 'teamID'> diff --git a/shared/teams/team/rows/invite-row/invite.tsx b/shared/teams/team/rows/invite-row/invite.tsx index 78b2c553a7c0..ff3f9958b352 100644 --- a/shared/teams/team/rows/invite-row/invite.tsx +++ b/shared/teams/team/rows/invite-row/invite.tsx @@ -2,8 +2,8 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import {formatPhoneNumber} from '@/util/phone-numbers' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' export type Props = { isKeybaseUser?: boolean diff --git a/shared/teams/team/rows/invite-row/request.tsx b/shared/teams/team/rows/invite-row/request.tsx index a4342c9d8e08..36be8c8e829c 100644 --- a/shared/teams/team/rows/invite-row/request.tsx +++ b/shared/teams/team/rows/invite-row/request.tsx @@ -1,8 +1,8 @@ import * as React from 'react' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Teams from '@/constants/teams' -import {useProfileState} from '@/constants/profile' +import * as Chat from '@/stores/chat2' +import * as Teams from '@/stores/teams' +import {useProfileState} from '@/stores/profile' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {FloatingRolePicker, sendNotificationFooter} from '@/teams/role-picker' diff --git a/shared/teams/team/rows/member-row.tsx b/shared/teams/team/rows/member-row.tsx index 5c74bd5d754e..7fc8920b2a01 100644 --- a/shared/teams/team/rows/member-row.tsx +++ b/shared/teams/team/rows/member-row.tsx @@ -1,15 +1,15 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as Kb from '@/common-adapters' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as React from 'react' import type * as T from '@/constants/types' import MenuHeader from './menu-header.new' import {useSafeNavigation} from '@/util/safe-navigation' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' -import {useUsersState} from '@/constants/users' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' +import {useUsersState} from '@/stores/users' +import {useCurrentUserState} from '@/stores/current-user' export type Props = { firstItem: boolean diff --git a/shared/teams/team/rows/subteam-row/add.tsx b/shared/teams/team/rows/subteam-row/add.tsx index 6e80257e3113..58102c169fa9 100644 --- a/shared/teams/team/rows/subteam-row/add.tsx +++ b/shared/teams/team/rows/subteam-row/add.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import {useTeamsState} from '@/constants/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' diff --git a/shared/teams/team/settings-tab/default-channels.tsx b/shared/teams/team/settings-tab/default-channels.tsx index 4bd9dbf45d56..76524135f8bc 100644 --- a/shared/teams/team/settings-tab/default-channels.tsx +++ b/shared/teams/team/settings-tab/default-channels.tsx @@ -4,8 +4,8 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import type {RPCError} from '@/util/errors' import {ChannelsWidget} from '@/teams/common' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' type Props = { teamID: T.Teams.TeamID diff --git a/shared/teams/team/settings-tab/index.tsx b/shared/teams/team/settings-tab/index.tsx index 264e7690abad..5b83a2071295 100644 --- a/shared/teams/team/settings-tab/index.tsx +++ b/shared/teams/team/settings-tab/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Teams from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {FloatingRolePicker} from '@/teams/role-picker' diff --git a/shared/teams/team/settings-tab/retention/index.tsx b/shared/teams/team/settings-tab/retention/index.tsx index 3fc393dd4026..0c539384b43c 100644 --- a/shared/teams/team/settings-tab/retention/index.tsx +++ b/shared/teams/team/settings-tab/retention/index.tsx @@ -1,8 +1,8 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat2' import * as React from 'react' -import * as Teams from '@/constants/teams' -import {useTeamsState} from '@/constants/teams' +import * as Teams from '@/stores/teams' +import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import type {StylesCrossPlatform} from '@/styles' diff --git a/shared/teams/team/tabs.tsx b/shared/teams/team/tabs.tsx index a27c8027232c..a2fcf15ecd24 100644 --- a/shared/teams/team/tabs.tsx +++ b/shared/teams/team/tabs.tsx @@ -1,8 +1,8 @@ import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import * as C from '@/constants' -import * as Chat from '@/constants/chat2' -import * as Teams from '@/constants/teams' +import * as Chat from '@/stores/chat2' +import * as Teams from '@/stores/teams' import type {Tab as TabType} from '@/common-adapters/tabs' type TeamTabsProps = { diff --git a/shared/teams/team/team-info.tsx b/shared/teams/team/team-info.tsx index 5c1a170c27e5..4eb6002f45b9 100644 --- a/shared/teams/team/team-info.tsx +++ b/shared/teams/team/team-info.tsx @@ -1,6 +1,6 @@ import * as C from '@/constants' import * as React from 'react' -import * as Teams from '@/constants/teams' +import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import {ModalTitle} from '../common' diff --git a/shared/todo.txt b/shared/todo.txt index ae8511c890cb..a13900cfbaa4 100644 --- a/shared/todo.txt +++ b/shared/todo.txt @@ -1,8 +1,8 @@ -android: +TOOD: +react-native-screens header doesn't handle dyanmic colors still https://github.com/software-mansion/react-native-screens/issues/3570 -ios: -input paste view removed <<<<<<<<<<<< +ios: ipad: expo-av to expo-video / expo-audio (not ready yet can't get video size...), missing stuff we need like on full screen change diff --git a/shared/tracker2/assertion.tsx b/shared/tracker2/assertion.tsx index 57a0014647c3..1cf078bf5ece 100644 --- a/shared/tracker2/assertion.tsx +++ b/shared/tracker2/assertion.tsx @@ -1,16 +1,16 @@ import * as React from 'react' import * as C from '@/constants' -import {useConfigState} from '@/constants/config' -import {useCurrentUserState} from '@/constants/current-user' +import {useConfigState} from '@/stores/config' +import {useCurrentUserState} from '@/stores/current-user' import type * as T from '@/constants/types' import openUrl from '@/util/open-url' import * as Kb from '@/common-adapters' import {SiteIcon} from '@/profile/generic/shared' import {formatTimeForAssertionPopup} from '@/util/timestamp' import {useColorScheme} from 'react-native' -import * as Tracker from '@/constants/tracker2' -import {useTrackerState} from '@/constants/tracker2' -import {useProfileState} from '@/constants/profile' +import * as Tracker from '@/stores/tracker2' +import {useTrackerState} from '@/stores/tracker2' +import {useProfileState} from '@/stores/profile' type OwnProps = { isSuggestion?: boolean @@ -424,7 +424,7 @@ const assertionColorToColor = (c: T.Tracker.AssertionColor) => { const StellarValue = (p: {value: string; color: T.Tracker.AssertionColor}) => { const {value, color} = p - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) const onCopyAddress = React.useCallback(() => { copyToClipboard(value) }, [copyToClipboard, value]) diff --git a/shared/tracker2/bio.tsx b/shared/tracker2/bio.tsx index 9113b0f41b25..e26d67af9457 100644 --- a/shared/tracker2/bio.tsx +++ b/shared/tracker2/bio.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' -import {useTrackerState} from '@/constants/tracker2' -import {useFollowerState} from '@/constants/followers' +import {useTrackerState} from '@/stores/tracker2' +import {useFollowerState} from '@/stores/followers' type OwnProps = { inTracker: boolean diff --git a/shared/tracker2/remote-container.desktop.tsx b/shared/tracker2/remote-container.desktop.tsx index 16c3549ec2ca..4433a19c09b2 100644 --- a/shared/tracker2/remote-container.desktop.tsx +++ b/shared/tracker2/remote-container.desktop.tsx @@ -1,7 +1,7 @@ // Inside tracker we use an embedded Avatar which is connected. import * as React from 'react' import * as C from '@/constants' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as R from '@/constants/remote' import * as RemoteGen from '../actions/remote-gen' import type * as T from '@/constants/types' @@ -9,11 +9,11 @@ import Tracker from './index.desktop' import type {DeserializeProps} from './remote-serializer.desktop' import KB2 from '@/util/electron.desktop' import {useAvatarState} from '@/common-adapters/avatar/store' -import {useTrackerState} from '@/constants/tracker2' -import {useUsersState} from '@/constants/users' -import {useFollowerState} from '@/constants/followers' -import {useCurrentUserState} from '@/constants/current-user' -import {useDarkModeState} from '@/constants/darkmode' +import {useTrackerState} from '@/stores/tracker2' +import {useUsersState} from '@/stores/users' +import {useFollowerState} from '@/stores/followers' +import {useCurrentUserState} from '@/stores/current-user' +import {useDarkModeState} from '@/stores/darkmode' const {closeWindow} = KB2.functions diff --git a/shared/tracker2/remote-proxy.desktop.tsx b/shared/tracker2/remote-proxy.desktop.tsx index f8ca55fb0c87..0cb25baaab0f 100644 --- a/shared/tracker2/remote-proxy.desktop.tsx +++ b/shared/tracker2/remote-proxy.desktop.tsx @@ -1,7 +1,7 @@ // A mirror of the remote tracker windows. import * as C from '@/constants' import {useAvatarState} from '@/common-adapters/avatar/store' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' import * as React from 'react' import useSerializeProps from '../desktop/remote/use-serialize-props.desktop' import useBrowserWindow from '../desktop/remote/use-browser-window.desktop' @@ -9,10 +9,10 @@ import {serialize, type ProxyProps} from './remote-serializer.desktop' import {intersect} from '@/util/set' import {mapFilterByKey} from '@/util/map' import {useColorScheme} from 'react-native' -import {useTrackerState} from '@/constants/tracker2' -import {useUsersState} from '@/constants/users' -import {useFollowerState} from '@/constants/followers' -import {useCurrentUserState} from '@/constants/current-user' +import {useTrackerState} from '@/stores/tracker2' +import {useUsersState} from '@/stores/users' +import {useFollowerState} from '@/stores/followers' +import {useCurrentUserState} from '@/stores/current-user' const MAX_TRACKERS = 5 const windowOpts = {hasShadow: false, height: 470, transparent: true, width: 320} diff --git a/shared/unlock-folders/device-list.desktop.tsx b/shared/unlock-folders/device-list.desktop.tsx index 9a2456b13680..ab64aeb86072 100644 --- a/shared/unlock-folders/device-list.desktop.tsx +++ b/shared/unlock-folders/device-list.desktop.tsx @@ -1,5 +1,5 @@ import * as Kb from '@/common-adapters' -import type {State as ConfigStore} from '@/constants/config' +import type {State as ConfigStore} from '@/stores/config' export type Props = { devices: ConfigStore['unlockFoldersDevices'] diff --git a/shared/unlock-folders/index.desktop.tsx b/shared/unlock-folders/index.desktop.tsx index d8cd03f4894a..af45ee616ebd 100644 --- a/shared/unlock-folders/index.desktop.tsx +++ b/shared/unlock-folders/index.desktop.tsx @@ -4,8 +4,8 @@ import DeviceList from './device-list.desktop' import DragHeader from '../desktop/remote/drag-header.desktop' import PaperKeyInput from './paper-key-input.desktop' import Success from './success.desktop' -import type * as Constants from '@/constants/unlock-folders' -import type {State as ConfigStore} from '@/constants/config' +import type * as Constants from '@/stores/unlock-folders' +import type {State as ConfigStore} from '@/stores/config' export type Props = { phase: Constants.State['phase'] diff --git a/shared/unlock-folders/remote-container.desktop.tsx b/shared/unlock-folders/remote-container.desktop.tsx index 15446563ffc6..18db5590d794 100644 --- a/shared/unlock-folders/remote-container.desktop.tsx +++ b/shared/unlock-folders/remote-container.desktop.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import * as RemoteGen from '../actions/remote-gen' import UnlockFolders from './index.desktop' import type {DeserializeProps} from './remote-serializer.desktop' -import {useUnlockFoldersState as useUFState} from '@/constants/unlock-folders' -import {useDarkModeState} from '@/constants/darkmode' +import {useUnlockFoldersState as useUFState} from '@/stores/unlock-folders' +import {useDarkModeState} from '@/stores/darkmode' const RemoteContainer = (d: DeserializeProps) => { const {darkMode, devices, waiting, paperKeyError: _error} = d diff --git a/shared/unlock-folders/remote-proxy.desktop.tsx b/shared/unlock-folders/remote-proxy.desktop.tsx index 34ae7ba99c1d..ebe89bf3d4b7 100644 --- a/shared/unlock-folders/remote-proxy.desktop.tsx +++ b/shared/unlock-folders/remote-proxy.desktop.tsx @@ -4,7 +4,7 @@ import useBrowserWindow from '../desktop/remote/use-browser-window.desktop' import useSerializeProps from '../desktop/remote/use-serialize-props.desktop' import {serialize, type ProxyProps} from './remote-serializer.desktop' import {useColorScheme} from 'react-native' -import {useConfigState} from '@/constants/config' +import {useConfigState} from '@/stores/config' const windowOpts = {height: 300, width: 500} diff --git a/shared/unlock-folders/remote-serializer.desktop.tsx b/shared/unlock-folders/remote-serializer.desktop.tsx index 0370a4c3a86c..b46524dc672d 100644 --- a/shared/unlock-folders/remote-serializer.desktop.tsx +++ b/shared/unlock-folders/remote-serializer.desktop.tsx @@ -1,5 +1,5 @@ import * as T from '@/constants/types' -import type * as ConfigConstants from '@/constants/config' +import type * as ConfigConstants from '@/stores/config' import {produce} from 'immer' export type ProxyProps = { diff --git a/shared/util/crop.tsx b/shared/util/crop.tsx index b06f65bb002b..2f3b745a0c57 100644 --- a/shared/util/crop.tsx +++ b/shared/util/crop.tsx @@ -1,4 +1,4 @@ -import type * as T from '../constants/types' +import type * as T from '@/constants/types' export const fixCrop = (c?: T.RPCChat.Keybase1.ImageCropRect) => { return c diff --git a/shared/util/phone-numbers/index.tsx b/shared/util/phone-numbers/index.tsx index 7c419b18d118..587795dbaf09 100644 --- a/shared/util/phone-numbers/index.tsx +++ b/shared/util/phone-numbers/index.tsx @@ -2,10 +2,10 @@ import * as C from '@/constants' import libphonenumber from 'google-libphonenumber' const PNF = libphonenumber.PhoneNumberFormat -export const PhoneNumberFormat = PNF +const PhoneNumberFormat = PNF -export const phoneUtil = libphonenumber.PhoneNumberUtil.getInstance() -export const ValidationResult = libphonenumber.PhoneNumberUtil.ValidationResult +const phoneUtil = libphonenumber.PhoneNumberUtil.getInstance() +const ValidationResult = libphonenumber.PhoneNumberUtil.ValidationResult const supported = phoneUtil.getSupportedRegions() export type CountryData = { @@ -188,3 +188,17 @@ export const formatPhoneNumberInternational = (rawNumber: string): string | unde return undefined } } + +// Get phone number in e.164, or null if we can't parse it. +export const getE164 = (phoneNumber: string, countryCode?: string) => { + try { + const parsed = countryCode ? phoneUtil.parse(phoneNumber, countryCode) : phoneUtil.parse(phoneNumber) + const reason = phoneUtil.isPossibleNumberWithReason(parsed) + if (reason !== ValidationResult.IS_POSSIBLE) { + return null + } + return phoneUtil.format(parsed, PhoneNumberFormat.E164) + } catch { + return null + } +} diff --git a/shared/constants/platform-specific/index.d.ts b/shared/util/platform-specific/index.d.ts similarity index 86% rename from shared/constants/platform-specific/index.d.ts rename to shared/util/platform-specific/index.d.ts index 7eefb9fc101e..caecba1087ae 100644 --- a/shared/constants/platform-specific/index.d.ts +++ b/shared/util/platform-specific/index.d.ts @@ -1,4 +1,4 @@ -import type * as T from '../types' +import type * as T from '@/constants/types' type NextURI = string @@ -15,5 +15,4 @@ export declare function saveAttachmentToCameraRoll(fileURL: string, mimeType: st export declare function requestLocationPermission(mode: T.RPCChat.UIWatchPositionPerm): Promise export declare function watchPositionForMap(conversationIDKey: T.Chat.ConversationIDKey): Promise<() => void> -export declare function initPlatformListener(): void export declare function requestPermissionsToWrite(): Promise diff --git a/shared/util/platform-specific/index.desktop.tsx b/shared/util/platform-specific/index.desktop.tsx new file mode 100644 index 000000000000..425732a96ab7 --- /dev/null +++ b/shared/util/platform-specific/index.desktop.tsx @@ -0,0 +1,13 @@ +export const requestPermissionsToWrite = async () => { + return Promise.resolve(true) +} + +export function showShareActionSheet() { + throw new Error('Show Share Action - unsupported on this platform') +} +export async function saveAttachmentToCameraRoll() { + return Promise.reject(new Error('Save Attachment to camera roll - unsupported on this platform')) +} + +export const requestLocationPermission = async () => Promise.resolve() +export const watchPositionForMap = async () => Promise.resolve(() => {}) diff --git a/shared/util/platform-specific/index.native.tsx b/shared/util/platform-specific/index.native.tsx new file mode 100644 index 000000000000..8b1f620d5d75 --- /dev/null +++ b/shared/util/platform-specific/index.native.tsx @@ -0,0 +1,111 @@ +import * as T from '@/constants/types' +import * as ExpoLocation from 'expo-location' +import * as MediaLibrary from 'expo-media-library' +import {addNotificationRequest} from 'react-native-kb' +import logger from '@/logger' +import {ActionSheetIOS} from 'react-native' +import {isIOS, isAndroid} from '@/constants/platform.native' +import {androidShare, androidShareText, androidUnlink} from 'react-native-kb' + +export const requestPermissionsToWrite = async () => { + if (isAndroid) { + const p = await MediaLibrary.requestPermissionsAsync(false) + return p.granted ? Promise.resolve() : Promise.reject(new Error('Unable to acquire storage permissions')) + } + return Promise.resolve() +} + +export const requestLocationPermission = async (mode: T.RPCChat.UIWatchPositionPerm) => { + if (isIOS) { + logger.info('[location] Requesting location perms', mode) + switch (mode) { + case T.RPCChat.UIWatchPositionPerm.base: + { + const iosFGPerms = await ExpoLocation.requestForegroundPermissionsAsync() + if (iosFGPerms.ios?.scope === 'none') { + throw new Error('Please allow Keybase to access your location in the phone settings.') + } + } + break + case T.RPCChat.UIWatchPositionPerm.always: { + const iosBGPerms = await ExpoLocation.requestBackgroundPermissionsAsync() + if (iosBGPerms.status !== ExpoLocation.PermissionStatus.GRANTED) { + throw new Error( + 'Please allow Keybase to access your location even if the app is not running for live location.' + ) + } + break + } + } + } else if (isAndroid) { + const androidBGPerms = await ExpoLocation.requestForegroundPermissionsAsync() + if (androidBGPerms.status !== ExpoLocation.PermissionStatus.GRANTED) { + throw new Error('Unable to acquire location permissions') + } + } +} + +export async function saveAttachmentToCameraRoll(filePath: string, mimeType: string): Promise { + const fileURL = 'file://' + filePath + const saveType: 'video' | 'photo' = mimeType.startsWith('video') ? 'video' : 'photo' + const logPrefix = '[saveAttachmentToCameraRoll] ' + try { + try { + // see it we can keep going anyways, android perms are needed sometimes and sometimes not w/ 33 + await requestPermissionsToWrite() + } catch {} + logger.info(logPrefix + `Attempting to save as ${saveType}`) + await MediaLibrary.saveToLibraryAsync(fileURL) + logger.info(logPrefix + 'Success') + } catch (e) { + // This can fail if the user backgrounds too quickly, so throw up a local notification + // just in case to get their attention. + addNotificationRequest({ + body: `Failed to save ${saveType} to camera roll`, + id: Math.floor(Math.random() * 2 ** 32).toString(), + }).catch(() => {}) + logger.debug(logPrefix + 'failed to save: ' + e) + throw e + } finally { + try { + await androidUnlink(filePath) + } catch { + logger.warn('failed to unlink') + } + } +} + +export const showShareActionSheet = async (options: { + filePath?: string + message?: string + mimeType: string +}) => { + if (isIOS) { + return new Promise((resolve, reject) => { + ActionSheetIOS.showShareActionSheetWithOptions( + { + message: options.message, + url: options.filePath, + }, + reject, + resolve + ) + }) + } else { + if (!options.filePath && options.message) { + try { + await androidShareText(options.message, options.mimeType) + return {completed: true, method: ''} + } catch (e) { + throw new Error('Failed to share: ' + String(e)) + } + } + + try { + await androidShare(options.filePath ?? '', options.mimeType) + return {completed: true, method: ''} + } catch (e) { + throw new Error('Failed to share: ' + String(e)) + } + } +} diff --git a/shared/constants/platform-specific/input-monitor.desktop.tsx b/shared/util/platform-specific/input-monitor.desktop.tsx similarity index 100% rename from shared/constants/platform-specific/input-monitor.desktop.tsx rename to shared/util/platform-specific/input-monitor.desktop.tsx diff --git a/shared/constants/platform-specific/kbfs-notifications.tsx b/shared/util/platform-specific/kbfs-notifications.tsx similarity index 98% rename from shared/constants/platform-specific/kbfs-notifications.tsx rename to shared/util/platform-specific/kbfs-notifications.tsx index f06fd2b4c629..1ee3f17113f6 100644 --- a/shared/constants/platform-specific/kbfs-notifications.tsx +++ b/shared/util/platform-specific/kbfs-notifications.tsx @@ -1,5 +1,5 @@ import {pathSep} from '@/constants/platform' -import {storeRegistry} from '@/constants/store-registry' +import {useCurrentUserState} from '@/stores/current-user' import capitalize from 'lodash/capitalize' import * as T from '@/constants/types' import {parseFolderNameToUsers} from '@/util/kbfs' @@ -200,7 +200,7 @@ export function kbfsNotification( let title = `KBFS: ${action}` let body = `Chat or files with ${usernames} ${notification.status}` - const user = storeRegistry.getState('current-user').username + const user = useCurrentUserState.getState().username let rateLimitKey: string const isError = notification.statusCode === T.RPCGen.FSStatusCode.error diff --git a/shared/util/qr-code.tsx b/shared/util/qr-code.tsx new file mode 100644 index 000000000000..4476b5c11fe2 --- /dev/null +++ b/shared/util/qr-code.tsx @@ -0,0 +1,643 @@ +// QR Code Generator — Version 4, ECL L, Byte mode only +// Generates GIF data URL with Keybase blue (#4C8EFF) dark modules +// +// Based on qrcode-generator by Kazuhiko Arase (MIT license) +// https://github.com/nicokoch/qrcode-generator + +// ============================================================ +// GF(2^8) arithmetic for Reed-Solomon error correction +// ============================================================ + +const EXP_TABLE: number[] = [] +const LOG_TABLE: number[] = [] + +for (let i = 0; i < 8; i++) EXP_TABLE[i] = 1 << i +for (let i = 8; i < 256; i++) { + EXP_TABLE[i] = EXP_TABLE[i - 4]! ^ EXP_TABLE[i - 5]! ^ EXP_TABLE[i - 6]! ^ EXP_TABLE[i - 8]! +} +for (let i = 0; i < 255; i++) LOG_TABLE[EXP_TABLE[i]!] = i + +const glog = (n: number) => LOG_TABLE[n]! +const gexp = (n: number): number => { + let v = n + while (v < 0) v += 255 + while (v >= 256) v -= 255 + return EXP_TABLE[v]! +} + +// ============================================================ +// Polynomial operations (Reed-Solomon) +// ============================================================ + +// Strip leading zeros and append `shift` zero terms +function makePoly(num: number[], shift: number): number[] { + let offset = 0 + while (offset < num.length && num[offset] === 0) offset++ + const result = new Array(num.length - offset + shift).fill(0) + for (let i = 0; i < num.length - offset; i++) result[i] = num[i + offset]! + return result +} + +function polyMultiply(a: number[], b: number[]): number[] { + const num = new Array(a.length + b.length - 1).fill(0) + for (let i = 0; i < a.length; i++) { + for (let j = 0; j < b.length; j++) { + num[i + j] = num[i + j]! ^ gexp(glog(a[i]!) + glog(b[j]!)) + } + } + return makePoly(num, 0) +} + +function polyMod(a: number[], b: number[]): number[] { + if (a.length - b.length < 0) return a + const ratio = glog(a[0]!) - glog(b[0]!) + const num = a.slice() + for (let i = 0; i < b.length; i++) { + num[i] = num[i]! ^ gexp(glog(b[i]!) + ratio) + } + return polyMod(makePoly(num, 0), b) +} + +function getErrorCorrectPolynomial(ecLength: number): number[] { + let poly = [1] + for (let i = 0; i < ecLength; i++) { + poly = polyMultiply(poly, makePoly([1, gexp(i)], 0)) + } + return poly +} + +// ============================================================ +// Bit buffer for data encoding +// ============================================================ + +function createBitBuffer() { + const buffer: number[] = [] + let length = 0 + return { + getBuffer: () => buffer, + getLengthInBits: () => length, + put(num: number, len: number) { + for (let i = 0; i < len; i++) { + this.putBit(((num >>> (len - i - 1)) & 1) === 1) + } + }, + putBit(bit: boolean) { + const bufIndex = Math.floor(length / 8) + if (buffer.length <= bufIndex) buffer.push(0) + if (bit) buffer[bufIndex] = buffer[bufIndex]! | (0x80 >>> (length % 8)) + length++ + }, + } +} + +// ============================================================ +// BCH encoding for format information +// ============================================================ + +const G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0) +const G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1) + +function getBCHDigit(data: number): number { + let digit = 0 + let d = data + while (d !== 0) { + digit++ + d >>>= 1 + } + return digit +} + +function getBCHTypeInfo(data: number): number { + let d = data << 10 + while (getBCHDigit(d) - getBCHDigit(G15) >= 0) { + d ^= G15 << (getBCHDigit(d) - getBCHDigit(G15)) + } + return ((data << 10) | d) ^ G15_MASK +} + +// ============================================================ +// Mask patterns +// ============================================================ + +const MASK_FUNCTIONS: Array<(i: number, j: number) => boolean> = [ + (i, j) => (i + j) % 2 === 0, + (i, _j) => i % 2 === 0, + (_i, j) => j % 3 === 0, + (i, j) => (i + j) % 3 === 0, + (i, j) => (Math.floor(i / 2) + Math.floor(j / 3)) % 2 === 0, + (i, j) => ((i * j) % 2) + ((i * j) % 3) === 0, + (i, j) => (((i * j) % 2) + ((i * j) % 3)) % 2 === 0, + (i, j) => (((i * j) % 3) + ((i + j) % 2)) % 2 === 0, +] + +// ============================================================ +// Penalty scoring for mask selection +// ============================================================ + +function getLostPoint(modules: boolean[][], moduleCount: number): number { + let lostPoint = 0 + + // LEVEL1: same-color neighbors + for (let row = 0; row < moduleCount; row++) { + for (let col = 0; col < moduleCount; col++) { + let sameCount = 0 + const dark = modules[row]![col]! + for (let r = -1; r <= 1; r++) { + if (row + r < 0 || moduleCount <= row + r) continue + for (let c = -1; c <= 1; c++) { + if (col + c < 0 || moduleCount <= col + c) continue + if (r === 0 && c === 0) continue + if (dark === modules[row + r]![col + c]!) sameCount++ + } + } + if (sameCount > 5) lostPoint += 3 + sameCount - 5 + } + } + + // LEVEL2: 2×2 same-color blocks + for (let row = 0; row < moduleCount - 1; row++) { + for (let col = 0; col < moduleCount - 1; col++) { + let count = 0 + if (modules[row]![col]!) count++ + if (modules[row + 1]![col]!) count++ + if (modules[row]![col + 1]!) count++ + if (modules[row + 1]![col + 1]!) count++ + if (count === 0 || count === 4) lostPoint += 3 + } + } + + // LEVEL3: 1:1:3:1:1 pattern in rows + for (let row = 0; row < moduleCount; row++) { + for (let col = 0; col < moduleCount - 6; col++) { + if ( + modules[row]![col]! && + !modules[row]![col + 1]! && + modules[row]![col + 2]! && + modules[row]![col + 3]! && + modules[row]![col + 4]! && + !modules[row]![col + 5]! && + modules[row]![col + 6]! + ) { + lostPoint += 40 + } + } + } + + // LEVEL3: 1:1:3:1:1 pattern in columns + for (let col = 0; col < moduleCount; col++) { + for (let row = 0; row < moduleCount - 6; row++) { + if ( + modules[row]![col]! && + !modules[row + 1]![col]! && + modules[row + 2]![col]! && + modules[row + 3]![col]! && + modules[row + 4]![col]! && + !modules[row + 5]![col]! && + modules[row + 6]![col]! + ) { + lostPoint += 40 + } + } + } + + // LEVEL4: dark/light ratio + let darkCount = 0 + for (let col = 0; col < moduleCount; col++) { + for (let row = 0; row < moduleCount; row++) { + if (modules[row]![col]!) darkCount++ + } + } + const ratio = Math.abs((100 * darkCount) / moduleCount / moduleCount - 50) / 5 + lostPoint += ratio * 10 + + return lostPoint +} + +// ============================================================ +// QR matrix construction (Version 4, 33×33) +// ============================================================ + +const MODULE_COUNT = 33 // Version 4: 4*4 + 17 +const ECL_L = 1 + +function createModules(): Array> { + const modules: Array> = [] + for (let row = 0; row < MODULE_COUNT; row++) { + modules[row] = new Array(MODULE_COUNT).fill(null) + } + return modules +} + +function setupFinderPattern(modules: Array>, row: number, col: number) { + for (let r = -1; r <= 7; r++) { + if (row + r <= -1 || MODULE_COUNT <= row + r) continue + for (let c = -1; c <= 7; c++) { + if (col + c <= -1 || MODULE_COUNT <= col + c) continue + if ( + (0 <= r && r <= 6 && (c === 0 || c === 6)) || + (0 <= c && c <= 6 && (r === 0 || r === 6)) || + (2 <= r && r <= 4 && 2 <= c && c <= 4) + ) { + modules[row + r]![col + c] = true + } else { + modules[row + r]![col + c] = false + } + } + } +} + +function setupAlignmentPattern(modules: Array>) { + // Version 4 alignment positions: [6, 26] + const pos = [6, 26] + for (const pRow of pos) { + for (const pCol of pos) { + if (modules[pRow]![pCol] !== null) continue + for (let r = -2; r <= 2; r++) { + for (let c = -2; c <= 2; c++) { + modules[pRow + r]![pCol + c] = + r === -2 || r === 2 || c === -2 || c === 2 || (r === 0 && c === 0) + } + } + } + } +} + +function setupTimingPattern(modules: Array>) { + for (let r = 8; r < MODULE_COUNT - 8; r++) { + if (modules[r]![6] !== null) continue + modules[r]![6] = r % 2 === 0 + } + for (let c = 8; c < MODULE_COUNT - 8; c++) { + if (modules[6]![c] !== null) continue + modules[6]![c] = c % 2 === 0 + } +} + +function setupTypeInfo( + modules: Array>, + test: boolean, + maskPattern: number +) { + const data = (ECL_L << 3) | maskPattern + const bits = getBCHTypeInfo(data) + + for (let i = 0; i < 15; i++) { + const mod = !test && ((bits >> i) & 1) === 1 + if (i < 6) { + modules[i]![8] = mod + } else if (i < 8) { + modules[i + 1]![8] = mod + } else { + modules[MODULE_COUNT - 15 + i]![8] = mod + } + } + + for (let i = 0; i < 15; i++) { + const mod = !test && ((bits >> i) & 1) === 1 + if (i < 8) { + modules[8]![MODULE_COUNT - i - 1] = mod + } else if (i < 9) { + modules[8]![15 - i - 1 + 1] = mod + } else { + modules[8]![15 - i - 1] = mod + } + } + + modules[MODULE_COUNT - 8]![8] = !test +} + +function mapData( + modules: Array>, + data: number[], + maskPattern: number +) { + let inc = -1 + let row = MODULE_COUNT - 1 + let bitIndex = 7 + let byteIndex = 0 + const maskFunc = MASK_FUNCTIONS[maskPattern]! + + for (let col = MODULE_COUNT - 1; col > 0; col -= 2) { + if (col === 6) col -= 1 + while (true) { + for (let c = 0; c < 2; c++) { + if (modules[row]![col - c] === null) { + let dark = false + if (byteIndex < data.length) { + dark = ((data[byteIndex]! >>> bitIndex) & 1) === 1 + } + if (maskFunc(row, col - c)) dark = !dark + modules[row]![col - c] = dark + bitIndex-- + if (bitIndex === -1) { + byteIndex++ + bitIndex = 7 + } + } + } + row += inc + if (row < 0 || MODULE_COUNT <= row) { + row -= inc + inc = -inc + break + } + } + } +} + +// ============================================================ +// Data encoding and Reed-Solomon error correction +// ============================================================ + +function encodeData(str: string): number[] { + const PAD0 = 0xec + const PAD1 = 0x11 + + // Convert string to bytes (charCode & 0xff, matching original library) + const bytes: number[] = [] + for (let i = 0; i < str.length; i++) bytes.push(str.charCodeAt(i) & 0xff) + + const buffer = createBitBuffer() + + // Byte mode indicator: MODE_8BIT_BYTE = 4 + buffer.put(4, 4) + + // Character count (8 bits for versions 1-9) + buffer.put(bytes.length, 8) + + // Data bytes + for (const b of bytes) buffer.put(b, 8) + + // Total data capacity: 80 bytes = 640 bits (version 4, ECL L) + const totalDataBits = 80 * 8 + + // Terminator + if (buffer.getLengthInBits() + 4 <= totalDataBits) { + buffer.put(0, 4) + } + + // Byte-align + while (buffer.getLengthInBits() % 8 !== 0) { + buffer.putBit(false) + } + + // Pad to capacity + while (buffer.getLengthInBits() < totalDataBits) { + buffer.put(PAD0, 8) + if (buffer.getLengthInBits() >= totalDataBits) break + buffer.put(PAD1, 8) + } + + // Reed-Solomon error correction + const ecCount = 20 + const dataCount = 80 + const dcData: number[] = [] + for (let i = 0; i < dataCount; i++) dcData.push(0xff & buffer.getBuffer()[i]!) + + const rsPoly = getErrorCorrectPolynomial(ecCount) + const rawPoly = makePoly(dcData, rsPoly.length - 1) + const modPoly = polyMod(rawPoly, rsPoly) + + const ecData: number[] = [] + for (let i = 0; i < rsPoly.length - 1; i++) { + const modIndex = i + modPoly.length - (rsPoly.length - 1) + ecData.push(modIndex >= 0 ? modPoly[modIndex]! : 0) + } + + // Single block: data codewords then EC codewords + return [...dcData, ...ecData] +} + +// ============================================================ +// Build complete QR matrix +// ============================================================ + +function buildMatrix(data: string): boolean[][] { + const codewords = encodeData(data) + + // Evaluate all 8 mask patterns to find best + let bestMask = 0 + let minPenalty = Infinity + + for (let mask = 0; mask < 8; mask++) { + const modules = createModules() + setupFinderPattern(modules, 0, 0) + setupFinderPattern(modules, MODULE_COUNT - 7, 0) + setupFinderPattern(modules, 0, MODULE_COUNT - 7) + setupAlignmentPattern(modules) + setupTimingPattern(modules) + setupTypeInfo(modules, true, mask) + mapData(modules, codewords, mask) + + const penalty = getLostPoint(modules as boolean[][], MODULE_COUNT) + if (mask === 0 || minPenalty > penalty) { + minPenalty = penalty + bestMask = mask + } + } + + // Build final matrix with best mask + const modules = createModules() + setupFinderPattern(modules, 0, 0) + setupFinderPattern(modules, MODULE_COUNT - 7, 0) + setupFinderPattern(modules, 0, MODULE_COUNT - 7) + setupAlignmentPattern(modules) + setupTimingPattern(modules) + setupTypeInfo(modules, false, bestMask) + mapData(modules, codewords, bestMask) + + return modules as boolean[][] +} + +// ============================================================ +// GIF generation with LZW compression +// ============================================================ + +function getLZWRaster(data: number[], lzwMinCodeSize: number): number[] { + const clearCode = 1 << lzwMinCodeSize + const endCode = (1 << lzwMinCodeSize) + 1 + let bitLength = lzwMinCodeSize + 1 + + // LZW table using string keys (matching original library exactly) + const map: {[key: string]: number} = {} + let tableSize = 0 + const tableAdd = (key: string) => { + map[key] = tableSize + tableSize++ + } + + for (let i = 0; i < clearCode; i++) tableAdd(String.fromCharCode(i)) + tableAdd(String.fromCharCode(clearCode)) + tableAdd(String.fromCharCode(endCode)) + + // LSB-first bit output stream + const outBytes: number[] = [] + let bitBuffer = 0 + let bitBufLen = 0 + + const writeBits = (d: number, len: number) => { + let vd = d + let vl = len + while (bitBufLen + vl >= 8) { + outBytes.push(0xff & ((vd << bitBufLen) | bitBuffer)) + vl -= 8 - bitBufLen + vd >>>= 8 - bitBufLen + bitBuffer = 0 + bitBufLen = 0 + } + bitBuffer = (vd << bitBufLen) | bitBuffer + bitBufLen += vl + } + + // Write clear code + writeBits(clearCode, bitLength) + + let dataIndex = 0 + let s = String.fromCharCode(data[dataIndex]!) + dataIndex++ + + while (dataIndex < data.length) { + const c = String.fromCharCode(data[dataIndex]!) + dataIndex++ + + if (s + c in map) { + s = s + c + } else { + writeBits(map[s]!, bitLength) + if (tableSize < 0xfff) { + if (tableSize === 1 << bitLength) bitLength++ + tableAdd(s + c) + } + s = c + } + } + + writeBits(map[s]!, bitLength) + writeBits(endCode, bitLength) + + // Flush remaining bits + if (bitBufLen > 0) outBytes.push(bitBuffer) + + return outBytes +} + +// ============================================================ +// Base64 encoding (matching original library's custom encoder) +// ============================================================ + +const B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + +function base64Encode(bytes: number[]): string { + let buffer = 0 + let buflen = 0 + let result = '' + let length = 0 + + for (const byte of bytes) { + buffer = (buffer << 8) | (byte & 0xff) + buflen += 8 + length++ + while (buflen >= 6) { + result += B64.charAt((buffer >>> (buflen - 6)) & 0x3f) + buflen -= 6 + } + } + + if (buflen > 0) { + result += B64.charAt((buffer << (6 - buflen)) & 0x3f) + } + + if (length % 3 !== 0) { + const padlen = 3 - (length % 3) + for (let i = 0; i < padlen; i++) result += '=' + } + + return result +} + +// ============================================================ +// Main exported function +// ============================================================ + +export default function generateQRDataURL( + data: string, + cellSize: number +): {url: string; moduleCount: number} { + const modules = buildMatrix(data) + const margin = cellSize * 4 + const size = MODULE_COUNT * cellSize + margin * 2 + const min = margin + const max = size - margin + + // Build pixel data (0 = blue/dark, 1 = white/light) + const pixelData = new Array(size * size) + for (let y = 0; y < size; y++) { + for (let x = 0; x < size; x++) { + if (min <= x && x < max && min <= y && y < max) { + const c = Math.floor((x - min) / cellSize) + const r = Math.floor((y - min) / cellSize) + pixelData[y * size + x] = modules[r]![c]! ? 0 : 1 + } else { + pixelData[y * size + x] = 1 + } + } + } + + // Build GIF + const out: number[] = [] + const writeByte = (b: number) => out.push(b & 0xff) + const writeShort = (i: number) => { + writeByte(i) + writeByte(i >>> 8) + } + const writeString = (s: string) => { + for (let i = 0; i < s.length; i++) writeByte(s.charCodeAt(i)) + } + + // GIF87a header + writeString('GIF87a') + writeShort(size) + writeShort(size) + writeByte(0x80) // GCT flag, 2 colors + writeByte(0) + writeByte(0) + + // Global Color Table: index 0 = Keybase blue, index 1 = white + writeByte(0x4c) + writeByte(0x8e) + writeByte(0xff) + writeByte(0xff) + writeByte(0xff) + writeByte(0xff) + + // Image Descriptor + writeString(',') + writeShort(0) + writeShort(0) + writeShort(size) + writeShort(size) + writeByte(0) + + // LZW compressed raster data + const lzwMinCodeSize = 2 + const raster = getLZWRaster(pixelData, lzwMinCodeSize) + writeByte(lzwMinCodeSize) + + let offset = 0 + while (raster.length - offset > 255) { + writeByte(255) + for (let i = 0; i < 255; i++) writeByte(raster[i + offset]!) + offset += 255 + } + writeByte(raster.length - offset) + for (let i = 0; i < raster.length - offset; i++) writeByte(raster[i + offset]!) + writeByte(0x00) + + // GIF Terminator + writeString(';') + + return { + moduleCount: MODULE_COUNT, + url: 'data:image/gif;base64,' + base64Encode(out), + } +} diff --git a/shared/util/zustand.tsx b/shared/util/zustand.tsx index 340308eb6d95..b3d223636d5b 100644 --- a/shared/util/zustand.tsx +++ b/shared/util/zustand.tsx @@ -9,7 +9,13 @@ import {wrapErrors} from '@/util/debug' // needed for tsc export type {WritableDraft} from 'immer' -type HasReset = {dispatch: {resetDeleteMe?: boolean; resetState: 'default' | (() => void)}} +type HasReset = { + dispatch: { + defer?: Record + resetDeleteMe?: boolean + resetState: 'default' | (() => void) + } +} const resetters: ((isDebug?: boolean) => void)[] = [] const resettersAndDelete: ((isDebug?: boolean) => void)[] = [] @@ -39,8 +45,9 @@ export const createZustand = ( let resetFunc: () => void if (reset === 'default') { resetFunc = () => { + const currentDefer = store.getState().dispatch.defer // eslint-disable-next-line - store.setState(initialState as any, true) + store.setState({...initialState, dispatch: {...initialState.dispatch, defer: currentDefer}} as any, true) } } else { resetFunc = reset diff --git a/shared/wallets/index.tsx b/shared/wallets/index.tsx index 796c7dc738ab..9cdbccfe61df 100644 --- a/shared/wallets/index.tsx +++ b/shared/wallets/index.tsx @@ -2,8 +2,8 @@ import * as C from '@/constants' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' -import * as Wallets from '@/constants/wallets' -import {useState as useWalletsState} from '@/constants/wallets' +import * as Wallets from '@/stores/wallets' +import {useState as useWalletsState} from '@/stores/wallets' const Row = (p: {account: Wallets.Account}) => { const {account} = p diff --git a/shared/wallets/really-remove-account.tsx b/shared/wallets/really-remove-account.tsx index e72a8cf90bba..aa7b88fb2143 100644 --- a/shared/wallets/really-remove-account.tsx +++ b/shared/wallets/really-remove-account.tsx @@ -3,9 +3,9 @@ import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import * as React from 'react' import WalletPopup from './wallet-popup' -import * as Wallets from '@/constants/wallets' -import {useState as useWalletsState} from '@/constants/wallets' -import {useConfigState} from '@/constants/config' +import * as Wallets from '@/stores/wallets' +import {useState as useWalletsState} from '@/stores/wallets' +import {useConfigState} from '@/stores/config' type OwnProps = {accountID: string} @@ -17,7 +17,7 @@ const ReallyRemoveAccountPopup = (props: OwnProps) => { const attachmentRef = React.useRef(null) const setShowToastFalseLater = Kb.useTimeout(() => setShowToast(false), 2000) - const copyToClipboard = useConfigState(s => s.dispatch.dynamic.copyToClipboard) + const copyToClipboard = useConfigState(s => s.dispatch.defer.copyToClipboard) const [sk, setSK] = React.useState('') const loading = !sk diff --git a/shared/wallets/remove-account.tsx b/shared/wallets/remove-account.tsx index 6088a87265c4..4847147c1825 100644 --- a/shared/wallets/remove-account.tsx +++ b/shared/wallets/remove-account.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as Kb from '@/common-adapters' import WalletPopup from './wallet-popup' -import {useState as useWalletsState} from '@/constants/wallets' +import {useState as useWalletsState} from '@/stores/wallets' type OwnProps = {accountID: string} diff --git a/shared/whats-new/container.tsx b/shared/whats-new/container.tsx index 605376435ff3..5f2fbf69501d 100644 --- a/shared/whats-new/container.tsx +++ b/shared/whats-new/container.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' import openURL from '@/util/open-url' -import {currentVersion} from '@/constants/whats-new' +import {currentVersion} from '@/stores/whats-new' import {Current, Last, LastLast} from './versions' import WhatsNew from '.' -import {useWhatsNewState as useWNState} from '@/constants/whats-new' -import {useConfigState} from '@/constants/config' +import {useWhatsNewState as useWNState} from '@/stores/whats-new' +import {useConfigState} from '@/stores/config' const WhatsNewContainer = () => { const _onNavigateExternal = (url: string) => { diff --git a/shared/whats-new/icon/index.tsx b/shared/whats-new/icon/index.tsx index 05ca4fb3b455..08ee61d17832 100644 --- a/shared/whats-new/icon/index.tsx +++ b/shared/whats-new/icon/index.tsx @@ -1,10 +1,10 @@ import * as React from 'react' import * as Kb from '@/common-adapters' import type {IconStyle} from '@/common-adapters/icon' -import {keybaseFM} from '@/constants/whats-new' +import {keybaseFM} from '@/stores/whats-new' import Popup from './popup' import './icon.css' -import {useWhatsNewState as useWNState} from '@/constants/whats-new' +import {useWhatsNewState as useWNState} from '@/stores/whats-new' type OwnProps = { color?: string diff --git a/shared/whats-new/index.tsx b/shared/whats-new/index.tsx index f52f93af4ef6..f15609db9263 100644 --- a/shared/whats-new/index.tsx +++ b/shared/whats-new/index.tsx @@ -1,7 +1,7 @@ import type * as React from 'react' import * as Kb from '@/common-adapters' import type * as C from '@/constants' -import {currentVersion, lastVersion, lastLastVersion} from '@/constants/whats-new' +import {currentVersion, lastVersion, lastLastVersion} from '@/stores/whats-new' import type {VersionProps} from './versions' type Props = { diff --git a/shared/whats-new/versions.tsx b/shared/whats-new/versions.tsx index cd3763ab6b12..ddf79961c28c 100644 --- a/shared/whats-new/versions.tsx +++ b/shared/whats-new/versions.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' -import {encryptTab} from '@/constants/crypto/util' +import {encryptTab} from '@/constants/crypto' import type * as React from 'react' import * as Kb from '@/common-adapters' -import {keybaseFM} from '@/constants/whats-new' +import {keybaseFM} from '@/stores/whats-new' import NewFeatureRow from './new-feature-row' -import {settingsCryptoTab, settingsDisplayTab} from '@/constants/settings/util' +import {settingsCryptoTab, settingsDisplayTab} from '@/constants/settings' export type VersionProps = { seen: boolean diff --git a/shared/yarn.lock b/shared/yarn.lock index b1bcd0b269be..0aab599e89da 100644 --- a/shared/yarn.lock +++ b/shared/yarn.lock @@ -7,41 +7,41 @@ resolved "https://registry.yarnpkg.com/@0no-co/graphql.web/-/graphql.web-1.2.0.tgz#296d00581bfaaabfda1e976849d927824aaea81b" integrity sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw== -"@babel/code-frame@7.10.4", "@babel/code-frame@~7.10.4": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.20.0", "@babel/code-frame@^7.24.7", "@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" + integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== + dependencies: + "@babel/helper-validator-identifier" "^7.28.5" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/code-frame@~7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.20.0", "@babel/code-frame@^7.24.7", "@babel/code-frame@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== - dependencies: - "@babel/helper-validator-identifier" "^7.27.1" - js-tokens "^4.0.0" - picocolors "^1.1.1" - -"@babel/compat-data@^7.27.2", "@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.5.tgz#a8a4962e1567121ac0b3b487f52107443b455c7f" - integrity sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA== - -"@babel/core@7.28.5", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.20.0", "@babel/core@^7.24.4", "@babel/core@^7.25.2", "@babel/core@^7.26.0": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.5.tgz#4c81b35e51e1b734f510c99b07dfbc7bbbb48f7e" - integrity sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.5" - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-module-transforms" "^7.28.3" - "@babel/helpers" "^7.28.4" - "@babel/parser" "^7.28.5" - "@babel/template" "^7.27.2" - "@babel/traverse" "^7.28.5" - "@babel/types" "^7.28.5" +"@babel/compat-data@^7.28.6", "@babel/compat-data@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d" + integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== + +"@babel/core@7.29.0", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.20.0", "@babel/core@^7.24.4", "@babel/core@^7.25.2", "@babel/core@^7.26.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322" + integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helpers" "^7.28.6" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" "@jridgewell/remapping" "^2.3.5" convert-source-map "^2.0.0" debug "^4.1.0" @@ -50,21 +50,21 @@ semver "^6.3.1" "@babel/eslint-parser@^7.25.1": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.28.5.tgz#0b8883a4a1c2cbed7b3cd9d7765d80e8f480b9ae" - integrity sha512-fcdRcWahONYo+JRnJg1/AekOacGvKx12Gu0qXJXFi2WBqQA1i7+O5PaxRB7kxE/Op94dExnCiiar6T09pvdHpA== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.28.6.tgz#6a294a4add732ebe7ded8a8d2792dd03dd81dc3f" + integrity sha512-QGmsKi2PBO/MHSQk+AAgA9R6OHQr+VqnniFE0eMWZcVcfBZoA2dKn2hUsl3Csg/Plt9opRUWdY7//VXsrIlEiA== dependencies: "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" eslint-visitor-keys "^2.1.0" semver "^6.3.1" -"@babel/generator@^7.20.5", "@babel/generator@^7.25.0", "@babel/generator@^7.26.2", "@babel/generator@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.5.tgz#712722d5e50f44d07bc7ac9fe84438742dd61298" - integrity sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ== +"@babel/generator@^7.20.5", "@babel/generator@^7.25.0", "@babel/generator@^7.26.2", "@babel/generator@^7.29.0": + version "7.29.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.1.tgz#d09876290111abbb00ef962a7b83a5307fba0d50" + integrity sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw== dependencies: - "@babel/parser" "^7.28.5" - "@babel/types" "^7.28.5" + "@babel/parser" "^7.29.0" + "@babel/types" "^7.29.0" "@jridgewell/gen-mapping" "^0.3.12" "@jridgewell/trace-mapping" "^0.3.28" jsesc "^3.0.2" @@ -76,31 +76,31 @@ dependencies: "@babel/types" "^7.27.3" -"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" - integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== +"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2", "@babel/helper-compilation-targets@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" + integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== dependencies: - "@babel/compat-data" "^7.27.2" + "@babel/compat-data" "^7.28.6" "@babel/helper-validator-option" "^7.27.1" browserslist "^4.24.0" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.27.1", "@babel/helper-create-class-features-plugin@^7.28.3", "@babel/helper-create-class-features-plugin@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz#472d0c28028850968979ad89f173594a6995da46" - integrity sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ== +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.27.1", "@babel/helper-create-class-features-plugin@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz#611ff5482da9ef0db6291bcd24303400bca170fb" + integrity sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow== dependencies: "@babel/helper-annotate-as-pure" "^7.27.3" "@babel/helper-member-expression-to-functions" "^7.28.5" "@babel/helper-optimise-call-expression" "^7.27.1" - "@babel/helper-replace-supers" "^7.27.1" + "@babel/helper-replace-supers" "^7.28.6" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - "@babel/traverse" "^7.28.5" + "@babel/traverse" "^7.28.6" semver "^6.3.1" -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.27.1": +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.27.1", "@babel/helper-create-regexp-features-plugin@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz#7c1ddd64b2065c7f78034b25b43346a7e19ed997" integrity sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw== @@ -109,23 +109,23 @@ regexpu-core "^6.3.1" semver "^6.3.1" -"@babel/helper-define-polyfill-provider@^0.6.5": - version "0.6.5" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz#742ccf1cb003c07b48859fc9fa2c1bbe40e5f753" - integrity sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg== +"@babel/helper-define-polyfill-provider@^0.6.5", "@babel/helper-define-polyfill-provider@^0.6.6": + version "0.6.6" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz#714dfe33d8bd710f556df59953720f6eeb6c1a14" + integrity sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA== dependencies: - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-plugin-utils" "^7.27.1" - debug "^4.4.1" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + debug "^4.4.3" lodash.debounce "^4.0.8" - resolve "^1.22.10" + resolve "^1.22.11" "@babel/helper-globals@^7.28.0": version "7.28.0" resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== -"@babel/helper-member-expression-to-functions@^7.27.1", "@babel/helper-member-expression-to-functions@^7.28.5": +"@babel/helper-member-expression-to-functions@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz#f3e07a10be37ed7a63461c63e6929575945a6150" integrity sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg== @@ -133,22 +133,22 @@ "@babel/traverse" "^7.28.5" "@babel/types" "^7.28.5" -"@babel/helper-module-imports@^7.25.9", "@babel/helper-module-imports@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" - integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== +"@babel/helper-module-imports@^7.25.9", "@babel/helper-module-imports@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" + integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" -"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" - integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== +"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" + integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@babel/traverse" "^7.28.3" + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.6" "@babel/helper-optimise-call-expression@^7.27.1": version "7.27.1" @@ -157,10 +157,10 @@ dependencies: "@babel/types" "^7.27.1" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" - integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.28.6", "@babel/helper-plugin-utils@^7.8.0": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8" + integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== "@babel/helper-remap-async-to-generator@^7.27.1": version "7.27.1" @@ -171,14 +171,14 @@ "@babel/helper-wrap-function" "^7.27.1" "@babel/traverse" "^7.27.1" -"@babel/helper-replace-supers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz#b1ed2d634ce3bdb730e4b52de30f8cccfd692bc0" - integrity sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA== +"@babel/helper-replace-supers@^7.27.1", "@babel/helper-replace-supers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz#94aa9a1d7423a00aead3f204f78834ce7d53fe44" + integrity sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg== dependencies: - "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-member-expression-to-functions" "^7.28.5" "@babel/helper-optimise-call-expression" "^7.27.1" - "@babel/traverse" "^7.27.1" + "@babel/traverse" "^7.28.6" "@babel/helper-skip-transparent-expression-wrappers@^7.27.1": version "7.27.1" @@ -193,7 +193,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== -"@babel/helper-validator-identifier@^7.25.9", "@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5": +"@babel/helper-validator-identifier@^7.25.9", "@babel/helper-validator-identifier@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== @@ -204,21 +204,21 @@ integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== "@babel/helper-wrap-function@^7.27.1": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz#fe4872092bc1438ffd0ce579e6f699609f9d0a7a" - integrity sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz#4e349ff9222dab69a93a019cc296cdd8442e279a" + integrity sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ== dependencies: - "@babel/template" "^7.27.2" - "@babel/traverse" "^7.28.3" - "@babel/types" "^7.28.2" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" -"@babel/helpers@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" - integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== +"@babel/helpers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.6.tgz#fca903a313ae675617936e8998b814c415cbf5d7" + integrity sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw== dependencies: - "@babel/template" "^7.27.2" - "@babel/types" "^7.28.4" + "@babel/template" "^7.28.6" + "@babel/types" "^7.28.6" "@babel/highlight@^7.10.4": version "7.25.9" @@ -230,24 +230,24 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/node@7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/node/-/node-7.28.0.tgz#fe52d05121ca064e6919215ffe54fea480a8746f" - integrity sha512-6u1Mmn3SIMUH8uwTq543L062X3JDgms9HPf06o/pIGdDjeD/zNQ+dfZPQD27sCyvtP0ZOlJtwnl2RIdPe9bHeQ== +"@babel/node@7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/node/-/node-7.29.0.tgz#278d903d17bf80d361ed6ac9552125eebd5eab5f" + integrity sha512-9UeU8F3rx2lOZXneEW2HTnTYdA8+fXP0kr54tk7d0fPomWNlZ6WJ2H9lunr5dSvr8FNY0CDnop3Km6jZ5NAUsQ== dependencies: - "@babel/register" "^7.27.1" + "@babel/register" "^7.28.6" commander "^6.2.0" - core-js "^3.30.2" + core-js "^3.48.0" node-environment-flags "^1.0.5" regenerator-runtime "^0.14.0" v8flags "^3.1.1" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.24.4", "@babel/parser@^7.25.3", "@babel/parser@^7.27.2", "@babel/parser@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.5.tgz#0b0225ee90362f030efd644e8034c99468893b08" - integrity sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.24.4", "@babel/parser@^7.25.3", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.0.tgz#669ef345add7d057e92b7ed15f0bac07611831b6" + integrity sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww== dependencies: - "@babel/types" "^7.28.5" + "@babel/types" "^7.29.0" "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.28.5": version "7.28.5" @@ -280,22 +280,22 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" "@babel/plugin-transform-optional-chaining" "^7.27.1" -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz#373f6e2de0016f73caf8f27004f61d167743742a" - integrity sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw== +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz#0e8289cec28baaf05d54fd08d81ae3676065f69f" + integrity sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/traverse" "^7.28.3" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/traverse" "^7.28.6" "@babel/plugin-proposal-decorators@^7.12.9": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.28.0.tgz#419c8acc31088e05a774344c021800f7ddc39bf0" - integrity sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg== + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.0.tgz#d159f26f78740e47bf3ef075882b155b2d54ca81" + integrity sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-syntax-decorators" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-syntax-decorators" "^7.28.6" "@babel/plugin-proposal-export-default-from@^7.24.7": version "7.27.1" @@ -345,12 +345,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-decorators@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz#ee7dd9590aeebc05f9d4c8c0560007b05979a63d" - integrity sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A== +"@babel/plugin-syntax-decorators@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz#8c3293a0fef033e4c786b35ce1e159fc1d676153" + integrity sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" @@ -360,32 +360,32 @@ "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-export-default-from@^7.24.7": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.27.1.tgz#8efed172e79ab657c7fa4d599224798212fb7e18" - integrity sha512-eBC/3KSekshx19+N40MzjWqJd7KTEdOoLesAfa4IDFI8eRz5a47i5Oszus6zG/cwIXN63YhgLOMSSNJx49sENg== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.28.6.tgz#8e19047560a8a48b11f1f5b46881f445f8692830" + integrity sha512-Svlx1fjJFnNz0LZeUaybRukSxZI3KkpApUmIRzEdXC5k8ErTOz0OD0kNrICi5Vc3GlpP5ZCeRyRO+mfWTSz+iQ== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-syntax-flow@^7.12.1", "@babel/plugin-syntax-flow@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.27.1.tgz#6c83cf0d7d635b716827284b7ecd5aead9237662" - integrity sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.28.6.tgz#447559a225e66c4cd477a3ffb1a74d8c1fe25a62" + integrity sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-syntax-import-assertions@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz#88894aefd2b03b5ee6ad1562a7c8e1587496aecd" - integrity sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg== +"@babel/plugin-syntax-import-assertions@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz#ae9bc1923a6ba527b70104dd2191b0cd872c8507" + integrity sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-syntax-import-attributes@^7.24.7", "@babel/plugin-syntax-import-attributes@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" - integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== +"@babel/plugin-syntax-import-attributes@^7.24.7", "@babel/plugin-syntax-import-attributes@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz#b71d5914665f60124e133696f17cd7669062c503" + integrity sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-syntax-import-meta@^7.10.4": version "7.10.4" @@ -401,12 +401,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" - integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== +"@babel/plugin-syntax-jsx@^7.27.1", "@babel/plugin-syntax-jsx@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz#f8ca28bbd84883b5fea0e447c635b81ba73997ee" + integrity sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" @@ -464,12 +464,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" - integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== +"@babel/plugin-syntax-typescript@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz#c7b2ddf1d0a811145b1de800d1abd146af92e3a2" + integrity sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" @@ -486,22 +486,22 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-async-generator-functions@^7.25.4", "@babel/plugin-transform-async-generator-functions@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz#1276e6c7285ab2cd1eccb0bc7356b7a69ff842c2" - integrity sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q== +"@babel/plugin-transform-async-generator-functions@^7.25.4", "@babel/plugin-transform-async-generator-functions@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz#63ed829820298f0bf143d5a4a68fb8c06ffd742f" + integrity sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-remap-async-to-generator" "^7.27.1" - "@babel/traverse" "^7.28.0" + "@babel/traverse" "^7.29.0" -"@babel/plugin-transform-async-to-generator@^7.24.7", "@babel/plugin-transform-async-to-generator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz#9a93893b9379b39466c74474f55af03de78c66e7" - integrity sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA== +"@babel/plugin-transform-async-to-generator@^7.24.7", "@babel/plugin-transform-async-to-generator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz#bd97b42237b2d1bc90d74bcb486c39be5b4d7e77" + integrity sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g== dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-remap-async-to-generator" "^7.27.1" "@babel/plugin-transform-block-scoped-functions@^7.27.1": @@ -511,14 +511,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-block-scoping@^7.25.0", "@babel/plugin-transform-block-scoping@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz#e0d3af63bd8c80de2e567e690a54e84d85eb16f6" - integrity sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g== +"@babel/plugin-transform-block-scoping@^7.25.0", "@babel/plugin-transform-block-scoping@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz#e1ef5633448c24e76346125c2534eeb359699a99" + integrity sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-class-properties@7.27.1", "@babel/plugin-transform-class-properties@^7.25.4", "@babel/plugin-transform-class-properties@^7.27.1": +"@babel/plugin-transform-class-properties@7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz#dd40a6a370dfd49d32362ae206ddaf2bb082a925" integrity sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA== @@ -526,15 +526,23 @@ "@babel/helper-create-class-features-plugin" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-class-static-block@^7.27.1", "@babel/plugin-transform-class-static-block@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz#d1b8e69b54c9993bc558203e1f49bfc979bfd852" - integrity sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg== +"@babel/plugin-transform-class-properties@^7.25.4", "@babel/plugin-transform-class-properties@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz#d274a4478b6e782d9ea987fda09bdb6d28d66b72" + integrity sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.28.3" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-classes@7.28.4", "@babel/plugin-transform-classes@^7.25.4", "@babel/plugin-transform-classes@^7.28.4": +"@babel/plugin-transform-class-static-block@^7.27.1", "@babel/plugin-transform-class-static-block@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz#1257491e8259c6d125ac4d9a6f39f9d2bf3dba70" + integrity sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-classes@7.28.4": version "7.28.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz#75d66175486788c56728a73424d67cbc7473495c" integrity sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA== @@ -546,15 +554,27 @@ "@babel/helper-replace-supers" "^7.27.1" "@babel/traverse" "^7.28.4" -"@babel/plugin-transform-computed-properties@^7.24.7", "@babel/plugin-transform-computed-properties@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz#81662e78bf5e734a97982c2b7f0a793288ef3caa" - integrity sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw== +"@babel/plugin-transform-classes@^7.25.4", "@babel/plugin-transform-classes@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz#8f6fb79ba3703978e701ce2a97e373aae7dda4b7" + integrity sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/template" "^7.27.1" + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-globals" "^7.28.0" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-replace-supers" "^7.28.6" + "@babel/traverse" "^7.28.6" -"@babel/plugin-transform-destructuring@^7.24.8", "@babel/plugin-transform-destructuring@^7.28.0", "@babel/plugin-transform-destructuring@^7.28.5": +"@babel/plugin-transform-computed-properties@^7.24.7", "@babel/plugin-transform-computed-properties@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz#936824fc71c26cb5c433485776d79c8e7b0202d2" + integrity sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/template" "^7.28.6" + +"@babel/plugin-transform-destructuring@^7.24.8", "@babel/plugin-transform-destructuring@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz#b8402764df96179a2070bb7b501a1586cf8ad7a7" integrity sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw== @@ -562,13 +582,13 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/traverse" "^7.28.5" -"@babel/plugin-transform-dotall-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz#aa6821de864c528b1fecf286f0a174e38e826f4d" - integrity sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw== +"@babel/plugin-transform-dotall-regex@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz#def31ed84e0fb6e25c71e53c124e7b76a4ab8e61" + integrity sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-duplicate-keys@^7.27.1": version "7.27.1" @@ -577,13 +597,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz#5043854ca620a94149372e69030ff8cb6a9eb0ec" - integrity sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ== +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz#8014b8a6cfd0e7b92762724443bf0d2400f26df1" + integrity sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-dynamic-import@^7.27.1": version "7.27.1" @@ -592,20 +612,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-explicit-resource-management@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz#45be6211b778dbf4b9d54c4e8a2b42fa72e09a1a" - integrity sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ== +"@babel/plugin-transform-explicit-resource-management@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz#dd6788f982c8b77e86779d1d029591e39d9d8be7" + integrity sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-transform-destructuring" "^7.28.0" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-transform-destructuring" "^7.28.5" -"@babel/plugin-transform-exponentiation-operator@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz#7cc90a8170e83532676cfa505278e147056e94fe" - integrity sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw== +"@babel/plugin-transform-exponentiation-operator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz#5e477eb7eafaf2ab5537a04aaafcf37e2d7f1091" + integrity sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-export-namespace-from@^7.25.9", "@babel/plugin-transform-export-namespace-from@^7.27.1": version "7.27.1" @@ -639,12 +659,12 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/traverse" "^7.27.1" -"@babel/plugin-transform-json-strings@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz#a2e0ce6ef256376bd527f290da023983527a4f4c" - integrity sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q== +"@babel/plugin-transform-json-strings@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz#4c8c15b2dc49e285d110a4cf3dac52fd2dfc3038" + integrity sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-literals@^7.25.2", "@babel/plugin-transform-literals@^7.27.1": version "7.27.1" @@ -653,12 +673,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-logical-assignment-operators@^7.24.7", "@babel/plugin-transform-logical-assignment-operators@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz#d028fd6db8c081dee4abebc812c2325e24a85b0e" - integrity sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA== +"@babel/plugin-transform-logical-assignment-operators@^7.24.7", "@babel/plugin-transform-logical-assignment-operators@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz#53028a3d77e33c50ef30a8fce5ca17065936e605" + integrity sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-member-expression-literals@^7.27.1": version "7.27.1" @@ -675,23 +695,23 @@ "@babel/helper-module-transforms" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-modules-commonjs@^7.24.8", "@babel/plugin-transform-modules-commonjs@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz#8e44ed37c2787ecc23bdc367f49977476614e832" - integrity sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw== +"@babel/plugin-transform-modules-commonjs@^7.24.8", "@babel/plugin-transform-modules-commonjs@^7.27.1", "@babel/plugin-transform-modules-commonjs@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz#c0232e0dfe66a734cc4ad0d5e75fc3321b6fdef1" + integrity sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA== dependencies: - "@babel/helper-module-transforms" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-modules-systemjs@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz#7439e592a92d7670dfcb95d0cbc04bd3e64801d2" - integrity sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew== +"@babel/plugin-transform-modules-systemjs@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz#e458a95a17807c415924106a3ff188a3b8dee964" + integrity sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ== dependencies: - "@babel/helper-module-transforms" "^7.28.3" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-validator-identifier" "^7.28.5" - "@babel/traverse" "^7.28.5" + "@babel/traverse" "^7.29.0" "@babel/plugin-transform-modules-umd@^7.27.1": version "7.27.1" @@ -701,13 +721,13 @@ "@babel/helper-module-transforms" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-named-capturing-groups-regex@^7.24.7", "@babel/plugin-transform-named-capturing-groups-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz#f32b8f7818d8fc0cc46ee20a8ef75f071af976e1" - integrity sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng== +"@babel/plugin-transform-named-capturing-groups-regex@^7.24.7", "@babel/plugin-transform-named-capturing-groups-regex@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz#a26cd51e09c4718588fc4cce1c5d1c0152102d6a" + integrity sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-new-target@^7.27.1": version "7.27.1" @@ -716,30 +736,37 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-nullish-coalescing-operator@7.27.1", "@babel/plugin-transform-nullish-coalescing-operator@^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator@^7.27.1": +"@babel/plugin-transform-nullish-coalescing-operator@7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz#4f9d3153bf6782d73dd42785a9d22d03197bc91d" integrity sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA== dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-numeric-separator@^7.24.7", "@babel/plugin-transform-numeric-separator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz#614e0b15cc800e5997dadd9bd6ea524ed6c819c6" - integrity sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw== +"@babel/plugin-transform-nullish-coalescing-operator@^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz#9bc62096e90ab7a887f3ca9c469f6adec5679757" + integrity sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-object-rest-spread@^7.24.7", "@babel/plugin-transform-object-rest-spread@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz#9ee1ceca80b3e6c4bac9247b2149e36958f7f98d" - integrity sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew== +"@babel/plugin-transform-numeric-separator@^7.24.7", "@babel/plugin-transform-numeric-separator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz#1310b0292762e7a4a335df5f580c3320ee7d9e9f" + integrity sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w== dependencies: - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-transform-destructuring" "^7.28.0" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-object-rest-spread@^7.24.7", "@babel/plugin-transform-object-rest-spread@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz#fdd4bc2d72480db6ca42aed5c051f148d7b067f7" + integrity sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA== + dependencies: + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-transform-destructuring" "^7.28.5" "@babel/plugin-transform-parameters" "^7.27.7" - "@babel/traverse" "^7.28.4" + "@babel/traverse" "^7.28.6" "@babel/plugin-transform-object-super@^7.27.1": version "7.27.1" @@ -749,12 +776,12 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-replace-supers" "^7.27.1" -"@babel/plugin-transform-optional-catch-binding@^7.24.7", "@babel/plugin-transform-optional-catch-binding@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz#84c7341ebde35ccd36b137e9e45866825072a30c" - integrity sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q== +"@babel/plugin-transform-optional-catch-binding@^7.24.7", "@babel/plugin-transform-optional-catch-binding@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz#75107be14c78385978201a49c86414a150a20b4c" + integrity sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-optional-chaining@7.27.1": version "7.27.1" @@ -764,12 +791,12 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" -"@babel/plugin-transform-optional-chaining@^7.24.8", "@babel/plugin-transform-optional-chaining@^7.27.1", "@babel/plugin-transform-optional-chaining@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz#8238c785f9d5c1c515a90bf196efb50d075a4b26" - integrity sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ== +"@babel/plugin-transform-optional-chaining@^7.24.8", "@babel/plugin-transform-optional-chaining@^7.27.1", "@babel/plugin-transform-optional-chaining@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz#926cf150bd421fc8362753e911b4a1b1ce4356cd" + integrity sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" "@babel/plugin-transform-parameters@^7.24.7", "@babel/plugin-transform-parameters@^7.27.7": @@ -779,22 +806,22 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-private-methods@^7.24.7", "@babel/plugin-transform-private-methods@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz#fdacbab1c5ed81ec70dfdbb8b213d65da148b6af" - integrity sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA== +"@babel/plugin-transform-private-methods@^7.24.7", "@babel/plugin-transform-private-methods@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz#c76fbfef3b86c775db7f7c106fff544610bdb411" + integrity sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-private-property-in-object@^7.24.7", "@babel/plugin-transform-private-property-in-object@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz#4dbbef283b5b2f01a21e81e299f76e35f900fb11" - integrity sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ== +"@babel/plugin-transform-private-property-in-object@^7.24.7", "@babel/plugin-transform-private-property-in-object@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz#4fafef1e13129d79f1d75ac180c52aafefdb2811" + integrity sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA== dependencies: - "@babel/helper-annotate-as-pure" "^7.27.1" - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-property-literals@^7.27.1": version "7.27.1" @@ -832,15 +859,15 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-react-jsx@^7.25.2", "@babel/plugin-transform-react-jsx@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz#1023bc94b78b0a2d68c82b5e96aed573bcfb9db0" - integrity sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz#f51cb70a90b9529fbb71ee1f75ea27b7078eed62" + integrity sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow== dependencies: - "@babel/helper-annotate-as-pure" "^7.27.1" - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-syntax-jsx" "^7.27.1" - "@babel/types" "^7.27.1" + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-syntax-jsx" "^7.28.6" + "@babel/types" "^7.28.6" "@babel/plugin-transform-react-pure-annotations@^7.27.1": version "7.27.1" @@ -850,20 +877,20 @@ "@babel/helper-annotate-as-pure" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-regenerator@^7.24.7", "@babel/plugin-transform-regenerator@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz#9d3fa3bebb48ddd0091ce5729139cd99c67cea51" - integrity sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA== +"@babel/plugin-transform-regenerator@^7.24.7", "@babel/plugin-transform-regenerator@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz#dec237cec1b93330876d6da9992c4abd42c9d18b" + integrity sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-regexp-modifiers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz#df9ba5577c974e3f1449888b70b76169998a6d09" - integrity sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA== +"@babel/plugin-transform-regexp-modifiers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz#7ef0163bd8b4a610481b2509c58cf217f065290b" + integrity sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-reserved-words@^7.27.1": version "7.27.1" @@ -873,12 +900,12 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-runtime@^7.24.7": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.5.tgz#ae3e21fbefe2831ebac04dfa6b463691696afe17" - integrity sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w== + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz#a5fded13cc656700804bfd6e5ebd7fffd5266803" + integrity sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w== dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" babel-plugin-polyfill-corejs2 "^0.4.14" babel-plugin-polyfill-corejs3 "^0.13.0" babel-plugin-polyfill-regenerator "^0.6.5" @@ -891,12 +918,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-spread@^7.24.7", "@babel/plugin-transform-spread@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz#1a264d5fc12750918f50e3fe3e24e437178abb08" - integrity sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q== +"@babel/plugin-transform-spread@^7.24.7", "@babel/plugin-transform-spread@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz#40a2b423f6db7b70f043ad027a58bcb44a9757b6" + integrity sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" "@babel/plugin-transform-sticky-regex@^7.24.7", "@babel/plugin-transform-sticky-regex@^7.27.1": @@ -921,15 +948,15 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-typescript@^7.25.2", "@babel/plugin-transform-typescript@^7.27.1", "@babel/plugin-transform-typescript@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz#441c5f9a4a1315039516c6c612fc66d5f4594e72" - integrity sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz#1e93d96da8adbefdfdade1d4956f73afa201a158" + integrity sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw== dependencies: "@babel/helper-annotate-as-pure" "^7.27.3" - "@babel/helper-create-class-features-plugin" "^7.28.5" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - "@babel/plugin-syntax-typescript" "^7.27.1" + "@babel/plugin-syntax-typescript" "^7.28.6" "@babel/plugin-transform-unicode-escapes@^7.27.1": version "7.27.1" @@ -938,13 +965,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-unicode-property-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz#bdfe2d3170c78c5691a3c3be934c8c0087525956" - integrity sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q== +"@babel/plugin-transform-unicode-property-regex@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz#63a7a6c21a0e75dae9b1861454111ea5caa22821" + integrity sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-unicode-regex@7.27.1", "@babel/plugin-transform-unicode-regex@^7.24.7", "@babel/plugin-transform-unicode-regex@^7.27.1": version "7.27.1" @@ -954,88 +981,88 @@ "@babel/helper-create-regexp-features-plugin" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-unicode-sets-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz#6ab706d10f801b5c72da8bb2548561fa04193cd1" - integrity sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw== +"@babel/plugin-transform-unicode-sets-regex@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz#924912914e5df9fe615ec472f88ff4788ce04d4e" + integrity sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/preset-env@7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.28.5.tgz#82dd159d1563f219a1ce94324b3071eb89e280b0" - integrity sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg== +"@babel/preset-env@7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.29.0.tgz#c55db400c515a303662faaefd2d87e796efa08d0" + integrity sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w== dependencies: - "@babel/compat-data" "^7.28.5" - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/compat-data" "^7.29.0" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-validator-option" "^7.27.1" "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.28.5" "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.27.1" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.27.1" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.27.1" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.28.3" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.28.6" "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-import-assertions" "^7.27.1" - "@babel/plugin-syntax-import-attributes" "^7.27.1" + "@babel/plugin-syntax-import-assertions" "^7.28.6" + "@babel/plugin-syntax-import-attributes" "^7.28.6" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" "@babel/plugin-transform-arrow-functions" "^7.27.1" - "@babel/plugin-transform-async-generator-functions" "^7.28.0" - "@babel/plugin-transform-async-to-generator" "^7.27.1" + "@babel/plugin-transform-async-generator-functions" "^7.29.0" + "@babel/plugin-transform-async-to-generator" "^7.28.6" "@babel/plugin-transform-block-scoped-functions" "^7.27.1" - "@babel/plugin-transform-block-scoping" "^7.28.5" - "@babel/plugin-transform-class-properties" "^7.27.1" - "@babel/plugin-transform-class-static-block" "^7.28.3" - "@babel/plugin-transform-classes" "^7.28.4" - "@babel/plugin-transform-computed-properties" "^7.27.1" + "@babel/plugin-transform-block-scoping" "^7.28.6" + "@babel/plugin-transform-class-properties" "^7.28.6" + "@babel/plugin-transform-class-static-block" "^7.28.6" + "@babel/plugin-transform-classes" "^7.28.6" + "@babel/plugin-transform-computed-properties" "^7.28.6" "@babel/plugin-transform-destructuring" "^7.28.5" - "@babel/plugin-transform-dotall-regex" "^7.27.1" + "@babel/plugin-transform-dotall-regex" "^7.28.6" "@babel/plugin-transform-duplicate-keys" "^7.27.1" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.27.1" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.29.0" "@babel/plugin-transform-dynamic-import" "^7.27.1" - "@babel/plugin-transform-explicit-resource-management" "^7.28.0" - "@babel/plugin-transform-exponentiation-operator" "^7.28.5" + "@babel/plugin-transform-explicit-resource-management" "^7.28.6" + "@babel/plugin-transform-exponentiation-operator" "^7.28.6" "@babel/plugin-transform-export-namespace-from" "^7.27.1" "@babel/plugin-transform-for-of" "^7.27.1" "@babel/plugin-transform-function-name" "^7.27.1" - "@babel/plugin-transform-json-strings" "^7.27.1" + "@babel/plugin-transform-json-strings" "^7.28.6" "@babel/plugin-transform-literals" "^7.27.1" - "@babel/plugin-transform-logical-assignment-operators" "^7.28.5" + "@babel/plugin-transform-logical-assignment-operators" "^7.28.6" "@babel/plugin-transform-member-expression-literals" "^7.27.1" "@babel/plugin-transform-modules-amd" "^7.27.1" - "@babel/plugin-transform-modules-commonjs" "^7.27.1" - "@babel/plugin-transform-modules-systemjs" "^7.28.5" + "@babel/plugin-transform-modules-commonjs" "^7.28.6" + "@babel/plugin-transform-modules-systemjs" "^7.29.0" "@babel/plugin-transform-modules-umd" "^7.27.1" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.27.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.29.0" "@babel/plugin-transform-new-target" "^7.27.1" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.27.1" - "@babel/plugin-transform-numeric-separator" "^7.27.1" - "@babel/plugin-transform-object-rest-spread" "^7.28.4" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.28.6" + "@babel/plugin-transform-numeric-separator" "^7.28.6" + "@babel/plugin-transform-object-rest-spread" "^7.28.6" "@babel/plugin-transform-object-super" "^7.27.1" - "@babel/plugin-transform-optional-catch-binding" "^7.27.1" - "@babel/plugin-transform-optional-chaining" "^7.28.5" + "@babel/plugin-transform-optional-catch-binding" "^7.28.6" + "@babel/plugin-transform-optional-chaining" "^7.28.6" "@babel/plugin-transform-parameters" "^7.27.7" - "@babel/plugin-transform-private-methods" "^7.27.1" - "@babel/plugin-transform-private-property-in-object" "^7.27.1" + "@babel/plugin-transform-private-methods" "^7.28.6" + "@babel/plugin-transform-private-property-in-object" "^7.28.6" "@babel/plugin-transform-property-literals" "^7.27.1" - "@babel/plugin-transform-regenerator" "^7.28.4" - "@babel/plugin-transform-regexp-modifiers" "^7.27.1" + "@babel/plugin-transform-regenerator" "^7.29.0" + "@babel/plugin-transform-regexp-modifiers" "^7.28.6" "@babel/plugin-transform-reserved-words" "^7.27.1" "@babel/plugin-transform-shorthand-properties" "^7.27.1" - "@babel/plugin-transform-spread" "^7.27.1" + "@babel/plugin-transform-spread" "^7.28.6" "@babel/plugin-transform-sticky-regex" "^7.27.1" "@babel/plugin-transform-template-literals" "^7.27.1" "@babel/plugin-transform-typeof-symbol" "^7.27.1" "@babel/plugin-transform-unicode-escapes" "^7.27.1" - "@babel/plugin-transform-unicode-property-regex" "^7.27.1" + "@babel/plugin-transform-unicode-property-regex" "^7.28.6" "@babel/plugin-transform-unicode-regex" "^7.27.1" - "@babel/plugin-transform-unicode-sets-regex" "^7.27.1" + "@babel/plugin-transform-unicode-sets-regex" "^7.28.6" "@babel/preset-modules" "0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2 "^0.4.14" - babel-plugin-polyfill-corejs3 "^0.13.0" - babel-plugin-polyfill-regenerator "^0.6.5" - core-js-compat "^3.43.0" + babel-plugin-polyfill-corejs2 "^0.4.15" + babel-plugin-polyfill-corejs3 "^0.14.0" + babel-plugin-polyfill-regenerator "^0.6.6" + core-js-compat "^3.48.0" semver "^6.3.1" "@babel/preset-modules@0.1.6-no-external-plugins": @@ -1081,10 +1108,10 @@ "@babel/plugin-transform-modules-commonjs" "^7.27.1" "@babel/plugin-transform-typescript" "^7.28.5" -"@babel/register@^7.27.1": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.28.3.tgz#abd8a3753480c799bdaf9c9092d6745d16e052c2" - integrity sha512-CieDOtd8u208eI49bYl4z1J22ySFw87IGwE+IswFEExH7e3rLgKb0WNQeumnacQ1+VoDJLYI5QFA3AJZuyZQfA== +"@babel/register@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.28.6.tgz#f54461dd32f6a418c1eb1f583c95ed0b7266ea4c" + integrity sha512-pgcbbEl/dWQYb6L6Yew6F94rdwygfuv+vJ/tXfwIOYAfPB6TNWpXUMEtEq3YuTeHRdvMIhvz13bkT9CNaS+wqA== dependencies: clone-deep "^4.0.1" find-cache-dir "^2.0.0" @@ -1093,49 +1120,49 @@ source-map-support "^0.5.16" "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.0", "@babel/runtime@^7.25.0": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326" - integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.6.tgz#d267a43cb1836dc4d182cce93ae75ba954ef6d2b" + integrity sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA== -"@babel/template@^7.25.0", "@babel/template@^7.27.1", "@babel/template@^7.27.2", "@babel/template@^7.3.3": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" - integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== +"@babel/template@^7.25.0", "@babel/template@^7.28.6", "@babel/template@^7.3.3": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" + integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/parser" "^7.27.2" - "@babel/types" "^7.27.1" + "@babel/code-frame" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" "@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.5.tgz#450cab9135d21a7a2ca9d2d35aa05c20e68c360b" - integrity sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ== + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" + integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.5" + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" "@babel/helper-globals" "^7.28.0" - "@babel/parser" "^7.28.5" - "@babel/template" "^7.27.2" - "@babel/types" "^7.28.5" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" debug "^4.3.1" -"@babel/traverse@^7.25.3", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4", "@babel/traverse@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.5.tgz#450cab9135d21a7a2ca9d2d35aa05c20e68c360b" - integrity sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ== +"@babel/traverse@^7.25.3", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.4", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" + integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.5" + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" "@babel/helper-globals" "^7.28.0" - "@babel/parser" "^7.28.5" - "@babel/template" "^7.27.2" - "@babel/types" "^7.28.5" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" debug "^4.3.1" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.2", "@babel/types@^7.26.0", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4", "@babel/types@^7.28.5", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.5.tgz#10fc405f60897c35f07e85493c932c7b5ca0592b" - integrity sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.2", "@babel/types@^7.26.0", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.5", "@babel/types@^7.28.6", "@babel/types@^7.29.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" + integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== dependencies: "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" @@ -1169,14 +1196,14 @@ dependencies: "@types/hammerjs" "^2.0.36" -"@electron/asar@^3.2.13", "@electron/asar@^3.3.1": - version "3.4.1" - resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.4.1.tgz#4e9196a4b54fba18c56cd8d5cac67c5bdc588065" - integrity sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA== +"@electron/asar@^4.0.0", "@electron/asar@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-4.0.1.tgz#0f0edc51ddb5bf30acb49f706d616b11a0b90668" + integrity sha512-F4Ykm1jiBGY1WV/o8Q8oFW8Nq0u+S2/vPujzNJtdSJ6C4LHC4CiGLn7c17s7SolZ23gcvCebMncmZtNc+MkxPQ== dependencies: - commander "^5.0.0" - glob "^7.1.6" - minimatch "^3.0.4" + commander "^13.1.0" + glob "^11.0.1" + minimatch "^10.0.1" "@electron/get@^2.0.0": version "2.0.3" @@ -1193,231 +1220,352 @@ optionalDependencies: global-agent "^3.0.0" -"@electron/get@^3.0.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@electron/get/-/get-3.1.0.tgz#22c5a0bd917ab201badeb77bc4ad18cba54cb4ec" - integrity sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ== +"@electron/get@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@electron/get/-/get-4.0.2.tgz#fbac5bf11e94a53cca90b81559a7fcb10fb532e6" + integrity sha512-n9fRt/nzzOOZdDtTP3kT6GVdo0ro9FgMKCoS520kQMIiKBhpGmPny6yK/lER3tOCKr+wLYW1O25D9oI6ZinwCA== dependencies: debug "^4.1.1" - env-paths "^2.2.0" - fs-extra "^8.1.0" - got "^11.8.5" + env-paths "^3.0.0" + got "^14.4.5" + graceful-fs "^4.2.11" progress "^2.0.3" - semver "^6.2.0" + semver "^7.6.3" sumchecker "^3.0.1" optionalDependencies: global-agent "^3.0.0" -"@electron/notarize@^2.1.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@electron/notarize/-/notarize-2.5.0.tgz#d4d25356adfa29df4a76bd64a8bd347237cd251e" - integrity sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A== +"@electron/notarize@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@electron/notarize/-/notarize-3.1.1.tgz#428d96ab84e333506f2a16fcf12db7ca12fdc7e0" + integrity sha512-uQQSlOiJnqRkTL1wlEBAxe90nVN/Fc/hEmk0bqpKk8nKjV1if/tXLHKUPePtv9Xsx90PtZU8aidx5lAiOpjkQQ== dependencies: - debug "^4.1.1" - fs-extra "^9.0.1" + debug "^4.4.0" promise-retry "^2.0.1" -"@electron/osx-sign@^1.0.5": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@electron/osx-sign/-/osx-sign-1.3.3.tgz#af751510488318d9f7663694af85819690d75583" - integrity sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg== +"@electron/osx-sign@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@electron/osx-sign/-/osx-sign-2.3.0.tgz#9418be8bacd65d211ef8a738b9094bdb5277dd00" + integrity sha512-qh/BkWUHITP2W1grtCWn8Pm4EML3q1Rfo2tnd/KHo+f1le5gAYotBc6yEfK6X2VHyN21mPZ3CjvOiYOwjimBlA== dependencies: - compare-version "^0.1.2" debug "^4.3.4" - fs-extra "^10.0.0" isbinaryfile "^4.0.8" - minimist "^1.2.6" plist "^3.0.5" - -"@electron/packager@18.4.4": - version "18.4.4" - resolved "https://registry.yarnpkg.com/@electron/packager/-/packager-18.4.4.tgz#708458f73639c2aa919ce5f6250ac519fa53ee5c" - integrity sha512-fTUCmgL25WXTcFpM1M72VmFP8w3E4d+KNzWxmTDRpvwkfn/S206MAtM2cy0GF78KS9AwASMOUmlOIzCHeNxcGQ== - dependencies: - "@electron/asar" "^3.2.13" - "@electron/get" "^3.0.0" - "@electron/notarize" "^2.1.0" - "@electron/osx-sign" "^1.0.5" - "@electron/universal" "^2.0.1" - "@electron/windows-sign" "^1.0.0" + semver "^7.7.1" + +"@electron/packager@19.0.5": + version "19.0.5" + resolved "https://registry.yarnpkg.com/@electron/packager/-/packager-19.0.5.tgz#9c35a7e1314555be2c675cd289a1741a3074b913" + integrity sha512-HSWJp5ZTYkavzoLViPBK8sUaINrHbhHMueMrYpQea5DkzOplxYCgAKmy+FKsBk7WUm6iYZZ0gJe8f8nIEScqJQ== + dependencies: + "@electron/asar" "^4.0.1" + "@electron/get" "^4.0.2" + "@electron/notarize" "^3.1.0" + "@electron/osx-sign" "^2.2.0" + "@electron/universal" "^3.0.1" + "@electron/windows-sign" "^2.0.2" "@malept/cross-spawn-promise" "^2.0.0" - debug "^4.0.1" - extract-zip "^2.0.0" - filenamify "^4.1.0" - fs-extra "^11.1.0" - galactus "^1.0.0" + debug "^4.4.1" + extract-zip "^2.0.1" + filenamify "^6.0.0" + galactus "^2.0.2" get-package-info "^1.0.0" - junk "^3.1.0" + graceful-fs "^4.2.11" + junk "^4.0.1" parse-author "^2.0.0" - plist "^3.0.0" - prettier "^3.4.2" - resedit "^2.0.0" - resolve "^1.1.6" - semver "^7.1.3" - yargs-parser "^21.1.1" + plist "^3.1.0" + resedit "^2.0.3" + resolve "^1.22.10" + semver "^7.7.2" + yargs-parser "^22.0.0" -"@electron/universal@^2.0.1": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-2.0.3.tgz#1680df6ced8f128ca0ff24e29c2165d41d78b3ce" - integrity sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g== +"@electron/universal@^3.0.1": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-3.0.2.tgz#d21fabd2729f9f5edbf2d8ab088718c4ac29244c" + integrity sha512-2/NBjJhw/VXQayIIj8Mu3ZeZ+lRjx90l2aKqkQiVrA2HiFTc/KHKN8Fjj3Ta7xMAxn45mAKJCatR8xeJ/eW7Tg== dependencies: - "@electron/asar" "^3.3.1" + "@electron/asar" "^4.0.0" "@malept/cross-spawn-promise" "^2.0.0" debug "^4.3.1" dir-compare "^4.2.0" - fs-extra "^11.1.1" minimatch "^9.0.3" plist "^3.1.0" -"@electron/windows-sign@^1.0.0": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@electron/windows-sign/-/windows-sign-1.2.2.tgz#8ceaad52d5c1eb18702f48103d5f3bc7c338fa9d" - integrity sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ== +"@electron/windows-sign@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@electron/windows-sign/-/windows-sign-2.0.2.tgz#7396f18c19e407751ffe8528628e292cba52c634" + integrity sha512-9Lldk4pvRBh/BWhwopW4CxCnVoztEAVWdxvVVwpvrFd/3QU3dVn15IRmVB9i46IqpAg1Y42cFtRT0NQKZPpc5A== dependencies: - cross-dirname "^0.1.0" debug "^4.3.4" - fs-extra "^11.1.1" - minimist "^1.2.8" + graceful-fs "^4.2.11" postject "^1.0.0-alpha.6" +"@epic-web/invariant@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@epic-web/invariant/-/invariant-1.0.0.tgz#1073e5dee6dd540410784990eb73e4acd25c9813" + integrity sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA== + "@esbuild/aix-ppc64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz#80fcbe36130e58b7670511e888b8e88a259ed76c" integrity sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA== +"@esbuild/aix-ppc64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz#815b39267f9bffd3407ea6c376ac32946e24f8d2" + integrity sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg== + "@esbuild/android-arm64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz#8aa4965f8d0a7982dc21734bf6601323a66da752" integrity sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg== +"@esbuild/android-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz#19b882408829ad8e12b10aff2840711b2da361e8" + integrity sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg== + "@esbuild/android-arm@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz#300712101f7f50f1d2627a162e6e09b109b6767a" integrity sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg== +"@esbuild/android-arm@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.3.tgz#90be58de27915efa27b767fcbdb37a4470627d7b" + integrity sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA== + "@esbuild/android-x64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz#87dfb27161202bdc958ef48bb61b09c758faee16" integrity sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg== +"@esbuild/android-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.3.tgz#d7dcc976f16e01a9aaa2f9b938fbec7389f895ac" + integrity sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ== + "@esbuild/darwin-arm64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz#79197898ec1ff745d21c071e1c7cc3c802f0c1fd" integrity sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg== +"@esbuild/darwin-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz#9f6cac72b3a8532298a6a4493ed639a8988e8abd" + integrity sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg== + "@esbuild/darwin-x64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz#146400a8562133f45c4d2eadcf37ddd09718079e" integrity sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA== +"@esbuild/darwin-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz#ac61d645faa37fd650340f1866b0812e1fb14d6a" + integrity sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg== + "@esbuild/freebsd-arm64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz#1c5f9ba7206e158fd2b24c59fa2d2c8bb47ca0fe" integrity sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg== +"@esbuild/freebsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz#b8625689d73cf1830fe58c39051acdc12474ea1b" + integrity sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w== + "@esbuild/freebsd-x64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz#ea631f4a36beaac4b9279fa0fcc6ca29eaeeb2b3" integrity sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ== +"@esbuild/freebsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz#07be7dd3c9d42fe0eccd2ab9f9ded780bc53bead" + integrity sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA== + "@esbuild/linux-arm64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz#e1066bce58394f1b1141deec8557a5f0a22f5977" integrity sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ== +"@esbuild/linux-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz#bf31918fe5c798586460d2b3d6c46ed2c01ca0b6" + integrity sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg== + "@esbuild/linux-arm@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz#452cd66b20932d08bdc53a8b61c0e30baf4348b9" integrity sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw== +"@esbuild/linux-arm@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz#28493ee46abec1dc3f500223cd9f8d2df08f9d11" + integrity sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw== + "@esbuild/linux-ia32@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz#b24f8acc45bcf54192c7f2f3be1b53e6551eafe0" integrity sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA== +"@esbuild/linux-ia32@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz#750752a8b30b43647402561eea764d0a41d0ee29" + integrity sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg== + "@esbuild/linux-loong64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz#f9cfffa7fc8322571fbc4c8b3268caf15bd81ad0" integrity sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng== +"@esbuild/linux-loong64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz#a5a92813a04e71198c50f05adfaf18fc1e95b9ed" + integrity sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA== + "@esbuild/linux-mips64el@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz#575a14bd74644ffab891adc7d7e60d275296f2cd" integrity sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw== +"@esbuild/linux-mips64el@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz#deb45d7fd2d2161eadf1fbc593637ed766d50bb1" + integrity sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw== + "@esbuild/linux-ppc64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz#75b99c70a95fbd5f7739d7692befe60601591869" integrity sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA== +"@esbuild/linux-ppc64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz#6f39ae0b8c4d3d2d61a65b26df79f6e12a1c3d78" + integrity sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA== + "@esbuild/linux-riscv64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz#2e3259440321a44e79ddf7535c325057da875cd6" integrity sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w== +"@esbuild/linux-riscv64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz#4c5c19c3916612ec8e3915187030b9df0b955c1d" + integrity sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ== + "@esbuild/linux-s390x@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz#17676cabbfe5928da5b2a0d6df5d58cd08db2663" integrity sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg== +"@esbuild/linux-s390x@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz#9ed17b3198fa08ad5ccaa9e74f6c0aff7ad0156d" + integrity sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw== + "@esbuild/linux-x64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz#0583775685ca82066d04c3507f09524d3cd7a306" integrity sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw== +"@esbuild/linux-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz#12383dcbf71b7cf6513e58b4b08d95a710bf52a5" + integrity sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA== + "@esbuild/netbsd-arm64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz#f04c4049cb2e252fe96b16fed90f70746b13f4a4" integrity sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg== +"@esbuild/netbsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz#dd0cb2fa543205fcd931df44f4786bfcce6df7d7" + integrity sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA== + "@esbuild/netbsd-x64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz#77da0d0a0d826d7c921eea3d40292548b258a076" integrity sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ== +"@esbuild/netbsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz#028ad1807a8e03e155153b2d025b506c3787354b" + integrity sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA== + "@esbuild/openbsd-arm64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz#6296f5867aedef28a81b22ab2009c786a952dccd" integrity sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A== +"@esbuild/openbsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz#e3c16ff3490c9b59b969fffca87f350ffc0e2af5" + integrity sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw== + "@esbuild/openbsd-x64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz#f8d23303360e27b16cf065b23bbff43c14142679" integrity sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw== +"@esbuild/openbsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz#c5a4693fcb03d1cbecbf8b422422468dfc0d2a8b" + integrity sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ== + "@esbuild/openharmony-arm64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz#49e0b768744a3924be0d7fd97dd6ce9b2923d88d" integrity sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg== +"@esbuild/openharmony-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz#082082444f12db564a0775a41e1991c0e125055e" + integrity sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g== + "@esbuild/sunos-x64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz#a6ed7d6778d67e528c81fb165b23f4911b9b13d6" integrity sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w== +"@esbuild/sunos-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz#5ab036c53f929e8405c4e96e865a424160a1b537" + integrity sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA== + "@esbuild/win32-arm64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz#9ac14c378e1b653af17d08e7d3ce34caef587323" integrity sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg== +"@esbuild/win32-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz#38de700ef4b960a0045370c171794526e589862e" + integrity sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA== + "@esbuild/win32-ia32@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz#918942dcbbb35cc14fca39afb91b5e6a3d127267" integrity sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ== +"@esbuild/win32-ia32@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz#451b93dc03ec5d4f38619e6cd64d9f9eff06f55c" + integrity sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q== + "@esbuild/win32-x64@0.25.12": version "0.25.12" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz#9bdad8176be7811ad148d1f8772359041f46c6c5" integrity sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA== -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0", "@eslint-community/eslint-utils@^4.8.0": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3" - integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g== - dependencies: - eslint-visitor-keys "^3.4.3" +"@esbuild/win32-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz#0eaf705c941a218a43dba8e09f1df1d6cd2f1f17" + integrity sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA== -"@eslint-community/eslint-utils@^4.9.1": +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0", "@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": version "4.9.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== @@ -1453,9 +1601,9 @@ "@types/json-schema" "^7.0.15" "@eslint/eslintrc@^3.3.1": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964" - integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== + version "3.3.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.3.tgz#26393a0806501b5e2b6a43aa588a4d8df67880ac" + integrity sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -1463,7 +1611,7 @@ globals "^14.0.0" ignore "^5.2.0" import-fresh "^3.2.1" - js-yaml "^4.1.0" + js-yaml "^4.1.1" minimatch "^3.1.2" strip-json-comments "^3.1.1" @@ -1485,10 +1633,10 @@ "@eslint/core" "^0.17.0" levn "^0.4.1" -"@expo/cli@54.0.21": - version "54.0.21" - resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-54.0.21.tgz#196ea08f539535b26717e09661937b24b38f25e7" - integrity sha512-L/FdpyZDsg/Nq6xW6kfiyF9DUzKfLZCKFXEVZcDqCNar6bXxQVotQyvgexRvtUF5nLinuT/UafLOdC3FUALUmA== +"@expo/cli@54.0.23": + version "54.0.23" + resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-54.0.23.tgz#e8a7dc4e1f2a8a5361afd80bcc352014b57a87ac" + integrity sha512-km0h72SFfQCmVycH/JtPFTVy69w6Lx1cHNDmfLfQqgKFYeeHTjx7LVDP4POHCtNxFP2UeRazrygJhlh4zz498g== dependencies: "@0no-co/graphql.web" "^1.0.8" "@expo/code-signing-certificates" "^0.0.6" @@ -1499,9 +1647,9 @@ "@expo/image-utils" "^0.8.8" "@expo/json-file" "^10.0.8" "@expo/metro" "~54.2.0" - "@expo/metro-config" "~54.0.13" + "@expo/metro-config" "~54.0.14" "@expo/osascript" "^2.3.8" - "@expo/package-manager" "^1.9.9" + "@expo/package-manager" "^1.9.10" "@expo/plist" "^0.4.8" "@expo/prebuild-config" "^54.0.8" "@expo/schema-utils" "^0.1.8" @@ -1561,26 +1709,6 @@ dependencies: node-forge "^1.3.3" -"@expo/config-plugins@~54.0.3": - version "54.0.3" - resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-54.0.3.tgz#2b9ffd68a48e3b51299cdbe3ee777b9f5163fc03" - integrity sha512-tBIUZIxLQfCu5jmqTO+UOeeDUGIB0BbK6xTMkPRObAXRQeTLPPfokZRCo818d2owd+Bcmq1wBaDz0VY3g+glfw== - dependencies: - "@expo/config-types" "^54.0.9" - "@expo/json-file" "~10.0.7" - "@expo/plist" "^0.4.7" - "@expo/sdk-runtime-versions" "^1.0.0" - chalk "^4.1.2" - debug "^4.3.5" - getenv "^2.0.0" - glob "^13.0.0" - resolve-from "^5.0.0" - semver "^7.5.4" - slash "^3.0.0" - slugify "^1.6.6" - xcode "^3.0.1" - xml2js "0.6.0" - "@expo/config-plugins@~54.0.4": version "54.0.4" resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-54.0.4.tgz#b31cb16f6651342abcdafba600118245ecd9fb00" @@ -1606,30 +1734,6 @@ resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-54.0.10.tgz#688f4338255d2fdea970f44e2dfd8e8d37dec292" integrity sha512-/J16SC2an1LdtCZ67xhSkGXpALYUVUNyZws7v+PVsFZxClYehDSoKLqyRaGkpHlYrCc08bS0RF5E0JV6g50psA== -"@expo/config-types@^54.0.9": - version "54.0.9" - resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-54.0.9.tgz#b9279c47fe249b774fbd3358b6abddea08f1bcec" - integrity sha512-Llf4jwcrAnrxgE5WCdAOxtMf8FGwS4Sk0SSgI0NnIaSyCnmOCAm80GPFvsK778Oj19Ub4tSyzdqufPyeQPksWw== - -"@expo/config@~12.0.12": - version "12.0.12" - resolved "https://registry.yarnpkg.com/@expo/config/-/config-12.0.12.tgz#5e26390ec209ee56432e980451c08b361e0f07dd" - integrity sha512-X2MW86+ulLpMGvdgfvpl2EOBAKUlwvnvoPwdaZeeyWufGopn1nTUeh4C9gMsplDaP1kXv9sLXVhOoUoX6g9PvQ== - dependencies: - "@babel/code-frame" "~7.10.4" - "@expo/config-plugins" "~54.0.3" - "@expo/config-types" "^54.0.10" - "@expo/json-file" "^10.0.8" - deepmerge "^4.3.1" - getenv "^2.0.0" - glob "^13.0.0" - require-from-string "^2.0.2" - resolve-from "^5.0.0" - resolve-workspace-root "^2.0.0" - semver "^7.6.0" - slugify "^1.3.4" - sucrase "~3.35.1" - "@expo/config@~12.0.13": version "12.0.13" resolved "https://registry.yarnpkg.com/@expo/config/-/config-12.0.13.tgz#8e696e6121c3c364e1dd527f595cf0a1d9386828" @@ -1716,18 +1820,10 @@ "@babel/code-frame" "~7.10.4" json5 "^2.2.3" -"@expo/json-file@~10.0.7": - version "10.0.7" - resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-10.0.7.tgz#e4f58fdc03fc62f13610eeafe086d84e6e44fe01" - integrity sha512-z2OTC0XNO6riZu98EjdNHC05l51ySeTto6GP7oSQrCvQgG9ARBwD1YvMQaVZ9wU7p/4LzSf1O7tckL3B45fPpw== - dependencies: - "@babel/code-frame" "~7.10.4" - json5 "^2.2.3" - -"@expo/metro-config@54.0.13", "@expo/metro-config@~54.0.13": - version "54.0.13" - resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-54.0.13.tgz#a78f0da6ba7eca4d3b1fa0fa2487be4d947e4fd6" - integrity sha512-RRufMCgLR2Za1WGsh02OatIJo5qZFt31yCnIOSfoubNc3Qqe92Z41pVsbrFnmw5CIaisv1NgdBy05DHe7pEyuw== +"@expo/metro-config@54.0.14", "@expo/metro-config@~54.0.14": + version "54.0.14" + resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-54.0.14.tgz#e455dfb2bae9473ec665bc830d651baa709c1e8a" + integrity sha512-hxpLyDfOR4L23tJ9W1IbJJsG7k4lv2sotohBm/kTYyiG+pe1SYCAWsRmgk+H42o/wWf/HQjE5k45S5TomGLxNA== dependencies: "@babel/code-frame" "^7.20.0" "@babel/core" "^7.20.0" @@ -1779,10 +1875,10 @@ "@expo/spawn-async" "^1.7.2" exec-async "^2.2.0" -"@expo/package-manager@^1.9.9": - version "1.9.9" - resolved "https://registry.yarnpkg.com/@expo/package-manager/-/package-manager-1.9.9.tgz#dd030d2bccebd095e02bfb6976852afaddcd122a" - integrity sha512-Nv5THOwXzPprMJwbnXU01iXSrCp3vJqly9M4EJ2GkKko9Ifer2ucpg7x6OUsE09/lw+npaoUnHMXwkw7gcKxlg== +"@expo/package-manager@^1.9.10": + version "1.9.10" + resolved "https://registry.yarnpkg.com/@expo/package-manager/-/package-manager-1.9.10.tgz#5da3f4943f6fa44ddd7f0efe32a360040edd734a" + integrity sha512-axJm+NOj3jVxep49va/+L3KkF3YW/dkV+RwzqUJedZrv4LeTqOG4rhrCaCPXHTvLqCTDKu6j0Xyd28N7mnxsGA== dependencies: "@expo/json-file" "^10.0.8" "@expo/spawn-async" "^1.7.2" @@ -1791,15 +1887,6 @@ ora "^3.4.0" resolve-workspace-root "^2.0.0" -"@expo/plist@^0.4.7": - version "0.4.7" - resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.4.7.tgz#40fa796e93d5be0452ce72e5110e69c8ac915403" - integrity sha512-dGxqHPvCZKeRKDU1sJZMmuyVtcASuSYh1LPFVaM1DuffqPL36n6FMEL0iUqq2Tx3xhWk8wCnWl34IKplUjJDdA== - dependencies: - "@xmldom/xmldom" "^0.8.8" - base64-js "^1.2.3" - xmlbuilder "^15.1.1" - "@expo/plist@^0.4.8": version "0.4.8" resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.4.8.tgz#e014511a4a5008cf2b832b91caa8e9f2704127cc" @@ -1858,13 +1945,12 @@ integrity sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q== "@expo/xcpretty@^4.3.0": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@expo/xcpretty/-/xcpretty-4.3.2.tgz#12dba1295167a9c8dde4be783d74f7e81648ca5d" - integrity sha512-ReZxZ8pdnoI3tP/dNnJdnmAk7uLT4FjsKDGW7YeDdvdOMz2XCQSmSCM9IWlrXuWtMF9zeSB6WJtEhCQ41gQOfw== + version "4.4.0" + resolved "https://registry.yarnpkg.com/@expo/xcpretty/-/xcpretty-4.4.0.tgz#7a5aaf9ce5d538f84ae6518655d175f5cc94ce81" + integrity sha512-o2qDlTqJ606h4xR36H2zWTywmZ3v3842K6TU8Ik2n1mfW0S580VHlt3eItVYdLYz+klaPp7CXqanja8eASZjRw== dependencies: - "@babel/code-frame" "7.10.4" + "@babel/code-frame" "^7.20.0" chalk "^4.1.0" - find-up "^5.0.0" js-yaml "^4.1.0" "@gorhom/bottom-sheet@5.2.8": @@ -1922,13 +2008,18 @@ resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== -"@isaacs/brace-expansion@^5.0.0": +"@isaacs/brace-expansion@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz#0ef5a92d91f2fff2a37646ce54da9e5f599f6eff" integrity sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ== dependencies: "@isaacs/balanced-match" "^4.0.1" +"@isaacs/cliui@^9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-9.0.0.tgz#4d0a3f127058043bf2e7ee169eaf30ed901302f3" + integrity sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg== + "@isaacs/fs-minipass@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" @@ -2068,21 +2159,107 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jsonjoy.com/base64@17.67.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-17.67.0.tgz#7eeda3cb41138d77a90408fd2e42b2aba10576d7" + integrity sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw== + "@jsonjoy.com/base64@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578" integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA== +"@jsonjoy.com/buffers@17.67.0", "@jsonjoy.com/buffers@^17.65.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz#5c58dbcdeea8824ce296bd1cfce006c2eb167b3d" + integrity sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw== + "@jsonjoy.com/buffers@^1.0.0", "@jsonjoy.com/buffers@^1.2.0": version "1.2.1" resolved "https://registry.yarnpkg.com/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz#8d99c7f67eaf724d3428dfd9826c6455266a5c83" integrity sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA== +"@jsonjoy.com/codegen@17.67.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz#3635fd8769d77e19b75dc5574bc9756019b2e591" + integrity sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q== + "@jsonjoy.com/codegen@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz#5c23f796c47675f166d23b948cdb889184b93207" integrity sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g== +"@jsonjoy.com/fs-core@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-core/-/fs-core-4.56.10.tgz#320728b4b7bef63abb60e7630351623899237411" + integrity sha512-PyAEA/3cnHhsGcdY+AmIU+ZPqTuZkDhCXQ2wkXypdLitSpd6d5Ivxhnq4wa2ETRWFVJGabYynBWxIijOswSmOw== + dependencies: + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + thingies "^2.5.0" + +"@jsonjoy.com/fs-fsa@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-fsa/-/fs-fsa-4.56.10.tgz#02bac88c4968ddf2effbd7452861aaed60ba3557" + integrity sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q== + dependencies: + "@jsonjoy.com/fs-core" "4.56.10" + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + thingies "^2.5.0" + +"@jsonjoy.com/fs-node-builtins@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.56.10.tgz#a32a5bcb093f8b34a99aa8957e993a52ec316662" + integrity sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw== + +"@jsonjoy.com/fs-node-to-fsa@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.56.10.tgz#33fc503e50d283ac5fc510e3accced7fccecf2f4" + integrity sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw== + dependencies: + "@jsonjoy.com/fs-fsa" "4.56.10" + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + +"@jsonjoy.com/fs-node-utils@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.56.10.tgz#788e95052aa99744f6e8e55b5098afc203df2b9e" + integrity sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg== + dependencies: + "@jsonjoy.com/fs-node-builtins" "4.56.10" + +"@jsonjoy.com/fs-node@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-node/-/fs-node-4.56.10.tgz#70b18bfaf14544a9820d2016e913dde12c6de991" + integrity sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q== + dependencies: + "@jsonjoy.com/fs-core" "4.56.10" + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + "@jsonjoy.com/fs-print" "4.56.10" + "@jsonjoy.com/fs-snapshot" "4.56.10" + glob-to-regex.js "^1.0.0" + thingies "^2.5.0" + +"@jsonjoy.com/fs-print@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-print/-/fs-print-4.56.10.tgz#7c181b9aefcc1b268be0e6233bff26310c355335" + integrity sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw== + dependencies: + "@jsonjoy.com/fs-node-utils" "4.56.10" + tree-dump "^1.1.0" + +"@jsonjoy.com/fs-snapshot@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.56.10.tgz#05aadd2c0eaa855b13d6cb17d29b7c8cee239c8c" + integrity sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g== + dependencies: + "@jsonjoy.com/buffers" "^17.65.0" + "@jsonjoy.com/fs-node-utils" "4.56.10" + "@jsonjoy.com/json-pack" "^17.65.0" + "@jsonjoy.com/util" "^17.65.0" + "@jsonjoy.com/json-pack@^1.11.0": version "1.21.0" resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz#93f8dd57fe3a3a92132b33d1eb182dcd9e7629fa" @@ -2097,6 +2274,27 @@ thingies "^2.5.0" tree-dump "^1.1.0" +"@jsonjoy.com/json-pack@^17.65.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz#8dd8ff65dd999c5d4d26df46c63915c7bdec093a" + integrity sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w== + dependencies: + "@jsonjoy.com/base64" "17.67.0" + "@jsonjoy.com/buffers" "17.67.0" + "@jsonjoy.com/codegen" "17.67.0" + "@jsonjoy.com/json-pointer" "17.67.0" + "@jsonjoy.com/util" "17.67.0" + hyperdyperid "^1.2.0" + thingies "^2.5.0" + tree-dump "^1.1.0" + +"@jsonjoy.com/json-pointer@17.67.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz#74439573dc046e0c9a3a552fb94b391bc75313b8" + integrity sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA== + dependencies: + "@jsonjoy.com/util" "17.67.0" + "@jsonjoy.com/json-pointer@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz#049cb530ac24e84cba08590c5e36b431c4843408" @@ -2105,6 +2303,14 @@ "@jsonjoy.com/codegen" "^1.0.0" "@jsonjoy.com/util" "^1.9.0" +"@jsonjoy.com/util@17.67.0", "@jsonjoy.com/util@^17.65.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-17.67.0.tgz#7c4288fc3808233e55c7610101e7bb4590cddd3f" + integrity sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew== + dependencies: + "@jsonjoy.com/buffers" "17.67.0" + "@jsonjoy.com/codegen" "17.67.0" + "@jsonjoy.com/util@^1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.9.0.tgz#7ee95586aed0a766b746cd8d8363e336c3c47c46" @@ -2113,15 +2319,20 @@ "@jsonjoy.com/buffers" "^1.0.0" "@jsonjoy.com/codegen" "^1.0.0" +"@keyv/serialize@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@keyv/serialize/-/serialize-1.1.1.tgz#0c01dd3a3483882af7cf3878d4e71d505c81fc4a" + integrity sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA== + "@khanacademy/perseus-utils@2.1.4": version "2.1.4" resolved "https://registry.yarnpkg.com/@khanacademy/perseus-utils/-/perseus-utils-2.1.4.tgz#f87462f16781c7a40311ac0932e13a1f67ab9418" integrity sha512-TQLLUZQWc0K0gCLejNTd7G2y1qMhF3BtM9rBGvSObo10EVL4LwA2nCgm0FaPLowCp0ujweZACh9ty+xlekkGXA== -"@khanacademy/simple-markdown@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@khanacademy/simple-markdown/-/simple-markdown-2.1.4.tgz#1673e79fd1d4a2f4c3f697c5421b0a1289ed6f0e" - integrity sha512-2WBPGtnaCuMS2b6DewdXPbgy14QFo6V22FuMf2PxREQaAHakIOhjQ7sgeswN7ZdAZ1uyGi4TnDbN1qSgIbaC3Q== +"@khanacademy/simple-markdown@2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@khanacademy/simple-markdown/-/simple-markdown-2.2.1.tgz#3a477c2bcb026fbf25c0aa0272fbb9b3b0f7058c" + integrity sha512-9xmLOLIh//ZiWwQFtJETnV8Ha24J3KgHpcwrROjqjuXD3tNiNZrVMBBZU93Aw1/jeocA7p5jiYBrErZiqGlLag== dependencies: "@khanacademy/perseus-utils" "2.1.4" @@ -2142,10 +2353,10 @@ resolved "https://registry.yarnpkg.com/@msgpack/msgpack/-/msgpack-2.8.0.tgz#4210deb771ee3912964f14a15ddfb5ff877e70b9" integrity sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ== -"@msgpack/msgpack@3.1.2": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@msgpack/msgpack/-/msgpack-3.1.2.tgz#fdd25cc2202297519798bbaf4689152ad9609e19" - integrity sha512-JEW4DEtBzfe8HvUYecLU9e6+XJnKDlUAIve8FvPzF3Kzs6Xo/KuZkZJsDH0wJXl/qEZbeeE7edxDNY3kMs39hQ== +"@msgpack/msgpack@3.1.3": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@msgpack/msgpack/-/msgpack-3.1.3.tgz#c4bff2b9539faf0882f3ee03537a7e9a4b3a7864" + integrity sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA== "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" @@ -2326,9 +2537,9 @@ integrity sha512-slT6XeTCAbdql61GVLlGU4x7XHI7kCZV5Um5uhE4zLX4ApgiiXc0UYFvVOKq06xcovzp7p+61l68oPi563ARKg== "@preact/signals@^1.3.1": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@preact/signals/-/signals-1.3.3.tgz#fca03bc348bbcf7bf88f84d6d4cfa05b42c93b0f" - integrity sha512-FieIS2Z3iHAmjYTqsD3U3DEad1/bOicGN8wT34bbrdp422kQfkP7iwYgqTgx53wPdnsENaNkNNmScbi3RVZq8Q== + version "1.3.4" + resolved "https://registry.yarnpkg.com/@preact/signals/-/signals-1.3.4.tgz#83509e1ce1ba5572b2dfb45039121066c8851563" + integrity sha512-TPMkStdT0QpSc8FpB63aOwXoSiZyIrPsP9Uj347KopdS6olZdAYeeird/5FZv/M1Yc1ge5qstub2o8VDbvkT4g== dependencies: "@preact/signals-core" "^1.7.0" @@ -2760,19 +2971,19 @@ invariant "^2.2.4" nullthrows "^1.1.1" -"@react-navigation/bottom-tabs@7.9.1": - version "7.9.1" - resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-7.9.1.tgz#1a228e68c85f27bb3f0d2b814bdddb25eeb60360" - integrity sha512-1MHn1b5tWFa8t8LXUvaNtlg5WGVXcNTsiV00ygQDBFDusMcu0ZPOU1exqELZwHf6kDntmTQE96/NRM+Cd2QR+A== +"@react-navigation/bottom-tabs@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-7.13.0.tgz#cef50768633cdbd272809aec7751ac37ce36dab0" + integrity sha512-qxxjRDpjhZ4vIZqG4rBU1Vx2jgOAO/ciUKc9sJqVlTM005E2E+aK1EaE3lGaBDyZxTpjonollAucZcqL7OTscQ== dependencies: - "@react-navigation/elements" "^2.9.4" + "@react-navigation/elements" "^2.9.5" color "^4.2.3" sf-symbols-typescript "^2.1.0" -"@react-navigation/core@7.13.7", "@react-navigation/core@^7.13.7": - version "7.13.7" - resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-7.13.7.tgz#1263903a703b5f183a08c22dfc5c3735ac4ef91c" - integrity sha512-k2ABo3250vq1ovOh/iVwXS6Hwr5PVRGXoPh/ewVFOOuEKTvOx9i//OBzt8EF+HokBxS2HBRlR2b+aCOmscRqBw== +"@react-navigation/core@7.14.0", "@react-navigation/core@^7.14.0": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-7.14.0.tgz#d24f93d424ab33f645262dc4775e4708aa3d9a8b" + integrity sha512-tMpzskBzVp0E7CRNdNtJIdXjk54Kwe/TF9ViXAef+YFM1kSfGv4e/B2ozfXE+YyYgmh4WavTv8fkdJz1CNyu+g== dependencies: "@react-navigation/routers" "^7.5.3" escape-string-regexp "^4.0.0" @@ -2783,31 +2994,31 @@ use-latest-callback "^0.2.4" use-sync-external-store "^1.5.0" -"@react-navigation/elements@^2.9.4": - version "2.9.4" - resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-2.9.4.tgz#72101e7cf74e6952a303ffd3a69aab5e9ba178a8" - integrity sha512-TMFh+QzwesEuSaKpvZk4BFC5105t5gJs9eq+jG7jtfdlMKyrcFwheu2/dy1zfrW4WYbVcoMxhzFqCU4mKwI4fw== +"@react-navigation/elements@^2.9.5": + version "2.9.5" + resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-2.9.5.tgz#29f68c4975351724dcfe1d3bdc76c4d6dc65fc33" + integrity sha512-iHZU8rRN1014Upz73AqNVXDvSMZDh5/ktQ1CMe21rdgnOY79RWtHHBp9qOS3VtqlUVYGkuX5GEw5mDt4tKdl0g== dependencies: color "^4.2.3" use-latest-callback "^0.2.4" use-sync-external-store "^1.5.0" -"@react-navigation/native-stack@7.9.1": - version "7.9.1" - resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-7.9.1.tgz#0d8f576a9c2ca34344b114ae1e380ad0933dc7b1" - integrity sha512-DIRv72UliHvCWkBO1xwvik0maRka4aebn10huL9u4lPRXZZjumlMFHhA9z8vOaj02ri54m8pQUybRdbVzNeqgQ== +"@react-navigation/native-stack@7.12.0": + version "7.12.0" + resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-7.12.0.tgz#0511234ac6030ed6d716561a380cb2225541373c" + integrity sha512-XmNJsPshjkNsahgbxNgGWQUq4s1l6HqH/Fei4QsjBNn/0mTvVrRVZwJ1XrY9YhWYvyiYkAN6/OmarWQaQJ0otQ== dependencies: - "@react-navigation/elements" "^2.9.4" + "@react-navigation/elements" "^2.9.5" color "^4.2.3" sf-symbols-typescript "^2.1.0" warn-once "^0.1.1" -"@react-navigation/native@7.1.27": - version "7.1.27" - resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-7.1.27.tgz#5be53e9fac9f6173d229b334fe41c2208684bac2" - integrity sha512-kW7LGP/RrisktpGyizTKw6HwSeQJdXnAN9L8GyQJcJAlgL9YtfEg6yEyD5n9RWH90CL8G0cRyUhphKIAFf4lVw== +"@react-navigation/native@7.1.28": + version "7.1.28" + resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-7.1.28.tgz#1ee75cf3a8b3e4365f94c5d657bb3c015e387720" + integrity sha512-d1QDn+KNHfHGt3UIwOZvupvdsDdiHYZBEj7+wL2yDVo3tMezamYy60H9s3EnNVE1Ae1ty0trc7F2OKqo/RmsdQ== dependencies: - "@react-navigation/core" "^7.13.7" + "@react-navigation/core" "^7.14.0" escape-string-regexp "^4.0.0" fast-deep-equal "^3.1.3" nanoid "^3.3.11" @@ -2829,6 +3040,11 @@ estree-walker "^2.0.2" picomatch "^4.0.2" +"@sec-ant/readable-stream@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz#60de891bb126abfdc5410fdc6166aca065f10a0c" + integrity sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg== + "@sideway/address@^4.1.5": version "4.1.5" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" @@ -2847,15 +3063,20 @@ integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== "@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + version "0.27.10" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.10.tgz#beefe675f1853f73676aecc915b2bd2ac98c4fc6" + integrity sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA== "@sindresorhus/is@^4.0.0": version "4.6.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== +"@sindresorhus/is@^7.0.1": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-7.2.0.tgz#7c594e1a64336d2008d99d814056d459421504d4" + integrity sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw== + "@sinonjs/commons@^3.0.0": version "3.0.1" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" @@ -2972,9 +3193,9 @@ integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== "@types/express-serve-static-core@*", "@types/express-serve-static-core@^5.0.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz#74f47555b3d804b54cb7030e6f9aa0c7485cfc5b" - integrity sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA== + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz#1a77faffee9572d39124933259be2523837d7eaa" + integrity sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A== dependencies: "@types/node" "*" "@types/qs" "*" @@ -2982,9 +3203,9 @@ "@types/send" "*" "@types/express-serve-static-core@^4.17.21", "@types/express-serve-static-core@^4.17.33": - version "4.19.7" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz#f1d306dcc03b1aafbfb6b4fe684cce8a31cffc10" - integrity sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg== + version "4.19.8" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz#99b960322a4d576b239a640ab52ef191989b036f" + integrity sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA== dependencies: "@types/node" "*" "@types/qs" "*" @@ -2992,13 +3213,13 @@ "@types/send" "*" "@types/express@*": - version "5.0.5" - resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.5.tgz#3ba069177caa34ab96585ca23b3984d752300cdc" - integrity sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ== + version "5.0.6" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.6.tgz#2d724b2c990dcb8c8444063f3580a903f6d500cc" + integrity sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "^5.0.0" - "@types/serve-static" "^1" + "@types/serve-static" "^2" "@types/express@^4.17.25": version "4.17.25" @@ -3032,10 +3253,10 @@ resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== -"@types/http-cache-semantics@*": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" - integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== +"@types/http-cache-semantics@*", "@types/http-cache-semantics@^4.0.4": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#f6a7788f438cbfde15f29acad46512b4c01913b3" + integrity sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q== "@types/http-errors@*": version "2.0.5" @@ -3087,12 +3308,7 @@ dependencies: "@types/lodash" "*" -"@types/lodash@*": - version "4.17.20" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.20.tgz#1ca77361d7363432d29f5e55950d9ec1e1c6ea93" - integrity sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA== - -"@types/lodash@4.17.23": +"@types/lodash@*", "@types/lodash@4.17.23": version "4.17.23" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.23.tgz#c1bb06db218acc8fc232da0447473fc2fb9d9841" integrity sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA== @@ -3103,25 +3319,25 @@ integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== "@types/node@*": - version "24.10.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-24.10.1.tgz#91e92182c93db8bd6224fca031e2370cef9a8f01" - integrity sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ== + version "25.2.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.2.3.tgz#9c18245be768bdb4ce631566c7da303a5c99a7f8" + integrity sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ== dependencies: undici-types "~7.16.0" "@types/node@^20.17.9": - version "20.19.25" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.25.tgz#467da94a2fd966b57cc39c357247d68047611190" - integrity sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ== + version "20.19.33" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.33.tgz#ac8364c623b72d43125f0e7dd722bbe968f0c65e" + integrity sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw== dependencies: undici-types "~6.21.0" -"@types/node@^22.7.7": - version "22.19.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.19.1.tgz#1188f1ddc9f46b4cc3aec76749050b4e1f459b7b" - integrity sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ== +"@types/node@^24.9.0": + version "24.10.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.10.13.tgz#2fac25c0e30f3848e19912c3b8791a28370e9e07" + integrity sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg== dependencies: - undici-types "~6.21.0" + undici-types "~7.16.0" "@types/qs@*": version "6.14.0" @@ -3164,10 +3380,10 @@ resolved "https://registry.yarnpkg.com/@types/react-reconciler/-/react-reconciler-0.28.9.tgz#d24b4864c384e770c83275b3fe73fba00269c83b" integrity sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg== -"@types/react@*", "@types/react@19.2.8": - version "19.2.8" - resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.8.tgz#307011c9f5973a6abab8e17d0293f48843627994" - integrity sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg== +"@types/react@*", "@types/react@19.2.14": + version "19.2.14" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.14.tgz#39604929b5e3957e3a6fa0001dafb17c7af70bad" + integrity sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w== dependencies: csstype "^3.2.2" @@ -3219,6 +3435,14 @@ "@types/node" "*" "@types/send" "<1" +"@types/serve-static@^2": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-2.2.0.tgz#d4a447503ead0d1671132d1ab6bd58b805d8de6a" + integrity sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/shallowequal@1.1.5": version "1.1.5" resolved "https://registry.yarnpkg.com/@types/shallowequal/-/shallowequal-1.1.5.tgz#37e4871c464981b4abee74990c73c8f414cd13dd" @@ -3267,16 +3491,16 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.0.tgz#afb966c66a2fdc6158cf81118204a971a36d0fc5" - integrity sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg== +"@typescript-eslint/eslint-plugin@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz#086d2ef661507b561f7b17f62d3179d692a0765f" + integrity sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ== dependencies: "@eslint-community/regexpp" "^4.12.2" - "@typescript-eslint/scope-manager" "8.53.0" - "@typescript-eslint/type-utils" "8.53.0" - "@typescript-eslint/utils" "8.53.0" - "@typescript-eslint/visitor-keys" "8.53.0" + "@typescript-eslint/scope-manager" "8.55.0" + "@typescript-eslint/type-utils" "8.55.0" + "@typescript-eslint/utils" "8.55.0" + "@typescript-eslint/visitor-keys" "8.55.0" ignore "^7.0.5" natural-compare "^1.4.0" ts-api-utils "^2.4.0" @@ -3296,15 +3520,15 @@ natural-compare "^1.4.0" ts-api-utils "^1.3.0" -"@typescript-eslint/parser@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.53.0.tgz#d8bed6f12dc74e03751e5f947510ff2b165990c6" - integrity sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg== +"@typescript-eslint/parser@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.55.0.tgz#6eace4e9e95f178d3447ed1f17f3d6a5dfdb345c" + integrity sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw== dependencies: - "@typescript-eslint/scope-manager" "8.53.0" - "@typescript-eslint/types" "8.53.0" - "@typescript-eslint/typescript-estree" "8.53.0" - "@typescript-eslint/visitor-keys" "8.53.0" + "@typescript-eslint/scope-manager" "8.55.0" + "@typescript-eslint/types" "8.55.0" + "@typescript-eslint/typescript-estree" "8.55.0" + "@typescript-eslint/visitor-keys" "8.55.0" debug "^4.4.3" "@typescript-eslint/parser@^7.1.1": @@ -3318,13 +3542,13 @@ "@typescript-eslint/visitor-keys" "7.18.0" debug "^4.3.4" -"@typescript-eslint/project-service@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.53.0.tgz#327c67c61c16a1c8b12a440b0779b41eb77cc7df" - integrity sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg== +"@typescript-eslint/project-service@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.55.0.tgz#b8a71c06a625bdad481c24d5614b68e252f3ae9b" + integrity sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ== dependencies: - "@typescript-eslint/tsconfig-utils" "^8.53.0" - "@typescript-eslint/types" "^8.53.0" + "@typescript-eslint/tsconfig-utils" "^8.55.0" + "@typescript-eslint/types" "^8.55.0" debug "^4.4.3" "@typescript-eslint/scope-manager@5.62.0": @@ -3343,18 +3567,18 @@ "@typescript-eslint/types" "7.18.0" "@typescript-eslint/visitor-keys" "7.18.0" -"@typescript-eslint/scope-manager@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.53.0.tgz#f922fcbf0d42e72f065297af31779ccf19de9a97" - integrity sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g== +"@typescript-eslint/scope-manager@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz#8a0752c31c788651840dc98f840b0c2ebe143b8c" + integrity sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q== dependencies: - "@typescript-eslint/types" "8.53.0" - "@typescript-eslint/visitor-keys" "8.53.0" + "@typescript-eslint/types" "8.55.0" + "@typescript-eslint/visitor-keys" "8.55.0" -"@typescript-eslint/tsconfig-utils@8.53.0", "@typescript-eslint/tsconfig-utils@^8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.0.tgz#105279d7969a7abdc8345cc9c57cff83cf910f8f" - integrity sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA== +"@typescript-eslint/tsconfig-utils@8.55.0", "@typescript-eslint/tsconfig-utils@^8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz#62f1d005419985e09d37a040b2f1450e4e805afa" + integrity sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q== "@typescript-eslint/type-utils@7.18.0": version "7.18.0" @@ -3366,14 +3590,14 @@ debug "^4.3.4" ts-api-utils "^1.3.0" -"@typescript-eslint/type-utils@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.53.0.tgz#81a0de5c01fc68f6df0591d03cd8226bda01c91f" - integrity sha512-BBAUhlx7g4SmcLhn8cnbxoxtmS7hcq39xKCgiutL3oNx1TaIp+cny51s8ewnKMpVUKQUGb41RAUWZ9kxYdovuw== +"@typescript-eslint/type-utils@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz#195d854b3e56308ce475fdea2165313bb1190200" + integrity sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g== dependencies: - "@typescript-eslint/types" "8.53.0" - "@typescript-eslint/typescript-estree" "8.53.0" - "@typescript-eslint/utils" "8.53.0" + "@typescript-eslint/types" "8.55.0" + "@typescript-eslint/typescript-estree" "8.55.0" + "@typescript-eslint/utils" "8.55.0" debug "^4.4.3" ts-api-utils "^2.4.0" @@ -3387,10 +3611,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.18.0.tgz#b90a57ccdea71797ffffa0321e744f379ec838c9" integrity sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ== -"@typescript-eslint/types@8.53.0", "@typescript-eslint/types@^8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.53.0.tgz#1adcad3fa32bc2c4cbf3785ba07a5e3151819efb" - integrity sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ== +"@typescript-eslint/types@8.55.0", "@typescript-eslint/types@^8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.55.0.tgz#8449c5a7adac61184cac92dbf6315733569708c2" + integrity sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w== "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" @@ -3419,15 +3643,15 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/typescript-estree@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.0.tgz#7805b46b7a8ce97e91b7bb56fc8b1ba26ca8ef52" - integrity sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw== +"@typescript-eslint/typescript-estree@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz#c83ac92c11ce79bedd984937c7780a65e7f7b2e3" + integrity sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw== dependencies: - "@typescript-eslint/project-service" "8.53.0" - "@typescript-eslint/tsconfig-utils" "8.53.0" - "@typescript-eslint/types" "8.53.0" - "@typescript-eslint/visitor-keys" "8.53.0" + "@typescript-eslint/project-service" "8.55.0" + "@typescript-eslint/tsconfig-utils" "8.55.0" + "@typescript-eslint/types" "8.55.0" + "@typescript-eslint/visitor-keys" "8.55.0" debug "^4.4.3" minimatch "^9.0.5" semver "^7.7.3" @@ -3444,15 +3668,15 @@ "@typescript-eslint/types" "7.18.0" "@typescript-eslint/typescript-estree" "7.18.0" -"@typescript-eslint/utils@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.53.0.tgz#bf0a4e2edaf1afc9abce209fc02f8cab0b74af13" - integrity sha512-XDY4mXTez3Z1iRDI5mbRhH4DFSt46oaIFsLg+Zn97+sYrXACziXSQcSelMybnVZ5pa1P6xYkPr5cMJyunM1ZDA== +"@typescript-eslint/utils@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.55.0.tgz#c1744d94a3901deb01f58b09d3478d811f96d619" + integrity sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow== dependencies: "@eslint-community/eslint-utils" "^4.9.1" - "@typescript-eslint/scope-manager" "8.53.0" - "@typescript-eslint/types" "8.53.0" - "@typescript-eslint/typescript-estree" "8.53.0" + "@typescript-eslint/scope-manager" "8.55.0" + "@typescript-eslint/types" "8.55.0" + "@typescript-eslint/typescript-estree" "8.55.0" "@typescript-eslint/utils@^5.10.0": version "5.62.0" @@ -3484,12 +3708,12 @@ "@typescript-eslint/types" "7.18.0" eslint-visitor-keys "^3.4.3" -"@typescript-eslint/visitor-keys@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.0.tgz#9a785664ddae7e3f7e570ad8166e48dbc9c6cf02" - integrity sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw== +"@typescript-eslint/visitor-keys@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz#3d9a40fd4e3705c63d8fae3af58988add3ed464d" + integrity sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA== dependencies: - "@typescript-eslint/types" "8.53.0" + "@typescript-eslint/types" "8.55.0" eslint-visitor-keys "^4.2.1" "@ungap/structured-clone@^1.3.0": @@ -3514,9 +3738,9 @@ wonka "^6.3.2" "@vscode/sudo-prompt@^9.0.0": - version "9.3.1" - resolved "https://registry.yarnpkg.com/@vscode/sudo-prompt/-/sudo-prompt-9.3.1.tgz#c562334bc6647733649fd42afc96c0eea8de3b65" - integrity sha512-9ORTwwS74VaTn38tNbQhsA5U44zkJfcb0BdTSyyG6frP4e8KMtHuTXYmwefe5dpL8XB1aGSIVTaLjD3BbWb5iA== + version "9.3.2" + resolved "https://registry.yarnpkg.com/@vscode/sudo-prompt/-/sudo-prompt-9.3.2.tgz#692ba38df40bd3502ccc4e9f099fbbaedbd5f04e" + integrity sha512-gcXoCN00METUNFeQOFJ+C9xUI0DKB+0EGMVg7wbVYRHBw2Eq3fKisDZOkRdOz3kqXRKOENMfShPOmypw1/8nOw== "@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": version "1.14.1" @@ -3688,7 +3912,7 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" -accepts@^1.3.7, accepts@^1.3.8, accepts@~1.3.4, accepts@~1.3.7, accepts@~1.3.8: +accepts@^1.3.7, accepts@^1.3.8, accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -3761,9 +3985,9 @@ anser@^1.4.9: integrity sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww== anser@^2.1.1: - version "2.3.3" - resolved "https://registry.yarnpkg.com/anser/-/anser-2.3.3.tgz#84bca8b8a668ae3c7cf49355affb99494444ccc1" - integrity sha512-QGY1oxYE7/kkeNmbtY/2ZjQ07BCG3zYdz+k/+sf69kMzEIxb93guHkPnIXITQ+BYi61oQwG74twMOX1tD4aesg== + version "2.3.5" + resolved "https://registry.yarnpkg.com/anser/-/anser-2.3.5.tgz#3435896b68b93e5e744842499d0ce3e6f6d013ee" + integrity sha512-vcZjxvvVoxTeR5XBNJB38oTu/7eDCZlwdz32N1eNgpyPF7j/Z7Idf+CUwQOkKKpJ7RJyjxgLHCM7vdIK0iCNMQ== ansi-escapes@^4.2.1: version "4.3.2" @@ -3914,7 +4138,7 @@ array.prototype.flatmap@^1.3.3: es-abstract "^1.23.5" es-shim-unscopables "^1.0.2" -array.prototype.reduce@^1.0.6: +array.prototype.reduce@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.8.tgz#42f97f5078daedca687d4463fd3c05cbfd83da57" integrity sha512-DwuEqgXFBwbmZSRqt3BpQigWNUoqw9Ml2dTWdF3B2zQlQX4OeUE0zyuzX0fX0IbTvjdkZbcBTU3idgpO78qkTw== @@ -3981,11 +4205,6 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - author-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/author-regex/-/author-regex-1.0.0.tgz#d08885be6b9bbf9439fe087c76287245f0a81450" @@ -4050,13 +4269,13 @@ babel-plugin-module-resolver@5.0.2: reselect "^4.1.7" resolve "^1.22.8" -babel-plugin-polyfill-corejs2@^0.4.14: - version "0.4.14" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz#8101b82b769c568835611542488d463395c2ef8f" - integrity sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg== +babel-plugin-polyfill-corejs2@^0.4.14, babel-plugin-polyfill-corejs2@^0.4.15: + version "0.4.15" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz#808fa349686eea4741807cfaaa2aa3aa57ce120a" + integrity sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw== dependencies: - "@babel/compat-data" "^7.27.7" - "@babel/helper-define-polyfill-provider" "^0.6.5" + "@babel/compat-data" "^7.28.6" + "@babel/helper-define-polyfill-provider" "^0.6.6" semver "^6.3.1" babel-plugin-polyfill-corejs3@^0.13.0: @@ -4067,12 +4286,20 @@ babel-plugin-polyfill-corejs3@^0.13.0: "@babel/helper-define-polyfill-provider" "^0.6.5" core-js-compat "^3.43.0" -babel-plugin-polyfill-regenerator@^0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz#32752e38ab6f6767b92650347bf26a31b16ae8c5" - integrity sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg== +babel-plugin-polyfill-corejs3@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz#65b06cda48d6e447e1e926681f5a247c6ae2b9cf" + integrity sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ== dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.5" + "@babel/helper-define-polyfill-provider" "^0.6.6" + core-js-compat "^3.48.0" + +babel-plugin-polyfill-regenerator@^0.6.5, babel-plugin-polyfill-regenerator@^0.6.6: + version "0.6.6" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz#69f5dd263cab933c42fe5ea05e83443b374bd4bf" + integrity sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.6" babel-plugin-react-compiler@1.0.0, babel-plugin-react-compiler@^1.0.0: version "1.0.0" @@ -4121,10 +4348,10 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" -babel-preset-expo@54.0.9, babel-preset-expo@~54.0.9: - version "54.0.9" - resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-54.0.9.tgz#88af355f08dc49b4b54ac559c02ce8890ab08930" - integrity sha512-8J6hRdgEC2eJobjoft6mKJ294cLxmi3khCUy2JJQp4htOYYkllSLUq6vudWJkTJiIuGdVR4bR6xuz2EvJLWHNg== +babel-preset-expo@54.0.10, babel-preset-expo@~54.0.10: + version "54.0.10" + resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-54.0.10.tgz#3b70f4af3a5f65f945d7957ef511ee016e8f2fd6" + integrity sha512-wTt7POavLFypLcPW/uC5v8y+mtQKDJiyGLzYCjqr9tx0Qc3vCXcDKk1iCFIj/++Iy5CWhhTflEa7VvVPNWeCfw== dependencies: "@babel/helper-module-imports" "^7.25.9" "@babel/plugin-proposal-decorators" "^7.12.9" @@ -4167,7 +4394,7 @@ base64-js@^1.2.3, base64-js@^1.3.1, base64-js@^1.5.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -baseline-browser-mapping@^2.8.25, baseline-browser-mapping@^2.9.0: +baseline-browser-mapping@^2.9.0: version "2.9.19" resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz#3e508c43c46d961eb4d7d2e5b8d1dd0f9ee4f488" integrity sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg== @@ -4220,25 +4447,7 @@ bluebird@^3.1.1: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -body-parser@^1.20.3: - version "1.20.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" - integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.13.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" - -body-parser@~1.20.3: +body-parser@^1.20.3, body-parser@~1.20.3: version "1.20.4" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f" integrity sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA== @@ -4317,18 +4526,7 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.24.0, browserslist@^4.25.0, browserslist@^4.28.0: - version "4.28.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.0.tgz#9cefece0a386a17a3cd3d22ebf67b9deca1b5929" - integrity sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ== - dependencies: - baseline-browser-mapping "^2.8.25" - caniuse-lite "^1.0.30001754" - electron-to-chromium "^1.5.249" - node-releases "^2.0.27" - update-browserslist-db "^1.1.4" - -browserslist@^4.28.1: +browserslist@^4.24.0, browserslist@^4.25.0, browserslist@^4.28.1: version "4.28.1" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== @@ -4371,6 +4569,11 @@ bundle-name@^4.1.0: dependencies: run-applescript "^7.0.0" +byte-counter@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/byte-counter/-/byte-counter-0.1.0.tgz#c49760b5790e50e942a0d57a57b3fc0e94488dcc" + integrity sha512-jheRLVMeUKrDBjVw2O5+k4EvR4t9wtxHL+bo/LxfkxsVeuGMy3a5SEGgXdAFA4FSzTrU8rQXQIrsZ3oBq5a0pQ== + bytes@3.1.2, bytes@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -4386,6 +4589,24 @@ cacheable-lookup@^5.0.3: resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== +cacheable-lookup@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz#3476a8215d046e5a3202a9209dd13fec1f933a27" + integrity sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w== + +cacheable-request@^13.0.12: + version "13.0.18" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-13.0.18.tgz#b256c499df6c266d3c2ed075009f54e6d08fe431" + integrity sha512-rFWadDRKJs3s2eYdXlGggnBZKG7MTblkFBB0YllFds+UYnfogDp2wcR6JN97FhRkHTvq59n2vhNoHNZn29dh/Q== + dependencies: + "@types/http-cache-semantics" "^4.0.4" + get-stream "^9.0.1" + http-cache-semantics "^4.2.0" + keyv "^5.5.5" + mimic-response "^4.0.0" + normalize-url "^8.1.1" + responselike "^4.0.2" + cacheable-request@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817" @@ -4448,15 +4669,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001754: - version "1.0.30001755" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001755.tgz#c01cfb1c30f5acf1229391666ec03492f4c332ff" - integrity sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA== - caniuse-lite@^1.0.30001759: - version "1.0.30001760" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz#bdd1960fafedf8d5f04ff16e81460506ff9b798f" - integrity sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw== + version "1.0.30001769" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz#1ad91594fad7dc233777c2781879ab5409f7d9c2" + integrity sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg== chalk@^2.0.1, chalk@^2.4.2: version "2.4.2" @@ -4532,11 +4748,6 @@ ci-info@^3.2.0, ci-info@^3.3.0, ci-info@^3.7.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== -circular-dependency-plugin@5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz#39e836079db1d3cf2f988dc48c5188a44058b600" - integrity sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ== - classnames@2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" @@ -4667,6 +4878,11 @@ commander@^12.0.0, commander@^12.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== +commander@^13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-13.1.0.tgz#776167db68c78f38dcce1f9b8d7b8b9a488abf46" + integrity sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw== + commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -4677,11 +4893,6 @@ commander@^4.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -commander@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" - integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== - commander@^6.2.0: version "6.2.1" resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" @@ -4707,11 +4918,6 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== -compare-version@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/compare-version/-/compare-version-0.1.2.tgz#0162ec2d9351f5ddd59a9202cba935366a725080" - integrity sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A== - compressible@~2.0.18: version "2.0.18" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" @@ -4779,22 +4985,22 @@ cookie@~0.7.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== -core-js-compat@^3.43.0: - version "3.47.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.47.0.tgz#698224bbdbb6f2e3f39decdda4147b161e3772a3" - integrity sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ== +core-js-compat@^3.43.0, core-js-compat@^3.48.0: + version "3.48.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.48.0.tgz#7efbe1fc1cbad44008190462217cc5558adaeaa6" + integrity sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q== dependencies: - browserslist "^4.28.0" + browserslist "^4.28.1" core-js-pure@^3.23.3: - version "3.47.0" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.47.0.tgz#1104df8a3b6eb9189fcc559b5a65b90f66e7e887" - integrity sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw== + version "3.48.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.48.0.tgz#7d5a3fe1ec3631b9aa76a81c843ac2ce918e5023" + integrity sha512-1slJgk89tWC51HQ1AEqG+s2VuwpTRr8ocu4n20QUcH1v9lAN0RXen0Q0AABa/DK1I7RrNWLucplOHMx8hfTGTw== -core-js@^3.30.2: - version "3.47.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.47.0.tgz#436ef07650e191afeb84c24481b298bd60eb4a17" - integrity sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg== +core-js@^3.48.0: + version "3.48.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.48.0.tgz#1f813220a47bbf0e667e3885c36cd6f0593bf14d" + integrity sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ== core-util-is@~1.0.0: version "1.0.3" @@ -4811,17 +5017,13 @@ cosmiconfig@^9.0.0: js-yaml "^4.1.0" parse-json "^5.2.0" -cross-dirname@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/cross-dirname/-/cross-dirname-0.1.0.tgz#b899599f30a5389f59e78c150e19f957ad16a37c" - integrity sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q== - -cross-env@7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" - integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== +cross-env@10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-10.1.0.tgz#cfd2a6200df9ed75bfb9cb3d7ce609c13ea21783" + integrity sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw== dependencies: - cross-spawn "^7.0.1" + "@epic-web/invariant" "^1.0.0" + cross-spawn "^7.0.6" cross-fetch@^3.1.5: version "3.2.0" @@ -4851,19 +5053,19 @@ css-in-js-utils@^3.1.0: dependencies: hyphenate-style-name "^1.0.3" -css-loader@7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-7.1.2.tgz#64671541c6efe06b0e22e750503106bdd86880f8" - integrity sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA== +css-loader@7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-7.1.3.tgz#c0de715ceabe39b8531a85fcaf6734a430c4d99a" + integrity sha512-frbERmjT0UC5lMheWpJmMilnt9GEhbZJN/heUb7/zaJYeIzj5St9HvDcfshzzOqbsS+rYpMk++2SD3vGETDSyA== dependencies: icss-utils "^5.1.0" - postcss "^8.4.33" + postcss "^8.4.40" postcss-modules-extract-imports "^3.1.0" postcss-modules-local-by-default "^4.0.5" postcss-modules-scope "^3.2.0" postcss-modules-values "^4.0.0" postcss-value-parser "^4.2.0" - semver "^7.5.4" + semver "^7.6.3" css-select@^4.1.3: version "4.3.0" @@ -4935,7 +5137,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.4.0, debug@^4.4.1, debug@^4.4.3: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.4.0, debug@^4.4.1, debug@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== @@ -4959,6 +5161,13 @@ decode-uri-component@^0.2.2: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== +decompress-response@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-10.0.0.tgz#d8abd2a4c136c3b99b49a08d1f9a709fe35675a4" + integrity sha512-oj7KWToJuuxlPr7VV0vabvxEIiqNMo+q0NueIiL3XhtwC6FVOX7Hr1c0C4eD0bmf7Zr+S/dSf2xvkH3Ad6sU3Q== + dependencies: + mimic-response "^4.0.0" + decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -4987,9 +5196,9 @@ default-browser-id@^5.0.0: integrity sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q== default-browser@^5.2.1: - version "5.4.0" - resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.4.0.tgz#b55cf335bb0b465dd7c961a02cd24246aa434287" - integrity sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg== + version "5.5.0" + resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.5.0.tgz#2792e886f2422894545947cc80e1a444496c5976" + integrity sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw== dependencies: bundle-name "^4.1.0" default-browser-id "^5.0.0" @@ -5169,23 +5378,18 @@ electron-positioner@^4.1.0: resolved "https://registry.yarnpkg.com/electron-positioner/-/electron-positioner-4.1.0.tgz#e158f8f6aabd6725a8a9b4f2279b9504bcbea1b0" integrity sha512-726DfbI9ZNoCg+Fcu6XLuTKTnzf+6nFqv7h+K/V6Ug7IbaPMI7s9S8URnGtWFCy5N5PL4HSzRFF2mXuinftDdg== -electron-to-chromium@^1.5.249: - version "1.5.255" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.255.tgz#fe9294ce172241eb50733bc00f2bd00d9c1e4ec7" - integrity sha512-Z9oIp4HrFF/cZkDPMpz2XSuVpc1THDpT4dlmATFlJUIBVCy9Vap5/rIXsASP1CscBacBqhabwh8vLctqBwEerQ== - electron-to-chromium@^1.5.263: - version "1.5.267" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz#5d84f2df8cdb6bfe7e873706bb21bd4bfb574dc7" - integrity sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw== + version "1.5.286" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz#142be1ab5e1cd5044954db0e5898f60a4960384e" + integrity sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A== -electron@39.2.7: - version "39.2.7" - resolved "https://registry.yarnpkg.com/electron/-/electron-39.2.7.tgz#1cf2371304994fe26c564764bd50878fe405fb4b" - integrity sha512-KU0uFS6LSTh4aOIC3miolcbizOFP7N1M46VTYVfqIgFiuA2ilfNaOHLDS9tCMvwwHRowAsvqBrh9NgMXcTOHCQ== +electron@40.4.0: + version "40.4.0" + resolved "https://registry.yarnpkg.com/electron/-/electron-40.4.0.tgz#e2e73d837df141216dcd91fb6c3f754e771387c2" + integrity sha512-31l4V7Ys4oUuXyaN/cCNnyBdDXN9RwOVOG+JhiHCf4zx5tZkHd43PKGY6KLEWpeYCxaphsuGSEjagJLfPqKj8g== dependencies: "@electron/get" "^2.0.0" - "@types/node" "^22.7.7" + "@types/node" "^24.9.0" extract-zip "^2.0.1" emoji-datasource-apple@16.0.0: @@ -5225,13 +5429,13 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^5.17.4: - version "5.18.4" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz#c22d33055f3952035ce6a144ce092447c525f828" - integrity sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q== +enhanced-resolve@^5.19.0: + version "5.19.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz#6687446a15e969eaa63c2fa2694510e17ae6d97c" + integrity sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg== dependencies: graceful-fs "^4.2.4" - tapable "^2.2.0" + tapable "^2.3.0" entities@^2.0.0: version "2.2.0" @@ -5248,10 +5452,15 @@ env-paths@^2.2.0, env-paths@^2.2.1: resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== +env-paths@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-3.0.0.tgz#2f1e89c2f6dbd3408e1b1711dd82d62e317f58da" + integrity sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A== + envinfo@^7.13.0, envinfo@^7.14.0: - version "7.20.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.20.0.tgz#3fd9de69fb6af3e777a017dfa033676368d67dd7" - integrity sha512-+zUomDcLXsVkQ37vUqWBvQwLaLlj8eZPSi61llaEFAVBY5mhcXdaSw1pSJVl4yTYD5g/gEfpNl28YYk4IPvrrg== + version "7.21.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.21.0.tgz#04a251be79f92548541f37d13c8b6f22940c3bae" + integrity sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow== err-code@^2.0.2: version "2.0.3" @@ -5273,17 +5482,17 @@ error-stack-parser@^2.0.6: stackframe "^1.3.4" errorhandler@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/errorhandler/-/errorhandler-1.5.1.tgz#b9ba5d17cf90744cd1e851357a6e75bf806a9a91" - integrity sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A== + version "1.5.2" + resolved "https://registry.yarnpkg.com/errorhandler/-/errorhandler-1.5.2.tgz#dd0aa3952eca44aff7c2985e7d246c5932d70444" + integrity sha512-kNAL7hESndBCrWwS72QyV3IVOTrVmj9D062FV5BQswNL5zEdeRmz/WJFyh6Aj/plvvSOrzddkxW57HgkZcR9Fw== dependencies: - accepts "~1.3.7" + accepts "~1.3.8" escape-html "~1.0.3" -es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9, es-abstract@^1.24.0: - version "1.24.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.0.tgz#c44732d2beb0acc1ed60df840869e3106e7af328" - integrity sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg== +es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9, es-abstract@^1.24.0, es-abstract@^1.24.1: + version "1.24.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.1.tgz#f0c131ed5ea1bb2411134a8dd94def09c46c7899" + integrity sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw== dependencies: array-buffer-byte-length "^1.0.2" arraybuffer.prototype.slice "^1.0.4" @@ -5356,25 +5565,25 @@ es-errors@^1.3.0: integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== es-iterator-helpers@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz#d1dd0f58129054c0ad922e6a9a1e65eef435fe75" - integrity sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w== + version "1.2.2" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz#d979a9f686e2b0b72f88dbead7229924544720bc" + integrity sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w== dependencies: call-bind "^1.0.8" - call-bound "^1.0.3" + call-bound "^1.0.4" define-properties "^1.2.1" - es-abstract "^1.23.6" + es-abstract "^1.24.1" es-errors "^1.3.0" - es-set-tostringtag "^2.0.3" + es-set-tostringtag "^2.1.0" function-bind "^1.1.2" - get-intrinsic "^1.2.6" + get-intrinsic "^1.3.0" globalthis "^1.0.4" gopd "^1.2.0" has-property-descriptors "^1.0.2" has-proto "^1.2.0" has-symbols "^1.1.0" internal-slot "^1.1.0" - iterator.prototype "^1.1.4" + iterator.prototype "^1.1.5" safe-array-concat "^1.1.3" es-module-lexer@^2.0.0: @@ -5389,7 +5598,7 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: dependencies: es-errors "^1.3.0" -es-set-tostringtag@^2.0.3, es-set-tostringtag@^2.1.0: +es-set-tostringtag@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== @@ -5420,7 +5629,7 @@ es6-error@^4.1.1: resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -esbuild@^0.25.0, esbuild@~0.25.0: +esbuild@^0.25.0: version "0.25.12" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.12.tgz#97a1d041f4ab00c2fce2f838d2b9969a2d2a97a5" integrity sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg== @@ -5452,6 +5661,38 @@ esbuild@^0.25.0, esbuild@~0.25.0: "@esbuild/win32-ia32" "0.25.12" "@esbuild/win32-x64" "0.25.12" +esbuild@~0.27.0: + version "0.27.3" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.3.tgz#5859ca8e70a3af956b26895ce4954d7e73bd27a8" + integrity sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg== + optionalDependencies: + "@esbuild/aix-ppc64" "0.27.3" + "@esbuild/android-arm" "0.27.3" + "@esbuild/android-arm64" "0.27.3" + "@esbuild/android-x64" "0.27.3" + "@esbuild/darwin-arm64" "0.27.3" + "@esbuild/darwin-x64" "0.27.3" + "@esbuild/freebsd-arm64" "0.27.3" + "@esbuild/freebsd-x64" "0.27.3" + "@esbuild/linux-arm" "0.27.3" + "@esbuild/linux-arm64" "0.27.3" + "@esbuild/linux-ia32" "0.27.3" + "@esbuild/linux-loong64" "0.27.3" + "@esbuild/linux-mips64el" "0.27.3" + "@esbuild/linux-ppc64" "0.27.3" + "@esbuild/linux-riscv64" "0.27.3" + "@esbuild/linux-s390x" "0.27.3" + "@esbuild/linux-x64" "0.27.3" + "@esbuild/netbsd-arm64" "0.27.3" + "@esbuild/netbsd-x64" "0.27.3" + "@esbuild/openbsd-arm64" "0.27.3" + "@esbuild/openbsd-x64" "0.27.3" + "@esbuild/openharmony-arm64" "0.27.3" + "@esbuild/sunos-x64" "0.27.3" + "@esbuild/win32-arm64" "0.27.3" + "@esbuild/win32-ia32" "0.27.3" + "@esbuild/win32-x64" "0.27.3" + escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" @@ -5462,7 +5703,7 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== @@ -5671,9 +5912,9 @@ esprima@^4.0.0: integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + version "1.7.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== dependencies: estraverse "^5.1.0" @@ -5776,15 +6017,7 @@ expo-clipboard@8.0.8: resolved "https://registry.yarnpkg.com/expo-clipboard/-/expo-clipboard-8.0.8.tgz#5e52054a4bbaebef090ec6fe5eaa200072ff94f7" integrity sha512-VKoBkHIpZZDJTB0jRO4/PZskHdMNOEz3P/41tmM6fDuODMpqhvyWK053X0ebspkxiawJX9lX33JXHBCvVsTTOA== -expo-constants@~18.0.12: - version "18.0.12" - resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-18.0.12.tgz#3e67f7109ffd03eaa5514c19875a767c39f5a2ca" - integrity sha512-WzcKYMVNRRu4NcSzfIVRD5aUQFnSpTZgXFrlWmm19xJoDa4S3/PQNi6PNTBRc49xz9h8FT7HMxRKaC8lr0gflA== - dependencies: - "@expo/config" "~12.0.12" - "@expo/env" "~2.0.8" - -expo-constants@~18.0.13: +expo-constants@~18.0.12, expo-constants@~18.0.13: version "18.0.13" resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-18.0.13.tgz#0117f1f3d43be7b645192c0f4f431fb4efc4803d" integrity sha512-FnZn12E1dRYKDHlAdIyNFhBurKTS3F9CrfrBDJI5m3D7U17KBHMQ6JEfYlSj7LG7t+Ulr+IKaj58L1k5gBwTcQ== @@ -5802,10 +6035,10 @@ expo-file-system@19.0.21, expo-file-system@~19.0.21: resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-19.0.21.tgz#e96a68107fb629cf0dd1906fe7b46b566ff13e10" integrity sha512-s3DlrDdiscBHtab/6W1osrjGL+C2bvoInPJD7sOwmxfJ5Woynv2oc+Fz1/xVXaE/V7HE/+xrHC/H45tu6lZzzg== -expo-font@~14.0.10: - version "14.0.10" - resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-14.0.10.tgz#33fb9f6dc5661729192a6bc8cd6f08bd1a9097cc" - integrity sha512-UqyNaaLKRpj4pKAP4HZSLnuDQqueaO5tB1c/NWu5vh1/LF9ulItyyg2kF/IpeOp0DeOLk0GY0HrIXaKUMrwB+Q== +expo-font@~14.0.11: + version "14.0.11" + resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-14.0.11.tgz#198743d17332520545107df026d8a261e6b2732f" + integrity sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg== dependencies: fontfaceobserver "^2.1.0" @@ -5886,26 +6119,26 @@ expo-task-manager@14.0.9: dependencies: unimodules-app-loader "~6.0.8" -expo@54.0.31: - version "54.0.31" - resolved "https://registry.yarnpkg.com/expo/-/expo-54.0.31.tgz#c9b88d7154039bb7165abc21d73aec5e5fde6d71" - integrity sha512-kQ3RDqA/a59I7y+oqQGyrPbbYlgPMUdKBOgvFLpoHbD2bCM+F75i4N0mUijy7dG5F/CUCu2qHmGGUCXBbMDkCg== +expo@54.0.33: + version "54.0.33" + resolved "https://registry.yarnpkg.com/expo/-/expo-54.0.33.tgz#f7d572857323f5a8250a9afe245a487d2ee2735f" + integrity sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw== dependencies: "@babel/runtime" "^7.20.0" - "@expo/cli" "54.0.21" + "@expo/cli" "54.0.23" "@expo/config" "~12.0.13" "@expo/config-plugins" "~54.0.4" "@expo/devtools" "0.1.8" "@expo/fingerprint" "0.15.4" "@expo/metro" "~54.2.0" - "@expo/metro-config" "54.0.13" + "@expo/metro-config" "54.0.14" "@expo/vector-icons" "^15.0.3" "@ungap/structured-clone" "^1.3.0" - babel-preset-expo "~54.0.9" + babel-preset-expo "~54.0.10" expo-asset "~12.0.12" expo-constants "~18.0.13" expo-file-system "~19.0.21" - expo-font "~14.0.10" + expo-font "~14.0.11" expo-keep-awake "~15.0.8" expo-modules-autolinking "3.0.24" expo-modules-core "3.0.29" @@ -5955,7 +6188,7 @@ express@^4.22.1: utils-merge "1.0.1" vary "~1.1.2" -extract-zip@^2.0.0, extract-zip@^2.0.1: +extract-zip@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== @@ -6010,9 +6243,9 @@ fastest-levenshtein@^1.0.12: integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== fastq@^1.6.0: - version "1.19.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" - integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + version "1.20.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" + integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== dependencies: reusify "^1.0.4" @@ -6067,19 +6300,17 @@ file-entry-cache@^8.0.0: dependencies: flat-cache "^4.0.0" -filename-reserved-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" - integrity sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ== +filename-reserved-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz#3d5dd6d4e2d73a3fed2ebc4cd0b3448869a081f7" + integrity sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw== -filenamify@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-4.3.0.tgz#62391cb58f02b09971c9d4f9d63b3cf9aba03106" - integrity sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg== +filenamify@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-6.0.0.tgz#38def94098c62154c42a41d822650f5f55bcbac2" + integrity sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ== dependencies: - filename-reserved-regex "^2.0.0" - strip-outer "^1.0.1" - trim-repeated "^1.0.0" + filename-reserved-regex "^3.0.0" fill-range@^7.1.1: version "7.1.1" @@ -6190,13 +6421,12 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== -flora-colossus@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/flora-colossus/-/flora-colossus-2.0.0.tgz#af1e85db0a8256ef05f3fb531c1235236c97220a" - integrity sha512-dz4HxH6pOvbUzZpZ/yXhafjbR2I8cenK5xL0KtBFb7U2ADsR+OwXifnxZjij/pZWF775uSCMzWVd+jDik2H2IA== +flora-colossus@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/flora-colossus/-/flora-colossus-3.0.2.tgz#fb309f9041f309a03728de3306796b1239a3ede5" + integrity sha512-Jk78K/Tzt6saxQPGChlJw69xuFGpWyTSAS8EdU0h/FyXwD2K46yNOXmo6nRHcZ9ooekyBAzMkwmiGNt7wOC5zg== dependencies: - debug "^4.3.4" - fs-extra "^10.1.0" + debug "^4.4.1" flow-enums-runtime@^0.0.6: version "0.0.6" @@ -6220,14 +6450,27 @@ for-each@^0.3.3, for-each@^0.3.5: dependencies: is-callable "^1.2.7" +foreground-child@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== + dependencies: + cross-spawn "^7.0.6" + signal-exit "^4.0.1" + +form-data-encoder@^4.0.2: + version "4.1.0" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-4.1.0.tgz#497cedc94810bd5d53b99b5d4f6c152d5cbc9db2" + integrity sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw== + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== -framed-msgpack-rpc@keybase/node-framed-msgpack-rpc#nojima/HOTPOT-use-buffer: +framed-msgpack-rpc@keybase/node-framed-msgpack-rpc#nojima/HOTPOT-use-buffer-iserror: version "1.1.24" - resolved "https://codeload.github.com/keybase/node-framed-msgpack-rpc/tar.gz/be19aba75ae0b67e301953efe87933aaaae7dfd8" + resolved "https://codeload.github.com/keybase/node-framed-msgpack-rpc/tar.gz/9c7cf455201444cc6dbe1495e7343df3c5c2e1e5" dependencies: iced-error "0.0.13" iced-lock "2.0.1" @@ -6244,7 +6487,7 @@ freeport-async@^2.0.0: resolved "https://registry.yarnpkg.com/freeport-async/-/freeport-async-2.0.0.tgz#6adf2ec0c629d11abff92836acd04b399135bab4" integrity sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ== -fresh@0.5.2, fresh@~0.5.2: +fresh@~0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== @@ -6258,7 +6501,7 @@ fs-extra@11.3.3: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^10.0.0, fs-extra@^10.1.0: +fs-extra@^10.0.0: version "10.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== @@ -6267,15 +6510,6 @@ fs-extra@^10.0.0, fs-extra@^10.1.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^11.1.0, fs-extra@^11.1.1: - version "11.3.2" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.2.tgz#c838aeddc6f4a8c74dd15f85e11fe5511bfe02a4" - integrity sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -6285,16 +6519,6 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.1: - version "9.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -6332,14 +6556,13 @@ functions-have-names@^1.2.3: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== -galactus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/galactus/-/galactus-1.0.0.tgz#c2615182afa0c6d0859b92e56ae36d052827db7e" - integrity sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ== +galactus@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/galactus/-/galactus-2.0.2.tgz#04a4e403e28bdae7622ed7b46519953bb0717098" + integrity sha512-HmKyTFGomdAchz4umx8MwBnrnfFmdpwiTyGA4ZOF7rya2Lmgbc9qate4yweInL+0gUBVImhaz12SBGpW3SY4Yg== dependencies: - debug "^4.3.4" - flora-colossus "^2.0.0" - fs-extra "^10.1.0" + debug "^4.4.1" + flora-colossus "^3.0.2" generator-function@^2.0.0: version "2.0.1" @@ -6407,6 +6630,14 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-stream@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-9.0.1.tgz#95157d21df8eb90d1647102b63039b1df60ebd27" + integrity sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA== + dependencies: + "@sec-ant/readable-stream" "^0.4.1" + is-stream "^4.0.1" + get-symbol-description@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" @@ -6417,9 +6648,9 @@ get-symbol-description@^1.1.0: get-intrinsic "^1.2.6" get-tsconfig@^4.7.5: - version "4.13.0" - resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.13.0.tgz#fcdd991e6d22ab9a600f00e91c318707a5d9a0d7" - integrity sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ== + version "4.13.6" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.13.6.tgz#2fbfda558a98a691a798f123afd95915badce876" + integrity sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw== dependencies: resolve-pkg-maps "^1.0.0" @@ -6442,7 +6673,7 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob-to-regex.js@^1.0.1: +glob-to-regex.js@^1.0.0, glob-to-regex.js@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz#2b323728271d133830850e32311f40766c5f6413" integrity sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ== @@ -6452,16 +6683,28 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^13.0.0: - version "13.0.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.0.tgz#9d9233a4a274fc28ef7adce5508b7ef6237a1be3" - integrity sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA== +glob@^11.0.1: + version "11.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-11.1.0.tgz#4f826576e4eb99c7dad383793d2f9f08f67e50a6" + integrity sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw== dependencies: + foreground-child "^3.3.1" + jackspeak "^4.1.1" minimatch "^10.1.1" minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^2.0.0" + +glob@^13.0.0: + version "13.0.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.2.tgz#74b28859255e319c84d1aed1a0a5b5248bfea227" + integrity sha512-035InabNu/c1lW0tzPhAgapKctblppqsKKG9ZaNzbr+gXwWMjXoiyGSyB9sArzrjG7jY+zntRq5ZSUYemrnWVQ== + dependencies: + minimatch "^10.1.2" + minipass "^7.1.2" path-scurry "^2.0.0" -glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.1.1, glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -6527,10 +6770,10 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -google-libphonenumber@3.2.43: - version "3.2.43" - resolved "https://registry.yarnpkg.com/google-libphonenumber/-/google-libphonenumber-3.2.43.tgz#c1e5107ab9c6e3848dc2108e380bde08da80931c" - integrity sha512-TbIX/UC3BFRJwCxbBeCPwuRC4Qws9Jz/CECmfTM1t9RFoI3X6eRThurv6AYr9wSrt640IA9KFIHuAD/vlyjqRw== +google-libphonenumber@3.2.44: + version "3.2.44" + resolved "https://registry.yarnpkg.com/google-libphonenumber/-/google-libphonenumber-3.2.44.tgz#694ec9d5581f013b881c4c2937791e973a45f420" + integrity sha512-9p2TghluF2LTChFMLWsDRD5N78SZDsILdUk4gyqYxBXluCyxoPiOq+Fqt7DKM+LUd33+OgRkdrc+cPR93AypCQ== gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" @@ -6554,6 +6797,24 @@ got@^11.8.5: p-cancelable "^2.0.0" responselike "^2.0.0" +got@^14.4.5: + version "14.6.6" + resolved "https://registry.yarnpkg.com/got/-/got-14.6.6.tgz#5adf7f576f89de8e291fd782e8b277f9bcc8e6c0" + integrity sha512-QLV1qeYSo5l13mQzWgP/y0LbMr5Plr5fJilgAIwgnwseproEbtNym8xpLsDzeZ6MWXgNE6kdWGBjdh3zT/Qerg== + dependencies: + "@sindresorhus/is" "^7.0.1" + byte-counter "^0.1.0" + cacheable-lookup "^7.0.0" + cacheable-request "^13.0.12" + decompress-response "^10.0.0" + form-data-encoder "^4.0.2" + http2-wrapper "^2.2.1" + keyv "^5.5.3" + lowercase-keys "^3.0.0" + p-cancelable "^4.0.1" + responselike "^4.0.2" + type-fest "^4.26.1" + graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -6712,10 +6973,10 @@ html-minifier-terser@^6.0.2: relateurl "^0.2.7" terser "^5.10.0" -html-webpack-plugin@5.6.5: - version "5.6.5" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.5.tgz#d57defb83cabbf29bf56b2d4bf10b67b650066be" - integrity sha512-4xynFbKNNk+WlzXeQQ+6YYsH2g7mpfPszQZUi3ovKlj+pDmngQ7vRXjrrmGROabmKwyQkcgcX5hqfOwHbFmK5g== +html-webpack-plugin@5.6.6: + version "5.6.6" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.6.tgz#5321b9579f4a1949318550ced99c2a4a4e60cbaf" + integrity sha512-bLjW01UTrvoWTJQL5LsMRo1SypHW80FTm12OJRSnr3v6YHNhfe+1r0MYUZJMACxnCHURVnBWRwAsWs2yPU9Ezw== dependencies: "@types/html-minifier-terser" "^6.0.0" html-minifier-terser "^6.0.2" @@ -6733,7 +6994,7 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" -http-cache-semantics@^4.0.0: +http-cache-semantics@^4.0.0, http-cache-semantics@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#205f4db64f8562b76a4ff9235aa5279839a09dd5" integrity sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ== @@ -6743,27 +7004,17 @@ http-deceiver@^1.2.7: resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== +http-errors@~1.8.0: + version "1.8.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" + integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== dependencies: - depd "2.0.0" + depd "~1.1.2" inherits "2.0.4" setprototypeof "1.2.0" - statuses "2.0.1" + statuses ">= 1.5.0 < 2" toidentifier "1.0.1" -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - http-errors@~2.0.0, http-errors@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" @@ -6808,6 +7059,14 @@ http2-wrapper@^1.0.0-beta.5.2: quick-lru "^5.1.1" resolve-alpn "^1.0.0" +http2-wrapper@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.2.1.tgz#310968153dcdedb160d8b72114363ef5fce1f64a" + integrity sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.2.0" + https-proxy-agent@^7.0.5: version "7.0.6" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" @@ -6848,7 +7107,7 @@ iced-runtime@1.0.4, iced-runtime@^1.0.0: resolved "https://registry.yarnpkg.com/iced-runtime/-/iced-runtime-1.0.4.tgz#e9de26dfe98cd8621201f7f3dfb9f7f09c550990" integrity sha512-rgiJXNF6ZgF2Clh/TKUlBDW3q51YPDJUXmxGQXx1b8tbZpVpTn+1RX9q1sjNkujXIIaVxZByQzPHHORg7KV51g== -iconv-lite@0.4.24, iconv-lite@~0.4.24: +iconv-lite@~0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -6882,10 +7141,10 @@ image-size@^1.0.2: dependencies: queue "6.0.2" -immer@11.1.3: - version "11.1.3" - resolved "https://registry.yarnpkg.com/immer/-/immer-11.1.3.tgz#78681e1deb6cec39753acf04eb16d7576c04f4d6" - integrity sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q== +immer@11.1.4: + version "11.1.4" + resolved "https://registry.yarnpkg.com/immer/-/immer-11.1.4.tgz#37aee86890b134a8f1a2fadd44361fb86c6ae67e" + integrity sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw== import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.1" @@ -6921,11 +7180,6 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, i resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - ini@^1.3.4, ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" @@ -6965,9 +7219,9 @@ ipaddr.js@1.9.1: integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== ipaddr.js@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" - integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.3.0.tgz#71dce70e1398122208996d1c22f2ba46a24b1abc" + integrity sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg== is-arguments@^1.0.4: version "1.2.0" @@ -7187,6 +7441,11 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-stream@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-4.0.1.tgz#375cf891e16d2e4baec250b85926cffc14720d9b" + integrity sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A== + is-string@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" @@ -7296,7 +7555,7 @@ istanbul-lib-instrument@^5.0.4: istanbul-lib-coverage "^3.2.0" semver "^6.3.0" -iterator.prototype@^1.1.4: +iterator.prototype@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz#12c959a29de32de0aa3bbbb801f4d777066dae39" integrity sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g== @@ -7308,6 +7567,13 @@ iterator.prototype@^1.1.4: has-symbols "^1.1.0" set-function-name "^2.0.2" +jackspeak@^4.1.1: + version "4.2.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.2.3.tgz#27ef80f33b93412037c3bea4f8eddf80e1931483" + integrity sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg== + dependencies: + "@isaacs/cliui" "^9.0.0" + jest-environment-node@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" @@ -7445,7 +7711,7 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: +js-yaml@^4.1.0, js-yaml@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== @@ -7539,10 +7805,10 @@ jsonify@^0.0.1: object.assign "^4.1.4" object.values "^1.1.6" -junk@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" - integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== +junk@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/junk/-/junk-4.0.1.tgz#7ee31f876388c05177fe36529ee714b07b50fbed" + integrity sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ== keyv@^4.0.0, keyv@^4.5.4: version "4.5.4" @@ -7551,6 +7817,13 @@ keyv@^4.0.0, keyv@^4.5.4: dependencies: json-buffer "3.0.1" +keyv@^5.5.3, keyv@^5.5.5: + version "5.6.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-5.6.0.tgz#03044074c6b4d072d0a62c7b9fa649537baf0105" + integrity sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw== + dependencies: + "@keyv/serialize" "^1.1.1" + kind-of@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" @@ -7607,79 +7880,79 @@ lighthouse-logger@^1.0.0: debug "^2.6.9" marky "^1.2.2" -lightningcss-android-arm64@1.30.2: - version "1.30.2" - resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz#6966b7024d39c94994008b548b71ab360eb3a307" - integrity sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A== - -lightningcss-darwin-arm64@1.30.2: - version "1.30.2" - resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz#a5fa946d27c029e48c7ff929e6e724a7de46eb2c" - integrity sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA== - -lightningcss-darwin-x64@1.30.2: - version "1.30.2" - resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz#5ce87e9cd7c4f2dcc1b713f5e8ee185c88d9b7cd" - integrity sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ== - -lightningcss-freebsd-x64@1.30.2: - version "1.30.2" - resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz#6ae1d5e773c97961df5cff57b851807ef33692a5" - integrity sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA== - -lightningcss-linux-arm-gnueabihf@1.30.2: - version "1.30.2" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz#62c489610c0424151a6121fa99d77731536cdaeb" - integrity sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA== - -lightningcss-linux-arm64-gnu@1.30.2: - version "1.30.2" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz#2a3661b56fe95a0cafae90be026fe0590d089298" - integrity sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A== - -lightningcss-linux-arm64-musl@1.30.2: - version "1.30.2" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz#d7ddd6b26959245e026bc1ad9eb6aa983aa90e6b" - integrity sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA== - -lightningcss-linux-x64-gnu@1.30.2: - version "1.30.2" - resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz#5a89814c8e63213a5965c3d166dff83c36152b1a" - integrity sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w== - -lightningcss-linux-x64-musl@1.30.2: - version "1.30.2" - resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz#808c2e91ce0bf5d0af0e867c6152e5378c049728" - integrity sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA== - -lightningcss-win32-arm64-msvc@1.30.2: - version "1.30.2" - resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz#ab4a8a8a2e6a82a4531e8bbb6bf0ff161ee6625a" - integrity sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ== - -lightningcss-win32-x64-msvc@1.30.2: - version "1.30.2" - resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz#f01f382c8e0a27e1c018b0bee316d210eac43b6e" - integrity sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw== +lightningcss-android-arm64@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz#609ff48332adff452a8157a7c2842fd692a8eac4" + integrity sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg== + +lightningcss-darwin-arm64@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz#a13da040a7929582bab3ace9a67bdc146e99fc2d" + integrity sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg== + +lightningcss-darwin-x64@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz#f7482c311273571ec0c2bd8277c1f5f6e90e03a4" + integrity sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA== + +lightningcss-freebsd-x64@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz#91df1bb290f1cb7bb2af832d7d0d8809225e0124" + integrity sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A== + +lightningcss-linux-arm-gnueabihf@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz#c3cad5ae8b70045f21600dc95295ab6166acf57e" + integrity sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g== + +lightningcss-linux-arm64-gnu@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz#a5c4f6a5ac77447093f61b209c0bd7fef1f0a3e3" + integrity sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg== + +lightningcss-linux-arm64-musl@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz#af26ab8f829b727ada0a200938a6c8796ff36900" + integrity sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg== + +lightningcss-linux-x64-gnu@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz#a891d44e84b71c0d88959feb9a7522bbf61450ee" + integrity sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA== + +lightningcss-linux-x64-musl@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz#8c8b21def851f4d477fa897b80cb3db2b650bc6e" + integrity sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA== + +lightningcss-win32-arm64-msvc@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz#79000fb8c57e94a91b8fc643e74d5a54407d7080" + integrity sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w== + +lightningcss-win32-x64-msvc@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz#7f025274c81c7d659829731e09c8b6f442209837" + integrity sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw== lightningcss@^1.30.1: - version "1.30.2" - resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.30.2.tgz#4ade295f25d140f487d37256f4cd40dc607696d0" - integrity sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ== + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.31.1.tgz#1a19dd327b547a7eda1d5c296ebe1e72df5a184b" + integrity sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ== dependencies: detect-libc "^2.0.3" optionalDependencies: - lightningcss-android-arm64 "1.30.2" - lightningcss-darwin-arm64 "1.30.2" - lightningcss-darwin-x64 "1.30.2" - lightningcss-freebsd-x64 "1.30.2" - lightningcss-linux-arm-gnueabihf "1.30.2" - lightningcss-linux-arm64-gnu "1.30.2" - lightningcss-linux-arm64-musl "1.30.2" - lightningcss-linux-x64-gnu "1.30.2" - lightningcss-linux-x64-musl "1.30.2" - lightningcss-win32-arm64-msvc "1.30.2" - lightningcss-win32-x64-msvc "1.30.2" + lightningcss-android-arm64 "1.31.1" + lightningcss-darwin-arm64 "1.31.1" + lightningcss-darwin-x64 "1.31.1" + lightningcss-freebsd-x64 "1.31.1" + lightningcss-linux-arm-gnueabihf "1.31.1" + lightningcss-linux-arm64-gnu "1.31.1" + lightningcss-linux-arm64-musl "1.31.1" + lightningcss-linux-x64-gnu "1.31.1" + lightningcss-linux-x64-musl "1.31.1" + lightningcss-win32-arm64-msvc "1.31.1" + lightningcss-win32-x64-msvc "1.31.1" lines-and-columns@^1.1.6: version "1.2.4" @@ -7760,16 +8033,11 @@ lodash.throttle@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== -lodash@4.17.23: +lodash@4.17.23, lodash@^4, lodash@^4.17.20, lodash@^4.17.21: version "4.17.23" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== -lodash@^4, lodash@^4.17.20, lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - log-symbols@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" @@ -7801,10 +8069,10 @@ loose-envify@^1.0.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -lottie-react-native@7.3.5: - version "7.3.5" - resolved "https://registry.yarnpkg.com/lottie-react-native/-/lottie-react-native-7.3.5.tgz#515c703e8a8845c54dbd54d489467c0c1f21d8bd" - integrity sha512-5VPrHGbEmpNxrcEfmxyFZBvDksMaZ6LhyQZL0S0VIDwMRVrhGwOZQZKVFSEFU5HxNuDjxm/vPSoEhlKKfbYKHw== +lottie-react-native@7.3.6: + version "7.3.6" + resolved "https://registry.yarnpkg.com/lottie-react-native/-/lottie-react-native-7.3.6.tgz#b5ab46bff3ec9e48d5efa8d31166dd53758a1aa9" + integrity sha512-TevFHRvFURh6GlaqLKrSNXuKAxvBvFCiXfS7FXQI1K/ikOStgAwWLFPGjW0i1qB2/VzPACKmRs+535VjHUZZZQ== lottie-web@5.13.0: version "5.13.0" @@ -7823,15 +8091,20 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== +lowercase-keys@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" + integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== + lru-cache@^10.0.1, lru-cache@^10.2.0: version "10.4.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== lru-cache@^11.0.0: - version "11.2.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.2.tgz#40fd37edffcfae4b2940379c0722dc6eeaa75f24" - integrity sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg== + version "11.2.6" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.6.tgz#356bf8a29e88a7a2945507b31f6429a65a192c58" + integrity sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ== lru-cache@^5.1.1: version "5.1.1" @@ -7878,10 +8151,18 @@ media-typer@0.3.0: integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== memfs@^4.43.1: - version "4.51.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.51.0.tgz#f33b5eff5e2faa01bfacc02aacf23ec7d8c84c94" - integrity sha512-4zngfkVM/GpIhC8YazOsM6E8hoB33NP0BCESPOA6z7qaL6umPJNqkO8CNYaLV2FB2MV6H1O3x2luHHOSqppv+A== - dependencies: + version "4.56.10" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.56.10.tgz#eaf2f6556db10f91f1e9ad9f1274fd988c646202" + integrity sha512-eLvzyrwqLHnLYalJP7YZ3wBe79MXktMdfQbvMrVD80K+NhrIukCVBvgP30zTJYEEDh9hZ/ep9z0KOdD7FSHo7w== + dependencies: + "@jsonjoy.com/fs-core" "4.56.10" + "@jsonjoy.com/fs-fsa" "4.56.10" + "@jsonjoy.com/fs-node" "4.56.10" + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-to-fsa" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + "@jsonjoy.com/fs-print" "4.56.10" + "@jsonjoy.com/fs-snapshot" "4.56.10" "@jsonjoy.com/json-pack" "^1.11.0" "@jsonjoy.com/util" "^1.9.0" glob-to-regex.js "^1.0.1" @@ -8137,7 +8418,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== -mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34, mime-types@~2.1.35: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -8145,9 +8426,9 @@ mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: mime-db "1.52.0" mime-types@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce" - integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA== + version "3.0.2" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.2.tgz#39002d4182575d5af036ffa118100f2524b2e2ab" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== dependencies: mime-db "^1.54.0" @@ -8181,17 +8462,22 @@ mimic-response@^3.1.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== +mimic-response@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-4.0.0.tgz#35468b19e7c75d10f5165ea25e75a5ceea7cf70f" + integrity sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg== + minimalistic-assert@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@^10.1.1: - version "10.1.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.1.1.tgz#e6e61b9b0c1dcab116b5a7d1458e8b6ae9e73a55" - integrity sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ== +minimatch@^10.0.1, minimatch@^10.1.1, minimatch@^10.1.2: + version "10.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.1.2.tgz#6c3f289f9de66d628fa3feb1842804396a43d81c" + integrity sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw== dependencies: - "@isaacs/brace-expansion" "^5.0.0" + "@isaacs/brace-expansion" "^5.0.1" minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" @@ -8214,7 +8500,7 @@ minimatch@^9.0.0, minimatch@^9.0.3, minimatch@^9.0.4, minimatch@^9.0.5: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.8: +minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -8371,6 +8657,11 @@ normalize-url@^6.0.1: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== +normalize-url@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-8.1.1.tgz#751a20c8520e5725404c06015fea21d7567f25ef" + integrity sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ== + npm-package-arg@^11.0.0: version "11.0.3" resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-11.0.3.tgz#dae0c21199a99feca39ee4bfb074df3adac87e2d" @@ -8463,17 +8754,17 @@ object.fromentries@^2.0.8: es-object-atoms "^1.0.0" object.getownpropertydescriptors@^2.0.3: - version "2.1.8" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.8.tgz#2f1fe0606ec1a7658154ccd4f728504f69667923" - integrity sha512-qkHIGe4q0lSYMv0XI4SsBTJz3WaURhLvd0lKSgtVuOsJ2krg4SgMw3PIRQFMp07yi++UR3se2mkcLqsBNpBb/A== + version "2.1.9" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.9.tgz#bf9e7520f14d50de88dee2b9c9eca841166322dc" + integrity sha512-mt8YM6XwsTTovI+kdZdHSxoyF2DI59up034orlC9NfweclcWOt7CVascNNLp6U+bjFVCVCIh9PwS76tDM/rH8g== dependencies: - array.prototype.reduce "^1.0.6" - call-bind "^1.0.7" + array.prototype.reduce "^1.0.8" + call-bind "^1.0.8" define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - gopd "^1.0.1" - safe-array-concat "^1.1.2" + es-abstract "^1.24.0" + es-object-atoms "^1.1.1" + gopd "^1.2.0" + safe-array-concat "^1.1.3" object.values@^1.1.6, object.values@^1.2.1: version "1.2.1" @@ -8490,7 +8781,7 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -on-finished@2.4.1, on-finished@^2.4.1, on-finished@~2.4.1: +on-finished@^2.4.1, on-finished@~2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== @@ -8617,6 +8908,11 @@ p-cancelable@^2.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== +p-cancelable@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-4.0.1.tgz#2d1edf1ab8616b72c73db41c4bc9ecdd10af640e" + integrity sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg== + p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -8685,7 +8981,7 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -package-json-from-dist@^1.0.1: +package-json-from-dist@^1.0.0, package-json-from-dist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== @@ -8746,7 +9042,7 @@ parse-png@^2.1.0: dependencies: pngjs "^3.3.0" -parseurl@~1.3.2, parseurl@~1.3.3: +parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -8915,21 +9211,21 @@ pkijs@^3.3.3: pvutils "^1.1.3" tslib "^2.8.1" -playwright-core@1.56.1: - version "1.56.1" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.56.1.tgz#24a66481e5cd33a045632230aa2c4f0cb6b1db3d" - integrity sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ== +playwright-core@1.58.2: + version "1.58.2" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.58.2.tgz#ac5f5b4b10d29bcf934415f0b8d133b34b0dcb13" + integrity sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg== playwright@^1.49.0: - version "1.56.1" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.56.1.tgz#62e3b99ddebed0d475e5936a152c88e68be55fbf" - integrity sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw== + version "1.58.2" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.58.2.tgz#afe547164539b0bcfcb79957394a7a3fa8683cfd" + integrity sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A== dependencies: - playwright-core "1.56.1" + playwright-core "1.58.2" optionalDependencies: fsevents "2.3.2" -plist@^3.0.0, plist@^3.0.5, plist@^3.1.0: +plist@^3.0.5, plist@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/plist/-/plist-3.1.0.tgz#797a516a93e62f5bde55e0b9cc9c967f860893c9" integrity sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ== @@ -8977,9 +9273,9 @@ postcss-modules-values@^4.0.0: icss-utils "^5.0.0" postcss-selector-parser@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz#4d6af97eba65d73bc4d84bcb343e865d7dd16262" - integrity sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA== + version "7.1.1" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz#e75d2e0d843f620e5df69076166f4e16f891cb9f" + integrity sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" @@ -8989,7 +9285,7 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.4.33: +postcss@^8.4.40: version "8.5.6" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== @@ -9024,15 +9320,10 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier@3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.0.tgz#f72cf71505133f40cfa2ef77a2668cdc558fcd69" - integrity sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA== - -prettier@^3.4.2: - version "3.6.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.2.tgz#ccda02a1003ebbb2bfda6f83a074978f608b9393" - integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ== +prettier@3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173" + integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== pretty-bytes@^5.6.0: version "5.6.0" @@ -9147,27 +9438,15 @@ pvutils@^1.1.3: resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.1.5.tgz#84b0dea4a5d670249aa9800511804ee0b7c2809c" integrity sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA== -qrcode-generator@1.4.4: - version "1.4.4" - resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-1.4.4.tgz#63f771224854759329a99048806a53ed278740e7" - integrity sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw== - qrcode-terminal@0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz#ffc6c28a2fc0bfb47052b47e23f4f446a5fbdb9e" integrity sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ== -qs@6.13.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" - integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== - dependencies: - side-channel "^1.0.6" - qs@~6.14.0: - version "6.14.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.1.tgz#a41d85b9d3902f31d27861790506294881871159" - integrity sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ== + version "6.14.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.2.tgz#b5634cf9d9ad9898e31fba3504e866e8efb6798c" + integrity sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q== dependencies: side-channel "^1.1.0" @@ -9215,16 +9494,6 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - raw-body@~2.5.3: version "2.5.3" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2" @@ -9288,9 +9557,9 @@ react-is@^18.0.0: integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== react-is@^19.1.0: - version "19.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.2.0.tgz#ddc3b4a4e0f3336c3847f18b806506388d7b9973" - integrity sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA== + version "19.2.4" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.2.4.tgz#a080758243c572ccd4a63386537654298c99d135" + integrity sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA== react-list@0.8.18: version "0.8.18" @@ -9314,10 +9583,10 @@ react-native-is-edge-to-edge@1.2.1, react-native-is-edge-to-edge@^1.2.1: "react-native-kb@file:../rnmodules/react-native-kb": version "0.1.1" -react-native-keyboard-controller@1.20.6: - version "1.20.6" - resolved "https://registry.yarnpkg.com/react-native-keyboard-controller/-/react-native-keyboard-controller-1.20.6.tgz#139a66f36734a69648822bf2e7ec3c90e7b3dc8e" - integrity sha512-RS6FjIjTFtAMQGdcXp3m6jUs1XgDa8qkpO5c4ix1S5HS0z3L2E1LUOY5rD73YUADOO3MfQN1z3JkHdBtzKucbg== +react-native-keyboard-controller@1.20.7: + version "1.20.7" + resolved "https://registry.yarnpkg.com/react-native-keyboard-controller/-/react-native-keyboard-controller-1.20.7.tgz#e1be1c15a5eb10b96a40a0812d8472e6e4bd8f29" + integrity sha512-G8S5jz1FufPrcL1vPtReATx+jJhT/j+sTqxMIb30b1z7cYEfMlkIzOCyaHgf6IMB2KA9uBmnA5M6ve2A9Ou4kw== dependencies: react-native-is-edge-to-edge "^1.2.1" @@ -9334,10 +9603,10 @@ react-native-safe-area-context@5.6.2: resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz#283e006f5b434fb247fcb4be0971ad7473d5c560" integrity sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg== -react-native-screens@4.18.0: - version "4.18.0" - resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.18.0.tgz#ba5a951b3f145e3b773d201143c19e1b1c1337ff" - integrity sha512-mRTLWL7Uc1p/RFNveEIIrhP22oxHduC2ZnLr/2iHwBeYpGXR0rJZ7Bgc0ktxQSHRjWTPT70qc/7yd4r9960PBQ== +react-native-screens@4.23.0: + version "4.23.0" + resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.23.0.tgz#81574b1b0cc4ac6c9ed63e46eca7126f37affe86" + integrity sha512-XhO3aK0UeLpBn4kLecd+J+EDeRRJlI/Ro9Fze06vo1q163VeYtzfU9QS09/VyDFMWR1qxDC1iazCArTPSFFiPw== dependencies: react-freeze "^1.0.0" warn-once "^0.1.0" @@ -9364,10 +9633,10 @@ react-native-webview@13.16.0: escape-string-regexp "^4.0.0" invariant "2.2.4" -react-native-worklets@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/react-native-worklets/-/react-native-worklets-0.7.1.tgz#263da5216b0b5342b9f1b36e0ab897c5ca5c863b" - integrity sha512-KNsvR48ULg73QhTlmwPbdJLPsWcyBotrGPsrDRDswb5FYpQaJEThUKc2ncXE4UM5dn/ewLoQHjSjLaKUVPxPhA== +react-native-worklets@0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/react-native-worklets/-/react-native-worklets-0.7.3.tgz#e561c006b576019feb21888c4acf60261b55ca24" + integrity sha512-m/CIUCHvLQulboBn0BtgpsesXjOTeubU7t+V0lCPpBj0t2ExigwqDHoKj3ck7OeErnjgkD27wdAtQCubYATe3g== dependencies: "@babel/plugin-transform-arrow-functions" "7.27.1" "@babel/plugin-transform-class-properties" "7.27.1" @@ -9469,10 +9738,10 @@ react-test-renderer@19.1.0: react-is "^19.1.0" scheduler "^0.26.0" -react-window@2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/react-window/-/react-window-2.2.5.tgz#425a29609980083aafd5a48a1711a2af9319c1d2" - integrity sha512-6viWvPSZvVuMIe9hrl4IIZoVfO/npiqOb03m4Z9w+VihmVzBbiudUrtUqDpsWdKvd/Ai31TCR25CBcFFAUm28w== +react-window@2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/react-window/-/react-window-2.2.6.tgz#00ca174346b5146d3c33a752d888181250c71d9f" + integrity sha512-v89O08xRdpCaEuf380B39D1C/0KgUDZA59xft6SVAjzjz/xQxSyXrgDWHymIsYI6TMrqE8WO+G0/PB9AGE8VNA== react@19.1.0: version "19.1.0" @@ -9654,7 +9923,7 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== -resedit@^2.0.0: +resedit@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/resedit/-/resedit-2.0.3.tgz#5145a9faabca44b917d5636dbe8e67ec7f62c6f2" integrity sha512-oTeemxwoMuxxTYxXUwjkrOPfngTQehlv0/HoYFNkB4uzsP1Un1A9nI8JQKGOFkxpqkC7qkMs0lUsGrvUlbLNUA== @@ -9666,7 +9935,7 @@ reselect@^4.1.7: resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524" integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== -resolve-alpn@^1.0.0: +resolve-alpn@^1.0.0, resolve-alpn@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== @@ -9701,16 +9970,16 @@ resolve-pkg-maps@^1.0.0: integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== resolve-workspace-root@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-workspace-root/-/resolve-workspace-root-2.0.0.tgz#a0098daa0067cd0efa6eb525c57c8fb4a61e78f8" - integrity sha512-IsaBUZETJD5WsI11Wt8PKHwaIe45or6pwNc8yflvLJ4DWtImK9kuLoH5kUva/2Mmx/RdIyr4aONNSa2v9LTJsw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/resolve-workspace-root/-/resolve-workspace-root-2.0.1.tgz#9cbbf8321ebccaaf0e4ffea5274aa26b611ccd62" + integrity sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w== resolve.exports@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.20.0, resolve@^1.22.10, resolve@^1.22.2, resolve@^1.22.8: +resolve@^1.10.0, resolve@^1.20.0, resolve@^1.22.10, resolve@^1.22.11, resolve@^1.22.2, resolve@^1.22.8: version "1.22.11" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== @@ -9742,6 +10011,13 @@ responselike@^2.0.0: dependencies: lowercase-keys "^2.0.0" +responselike@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-4.0.2.tgz#d99a1105aeca5909c1e93156a839c7f3173e26c2" + integrity sha512-cGk8IbWEAnaCpdAt1BHzJ3Ahz5ewDJa0KseTsE3qIRMJ3C698W8psM7byCeWVpd/Ha7FUYzuRVzXoKoM6nRUbA== + dependencies: + lowercase-keys "^3.0.0" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -9812,7 +10088,7 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-array-concat@^1.1.2, safe-array-concat@^1.1.3: +safe-array-concat@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== @@ -9856,9 +10132,9 @@ safe-regex-test@^1.1.0: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sax@>=0.6.0: - version "1.4.3" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.3.tgz#fcebae3b756cdc8428321805f4b70f16ec0ab5db" - integrity sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ== + version "1.4.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.4.tgz#f29c2bba80ce5b86f4343b4c2be9f2b96627cf8b" + integrity sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw== scheduler@0.26.0, scheduler@^0.26.0: version "0.26.0" @@ -9907,7 +10183,7 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@7.7.3, semver@^7.1.3, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.7.3: +semver@7.7.3: version "7.7.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== @@ -9917,45 +10193,12 @@ semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -send@0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" - integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -send@^0.19.0: - version "0.19.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.1.tgz#1c2563b2ee4fe510b806b21ec46f355005a369f9" - integrity sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~2.0.0" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" +semver@^7.1.3, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3, semver@^7.7.1, semver@^7.7.2, semver@^7.7.3: + version "7.7.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== -send@~0.19.0, send@~0.19.1: +send@^0.19.0, send@~0.19.0, send@~0.19.1: version "0.19.2" resolved "https://registry.yarnpkg.com/send/-/send-0.19.2.tgz#59bc0da1b4ea7ad42736fd642b1c4294e114ff29" integrity sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg== @@ -9994,29 +10237,19 @@ serialize-javascript@^6.0.2: randombytes "^2.1.0" serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== + version "1.9.2" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.2.tgz#2988e3612106d78a5e4849ddff552ce7bd3d9bcb" + integrity sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ== dependencies: - accepts "~1.3.4" + accepts "~1.3.8" batch "0.6.1" debug "2.6.9" escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" - -serve-static@^1.13.1, serve-static@^1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" - integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== - dependencies: - encodeurl "~2.0.0" - escape-html "~1.0.3" + http-errors "~1.8.0" + mime-types "~2.1.35" parseurl "~1.3.3" - send "0.19.0" -serve-static@~1.16.2: +serve-static@^1.13.1, serve-static@^1.16.2, serve-static@~1.16.2: version "1.16.3" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.3.tgz#a97b74d955778583f3862a4f0b841eb4d5d78cf9" integrity sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA== @@ -10067,20 +10300,15 @@ setimmediate@^1.0.5: resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - setprototypeof@1.2.0, setprototypeof@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== sf-symbols-typescript@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/sf-symbols-typescript/-/sf-symbols-typescript-2.1.0.tgz#50a2d7b36edd6809606f0b0a36322fc1fdd7cfde" - integrity sha512-ezT7gu/SHTPIOEEoG6TF+O0m5eewl0ZDAO4AtdBi5HjsrUI6JdCG17+Q8+aKp0heM06wZKApRCn5olNbs0Wb/A== + version "2.2.0" + resolved "https://registry.yarnpkg.com/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz#926d6e0715e3d8784cadf7658431e36581254208" + integrity sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw== shallow-clone@^3.0.0: version "3.0.1" @@ -10140,7 +10368,7 @@ side-channel-weakmap@^1.0.2: object-inspect "^1.13.3" side-channel-map "^1.0.1" -side-channel@^1.0.6, side-channel@^1.1.0: +side-channel@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== @@ -10156,6 +10384,11 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-plist@^1.1.0: version "1.3.1" resolved "https://registry.yarnpkg.com/simple-plist/-/simple-plist-1.3.1.tgz#16e1d8f62c6c9b691b8383127663d834112fb017" @@ -10321,12 +10554,7 @@ stacktrace-parser@^0.1.10: dependencies: type-fest "^0.7.1" -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -"statuses@>= 1.4.0 < 2", statuses@~1.5.0: +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== @@ -10480,13 +10708,6 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== -strip-outer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" - integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg== - dependencies: - escape-string-regexp "^1.0.2" - strnum@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.1.2.tgz#57bca4fbaa6f271081715dbc9ed7cee5493e28e4" @@ -10561,7 +10782,7 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -tapable@^2.0.0, tapable@^2.2.0, tapable@^2.3.0: +tapable@^2.0.0, tapable@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== @@ -10602,9 +10823,9 @@ terser-webpack-plugin@5.3.16, terser-webpack-plugin@^5.3.16: terser "^5.31.1" terser@^5.10.0, terser@^5.15.0, terser@^5.31.1: - version "5.44.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.44.1.tgz#e391e92175c299b8c284ad6ded609e37303b0a9c" - integrity sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw== + version "5.46.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.46.0.tgz#1b81e560d584bbdd74a8ede87b4d9477b0ff9695" + integrity sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.15.0" @@ -10689,13 +10910,6 @@ tree-dump@^1.0.3, tree-dump@^1.1.0: resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.1.0.tgz#ab29129169dc46004414f5a9d4a3c6e89f13e8a4" integrity sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA== -trim-repeated@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" - integrity sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg== - dependencies: - escape-string-regexp "^1.0.2" - ts-api-utils@^1.3.0: version "1.4.3" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" @@ -10729,11 +10943,11 @@ tsutils@^3.21.0: tslib "^1.8.1" tsx@^4.19.3: - version "4.20.6" - resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.20.6.tgz#8fb803fd9c1f70e8ccc93b5d7c5e03c3979ccb2e" - integrity sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg== + version "4.21.0" + resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.21.0.tgz#32aa6cf17481e336f756195e6fe04dae3e6308b1" + integrity sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw== dependencies: - esbuild "~0.25.0" + esbuild "~0.27.0" get-tsconfig "^4.7.5" optionalDependencies: fsevents "~2.3.3" @@ -10772,6 +10986,11 @@ type-fest@^0.7.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== +type-fest@^4.26.1: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -10830,15 +11049,15 @@ typedarray-to-buffer@4.0.0: resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-4.0.0.tgz#cdd2933c61dd3f5f02eda5d012d441f95bfeb50a" integrity sha512-6dOYeZfS3O9RtRD1caom0sMxgK59b27+IwoNy8RDPsmslSGOyU+mpTamlaIW7aNKi90ZQZ9DFaZL3YRoiSCULQ== -typescript-eslint@8.53.0: - version "8.53.0" - resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.53.0.tgz#c35ca6403cd381753aee325f67e10d6101d55f04" - integrity sha512-xHURCQNxZ1dsWn0sdOaOfCSQG0HKeqSj9OexIxrz6ypU6wHYOdX2I3D2b8s8wFSsSOYJb+6q283cLiLlkEsBYw== +typescript-eslint@8.55.0: + version "8.55.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.55.0.tgz#abae8295c5f0f82f816218113a46e89bc30c3de2" + integrity sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw== dependencies: - "@typescript-eslint/eslint-plugin" "8.53.0" - "@typescript-eslint/parser" "8.53.0" - "@typescript-eslint/typescript-estree" "8.53.0" - "@typescript-eslint/utils" "8.53.0" + "@typescript-eslint/eslint-plugin" "8.55.0" + "@typescript-eslint/parser" "8.55.0" + "@typescript-eslint/typescript-estree" "8.55.0" + "@typescript-eslint/utils" "8.55.0" typescript@5.9.3: version "5.9.3" @@ -10925,7 +11144,7 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== -unpipe@1.0.0, unpipe@~1.0.0: +unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== @@ -10938,18 +11157,10 @@ unplugin@2.1.0: acorn "^8.14.0" webpack-virtual-modules "^0.6.2" -update-browserslist-db@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz#7802aa2ae91477f255b86e0e46dbc787a206ad4a" - integrity sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - update-browserslist-db@^1.2.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz#cfb4358afa08b3d5731a2ecd95eebf4ddef8033e" - integrity sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA== + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== dependencies: escalade "^3.2.0" picocolors "^1.1.1" @@ -11062,10 +11273,10 @@ warn-once@^0.1.0, warn-once@^0.1.1: resolved "https://registry.yarnpkg.com/warn-once/-/warn-once-0.1.1.tgz#952088f4fb56896e73fd4e6a3767272a3fccce43" integrity sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q== -watchpack@^2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.4.tgz#473bda72f0850453da6425081ea46fc0d7602947" - integrity sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA== +watchpack@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.5.1.tgz#dd38b601f669e0cbf567cb802e75cead82cde102" + integrity sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -11178,10 +11389,10 @@ webpack-virtual-modules@^0.6.2: resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8" integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ== -webpack@5.104.1: - version "5.104.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.104.1.tgz#94bd41eb5dbf06e93be165ba8be41b8260d4fb1a" - integrity sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA== +webpack@5.105.2: + version "5.105.2" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.105.2.tgz#f3b76f9fc36f1152e156e63ffda3bbb82e6739ea" + integrity sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw== dependencies: "@types/eslint-scope" "^3.7.7" "@types/estree" "^1.0.8" @@ -11193,7 +11404,7 @@ webpack@5.104.1: acorn-import-phases "^1.0.3" browserslist "^4.28.1" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.17.4" + enhanced-resolve "^5.19.0" es-module-lexer "^2.0.0" eslint-scope "5.1.1" events "^3.2.0" @@ -11206,7 +11417,7 @@ webpack@5.104.1: schema-utils "^4.3.3" tapable "^2.3.0" terser-webpack-plugin "^5.3.16" - watchpack "^2.4.4" + watchpack "^2.5.1" webpack-sources "^3.3.3" websocket-driver@>=0.5.1, websocket-driver@^0.7.4: @@ -11291,9 +11502,9 @@ which-module@^2.0.0: integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== which-typed-array@^1.1.16, which-typed-array@^1.1.19, which-typed-array@^1.1.2: - version "1.1.19" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" - integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== + version "1.1.20" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.20.tgz#3fdb7adfafe0ea69157b1509f3a1cd892bd1d122" + integrity sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg== dependencies: available-typed-arrays "^1.0.7" call-bind "^1.0.8" @@ -11369,9 +11580,9 @@ ws@^7, ws@^7.5.10: integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== ws@^8.12.1, ws@^8.18.0: - version "8.18.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" - integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== + version "8.19.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.19.0.tgz#ddc2bdfa5b9ad860204f5a72a4863a8895fd8c8b" + integrity sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg== wsl-utils@^0.1.0: version "0.1.0" @@ -11427,9 +11638,9 @@ yallist@^5.0.0: integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== yaml@^2.2.1, yaml@^2.2.2, yaml@^2.6.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.1.tgz#1870aa02b631f7e8328b93f8bc574fac5d6c4d79" - integrity sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw== + version "2.8.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.2.tgz#5694f25eca0ce9c3e7a9d9e00ce0ddabbd9e35c5" + integrity sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A== yargs-parser@^18.1.2: version "18.1.3" @@ -11444,6 +11655,11 @@ yargs-parser@^21.1.1: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== +yargs-parser@^22.0.0: + version "22.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-22.0.0.tgz#87b82094051b0567717346ecd00fd14804b357c8" + integrity sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw== + yargs@^15.1.0: version "15.4.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" @@ -11503,11 +11719,11 @@ zod@^3.22.4: integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== "zod@^3.25.0 || ^4.0.0": - version "4.1.12" - resolved "https://registry.yarnpkg.com/zod/-/zod-4.1.12.tgz#64f1ea53d00eab91853195653b5af9eee68970f0" - integrity sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ== - -zustand@5.0.10: - version "5.0.10" - resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.10.tgz#4db510c0c4c25a5f1ae43227b307ddf1641a3210" - integrity sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg== + version "4.3.6" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.3.6.tgz#89c56e0aa7d2b05107d894412227087885ab112a" + integrity sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg== + +zustand@5.0.11: + version "5.0.11" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.11.tgz#99f912e590de1ca9ce6c6d1cab6cdb1f034ab494" + integrity sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==