diff --git a/components/debug-menu.tsx b/components/debug-menu.tsx index 0f2b02ac..d6f06c88 100644 --- a/components/debug-menu.tsx +++ b/components/debug-menu.tsx @@ -21,7 +21,11 @@ import { } from "@/features/notifications/notifications.service" import { getXmtpConversationIdFromXmtpTopic } from "@/features/xmtp/xmtp-conversations/xmtp-conversation" import { getXmtpConversations } from "@/features/xmtp/xmtp-conversations/xmtp-conversations-list" -import { syncAllXmtpConversations } from "@/features/xmtp/xmtp-conversations/xmtp-conversations-sync" +import { + syncAllXmtpConversations, + syncOneXmtpConversation, +} from "@/features/xmtp/xmtp-conversations/xmtp-conversations-sync" +import { getXmtpDisappearingMessageSettings } from "@/features/xmtp/xmtp-disappearing-messages/xmtp-disappearing-messages" import { clearXmtpLogFiles, clearXmtpLogs, @@ -29,11 +33,13 @@ import { startXmtpFileLogging, stopXmtpFileLogging, } from "@/features/xmtp/xmtp-logs" -import type { IXmtpInboxId } from "@/features/xmtp/xmtp.types" +import { getXmtpConversationMessages } from "@/features/xmtp/xmtp-messages/xmtp-messages" +import type { IXmtpDecodedMessage, IXmtpInboxId } from "@/features/xmtp/xmtp.types" import { translate } from "@/i18n" -import { navigate } from "@/navigation/navigation.utils" +import { getCurrentRouteParams, navigate } from "@/navigation/navigation.utils" import { useAppStore } from "@/stores/app.store" import { captureError } from "@/utils/capture-error" +import { convertNanosecondsToMilliseconds } from "@/utils/date" import { GenericError } from "@/utils/error" import { getEnv, isProd } from "@/utils/getEnv" import { Haptics } from "@/utils/haptics" @@ -41,6 +47,7 @@ import { clearImageCache } from "@/utils/image" import { clearLogFile, LOG_FILE_PATH } from "@/utils/logger/logger" import { clearReacyQueryQueriesAndCache } from "@/utils/react-query/react-query.utils" import { shareContent } from "@/utils/share" +import { getHumanReadableTimeFromMs } from "@/utils/time.utils" import { showActionSheet } from "./action-sheet" export const DebugMenuWrapper = memo(function DebugWrapper(props: { children: React.ReactNode }) { @@ -459,6 +466,109 @@ function useShowDebugMenu() { const clientInboxId = currentSenderInboxId as IXmtpInboxId const xmtpMethods = { + "Get last 10 XMTP messages": async () => { + try { + useAppStore.getState().actions.setFullScreenLoaderOptions({ + isVisible: true, + texts: ["Loading messages..."], + }) + + const params = getCurrentRouteParams<"Conversation">() + const conversationId = params?.xmtpConversationId + + if (!conversationId) { + Alert.alert("Error", "Select this debug option in a conversation") + return + } + + await syncOneXmtpConversation({ + clientInboxId, + conversationId, + caller: "debugMenu", + }) + + const messages = await getXmtpConversationMessages({ + clientInboxId, + xmtpConversationId: conversationId, + limit: 10, + }) + + const messageContents = messages + .map((msg: IXmtpDecodedMessage) => JSON.stringify(msg.nativeContent)) + .join("\n\n") + Alert.alert("Last few Messages", messageContents) + } catch (error) { + captureError( + new GenericError({ + error, + additionalMessage: "Error getting messages", + }), + ) + Alert.alert("Error", "Failed to get messages") + } finally { + useAppStore.getState().actions.setFullScreenLoaderOptions({ + isVisible: false, + }) + } + }, + "Get disappearing message settings": async () => { + try { + useAppStore.getState().actions.setFullScreenLoaderOptions({ + isVisible: true, + texts: ["Loading disappearing message settings..."], + }) + + const params = getCurrentRouteParams<"Conversation">() + + const conversationId = params?.xmtpConversationId + + if (!conversationId) { + Alert.alert("Error", "Select this debug option in a conversation") + return + } + + await syncOneXmtpConversation({ + clientInboxId, + conversationId, + caller: "debugMenu", + }) + + const settings = await getXmtpDisappearingMessageSettings({ + clientInboxId, + conversationId, + }) + + if (!settings) { + Alert.alert("Error", "No disappearing message settings found for this conversation") + return + } + + const formattedSettings = { + disappearStartingAt: new Date( + convertNanosecondsToMilliseconds(settings.disappearStartingAtNs), + ).toLocaleString(), + retentionDuration: getHumanReadableTimeFromMs( + convertNanosecondsToMilliseconds(settings.retentionDurationInNs), + ), + } + Alert.alert( + "Disappearing Message Settings", + `Messages will start disappearing at:\n${formattedSettings.disappearStartingAt}\n\nMessages will be retained for:\n${formattedSettings.retentionDuration}`, + ) + } catch (error) { + captureError( + new GenericError({ + error, + additionalMessage: "Error getting disappearing message settings", + }), + ) + Alert.alert("Error", "Failed to get disappearing message settings") + } finally { + useAppStore.getState().actions.setFullScreenLoaderOptions({ + isVisible: false, + }) + } + }, "List Allowed XMTP Conversations": async () => { try { useAppStore.getState().actions.setFullScreenLoaderOptions({ diff --git a/components/prompt-async.ts b/components/prompt-async.ts new file mode 100644 index 00000000..329b34db --- /dev/null +++ b/components/prompt-async.ts @@ -0,0 +1,51 @@ +import { Alert, AlertOptions } from "react-native" + +/** + * Creates an Alert with a text input that returns a Promise which resolves with + * the input value when submitted, or undefined if canceled. + */ + +export function promptAsync(args: { + title: string + message?: string + type?: "plain-text" | "secure-text" + defaultValue?: string + submitText?: string + cancelText?: string + keyboardType?: string + options?: AlertOptions +}) { + const { + title, + message, + type = "plain-text", + defaultValue = "", + submitText = "OK", + cancelText = "Cancel", + keyboardType, + options, + } = args + + return new Promise<{ value: string | undefined }>((resolve) => { + Alert.prompt( + title, + message, + [ + { + text: cancelText, + style: "cancel", + onPress: () => resolve({ value: undefined }), + }, + { + text: submitText, + style: "default", + onPress: (value?: string) => resolve({ value }), + }, + ], + type, + defaultValue, + keyboardType, + options, + ) + }) +} diff --git a/features/blocked-conversations/blocked-conversations.screen.tsx b/features/archived-conversations/archived-conversations.screen.tsx similarity index 90% rename from features/blocked-conversations/blocked-conversations.screen.tsx rename to features/archived-conversations/archived-conversations.screen.tsx index f86543e4..f81c2750 100644 --- a/features/blocked-conversations/blocked-conversations.screen.tsx +++ b/features/archived-conversations/archived-conversations.screen.tsx @@ -2,13 +2,13 @@ import { translate } from "@i18n/index" import React from "react" import { Screen } from "@/components/screen/screen" import { EmptyState } from "@/design-system/empty-state" -import { useBlockedConversationsForCurrentAccount } from "@/features/blocked-conversations/use-blocked-conversations-for-current-account" +import { useBlockedConversationsForCurrentAccount } from "@/features/archived-conversations/use-archived-conversations-for-current-sender" import { ConversationList } from "@/features/conversation/conversation-list/conversation-list.component" import { useHeader } from "@/navigation/use-header" import { useRouter } from "@/navigation/use-navigation" import { $globalStyles } from "@/theme/styles" -export function BlockedConversationsScreen() { +export function ArchivedConversationsScreen() { const { data: blockedConversationsIds = [] } = useBlockedConversationsForCurrentAccount() const router = useRouter() diff --git a/features/archived-conversations/denied-conversations.query.ts b/features/archived-conversations/denied-conversations.query.ts new file mode 100644 index 00000000..e24adf5e --- /dev/null +++ b/features/archived-conversations/denied-conversations.query.ts @@ -0,0 +1,146 @@ +import { IXmtpConversationId, IXmtpInboxId } from "@features/xmtp/xmtp.types" +import { queryOptions, skipToken } from "@tanstack/react-query" +import { setConversationQueryData } from "@/features/conversation/queries/conversation.query" +import { convertXmtpConversationToConvosConversation } from "@/features/conversation/utils/convert-xmtp-conversation-to-convos-conversation" +import { getXmtpConversations } from "@/features/xmtp/xmtp-conversations/xmtp-conversations-list" +import { syncAllXmtpConversations } from "@/features/xmtp/xmtp-conversations/xmtp-conversations-sync" +import { reactQueryClient } from "@/utils/react-query/react-query.client" + +export type IDeniedConversationsQueryData = Awaited< + ReturnType +> + +async function getDeniedConversationsQueryFn(args: { inboxId: IXmtpInboxId }) { + const { inboxId } = args + + if (!inboxId) { + throw new Error("InboxId is required") + } + + await syncAllXmtpConversations({ + clientInboxId: inboxId, + caller: "getDeniedConversationsQueryFn", + consentStates: ["denied"], + }) + + const deniedConsentXmtpConversations = await getXmtpConversations({ + clientInboxId: inboxId, + consentStates: ["denied"], + caller: "getDeniedConversationsQueryFn", + }) + + const convosConversations = await Promise.all( + deniedConsentXmtpConversations.map(convertXmtpConversationToConvosConversation), + ) + + for (const conversation of convosConversations) { + setConversationQueryData({ + clientInboxId: inboxId, + xmtpConversationId: conversation.xmtpId, + conversation, + }) + } + + return convosConversations.map((c) => c.xmtpId) +} + +export const getDeniedConsentConversationsQueryData = (args: { inboxId: IXmtpInboxId }) => { + return reactQueryClient.getQueryData(getDeniedConsentConversationsQueryOptions(args).queryKey) +} + +export function addConversationToDeniedConsentConversationsQuery(args: { + clientInboxId: IXmtpInboxId + conversationId: IXmtpConversationId +}) { + const { clientInboxId, conversationId } = args + + return reactQueryClient.setQueryData( + getDeniedConsentConversationsQueryOptions({ + inboxId: clientInboxId, + caller: "addConversationToDeniedConsentConversationsQuery", + }).queryKey, + (previousConversationIds) => { + if (!previousConversationIds) { + return [conversationId] + } + + const conversationExists = previousConversationIds.includes(conversationId) + + if (conversationExists) { + return previousConversationIds + } + + return [conversationId, ...previousConversationIds] + }, + ) +} + +export function removeConversationFromDeniedConsentConversationsQuery(args: { + clientInboxId: IXmtpInboxId + conversationId: IXmtpConversationId +}) { + const { clientInboxId, conversationId } = args + + return reactQueryClient.setQueryData( + getDeniedConsentConversationsQueryOptions({ + inboxId: clientInboxId, + caller: "removeConversationFromDeniedConsentConversationsQuery", + }).queryKey, + (previousConversationIds) => { + if (!previousConversationIds) { + return [] + } + + return previousConversationIds.filter((id) => id !== conversationId) + }, + ) +} + +export function getDeniedConsentConversationsQueryOptions(args: { + inboxId: IXmtpInboxId + caller?: string +}) { + const { inboxId, caller } = args + + const enabled = !!inboxId + + return queryOptions({ + enabled, + meta: { + caller, + }, + queryKey: ["denied-consent-conversations", inboxId], + queryFn: enabled + ? async () => + getDeniedConversationsQueryFn({ + inboxId, + }) + : skipToken, + }) +} + +export function invalidateDeniedConsentConversationsQuery(args: { + inboxId: IXmtpInboxId + caller: string +}) { + const { inboxId, caller } = args + return reactQueryClient.invalidateQueries({ + queryKey: getDeniedConsentConversationsQueryOptions({ + inboxId, + caller, + }).queryKey, + }) +} + +export function refetchDeniedConsentConversationsQuery(args: { + inboxId: IXmtpInboxId + caller: string +}) { + const { inboxId, caller } = args + return reactQueryClient.refetchQueries({ + queryKey: getDeniedConsentConversationsQueryOptions({ + inboxId, + caller, + }).queryKey, + }) +} diff --git a/features/blocked-conversations/use-blocked-conversations-for-current-account.ts b/features/archived-conversations/use-archived-conversations-for-current-sender.ts similarity index 82% rename from features/blocked-conversations/use-blocked-conversations-for-current-account.ts rename to features/archived-conversations/use-archived-conversations-for-current-sender.ts index 6f17cd24..f70d43a2 100644 --- a/features/blocked-conversations/use-blocked-conversations-for-current-account.ts +++ b/features/archived-conversations/use-archived-conversations-for-current-sender.ts @@ -8,14 +8,14 @@ import { isConversationDenied } from "@/features/conversation/utils/is-conversat export const useBlockedConversationsForCurrentAccount = () => { const currentSender = useSafeCurrentSender() - const { data: conversationIds } = useAllowedConsentConversationsQuery({ + const { data: allowedConversationIds } = useAllowedConsentConversationsQuery({ clientInboxId: currentSender.inboxId, caller: "useBlockedConversationsForCurrentAccount", }) // Create an array of metadata query configs and conversation query configs const metadataQueries = useQueries({ - queries: (conversationIds ?? []).map((conversationId) => ({ + queries: (allowedConversationIds ?? []).map((conversationId) => ({ ...getConversationMetadataQueryOptions({ clientInboxId: currentSender.inboxId, xmtpConversationId: conversationId, @@ -25,7 +25,7 @@ export const useBlockedConversationsForCurrentAccount = () => { }) const conversationQueries = useQueries({ - queries: (conversationIds ?? []).map((conversationId) => ({ + queries: (allowedConversationIds ?? []).map((conversationId) => ({ ...getConversationQueryOptions({ clientInboxId: currentSender.inboxId, xmtpConversationId: conversationId, @@ -35,10 +35,12 @@ export const useBlockedConversationsForCurrentAccount = () => { }) // Find blocked conversations by comparing both query results - const blockedConversationIds = (conversationIds ?? []).filter((conversationId, index) => { + const blockedConversationIds = (allowedConversationIds ?? []).filter((conversationId, index) => { const metadataQuery = metadataQueries[index] const conversationQuery = conversationQueries[index] + console.log("conversationQuery:", conversationQuery) + return ( metadataQuery.data?.deleted || (conversationQuery.data && isConversationDenied(conversationQuery.data)) diff --git a/features/consent/consent-for-inbox-id.query.ts b/features/consent/consent-for-inbox-id.query.ts new file mode 100644 index 00000000..7c687e2f --- /dev/null +++ b/features/consent/consent-for-inbox-id.query.ts @@ -0,0 +1,73 @@ +import { IXmtpInboxId } from "@features/xmtp/xmtp.types" +import { queryOptions, skipToken, useQuery } from "@tanstack/react-query" +import { IConsentState } from "@/features/consent/consent.types" +import { convertXmtpConsentStateToConsentState } from "@/features/consent/consent.utils" +import { getXmtpConsentStateForInboxId } from "@/features/xmtp/xmtp-consent/xmtp-consent" +import { reactQueryClient } from "@/utils/react-query/react-query.client" +import { reactQueryLongCacheQueryOptions } from "@/utils/react-query/react-query.constants" +import { getReactQueryKey } from "@/utils/react-query/react-query.utils" + +type IArgs = { + clientInboxId: IXmtpInboxId + inboxIdToCheck: IXmtpInboxId | undefined +} + +type IStrictArgs = { + clientInboxId: IXmtpInboxId + inboxIdToCheck: IXmtpInboxId +} + +export function getConsentForInboxIdQueryOptions(args: IArgs & { caller?: string }) { + const { clientInboxId, inboxIdToCheck, caller } = args + const enabled = !!clientInboxId && !!inboxIdToCheck + + return queryOptions({ + queryKey: getReactQueryKey({ + baseStr: "consent-for-inbox-id", + clientInboxId, + inboxIdToCheck, + }), + meta: { + caller, + }, + enabled, + queryFn: enabled + ? async () => { + // eslint-disable-next-line custom-plugin/require-promise-error-handling + const xmtpConsent = await getXmtpConsentStateForInboxId({ + clientInboxId, + inboxIdToCheck, + }) + + const convosConsent = convertXmtpConsentStateToConsentState(xmtpConsent) + + return convosConsent + } + : skipToken, + ...reactQueryLongCacheQueryOptions, + }) +} + +export function useConsentForInboxIdQuery(args: IArgs & { caller: string }) { + return useQuery(getConsentForInboxIdQueryOptions(args)) +} + +export function ensureConsentForInboxIdQueryData(args: IStrictArgs & { caller: string }) { + return reactQueryClient.ensureQueryData(getConsentForInboxIdQueryOptions(args)) +} + +export function invalidateConsentForInboxIdQuery(args: IStrictArgs) { + return reactQueryClient.invalidateQueries(getConsentForInboxIdQueryOptions(args)) +} + +export function getConsentForInboxIdQueryData(args: IStrictArgs) { + return reactQueryClient.getQueryData(getConsentForInboxIdQueryOptions(args).queryKey) +} + +export function setConsentForInboxIdQueryData( + args: IStrictArgs & { + consent: IConsentState + }, +) { + return reactQueryClient.setQueryData(getConsentForInboxIdQueryOptions(args).queryKey, args) +} diff --git a/features/consent/consent.utils.ts b/features/consent/consent.utils.ts index 1b674bfb..a8037ad1 100644 --- a/features/consent/consent.utils.ts +++ b/features/consent/consent.utils.ts @@ -14,3 +14,17 @@ export function convertConsentStateToXmtpConsentState( return "denied" } + +export function convertXmtpConsentStateToConsentState( + consentState: IXmtpConsentState, +): IConsentState { + if (consentState === "allowed") { + return "allowed" + } + + if (consentState === "unknown") { + return "unknown" + } + + return "denied" +} diff --git a/features/consent/update-consent-for-inbox-id.mutation.ts b/features/consent/update-consent-for-inbox-id.mutation.ts new file mode 100644 index 00000000..8e6739f1 --- /dev/null +++ b/features/consent/update-consent-for-inbox-id.mutation.ts @@ -0,0 +1,73 @@ +import { IXmtpInboxId } from "@features/xmtp/xmtp.types" +import { MutationObserver, MutationOptions, useMutation } from "@tanstack/react-query" +import { IConsentState } from "@/features/consent/consent.types" +import { setXmtpConsentStateForInboxId } from "@/features/xmtp/xmtp-consent/xmtp-consent" +import { reactQueryClient } from "@/utils/react-query/react-query.client" +import { + getConsentForInboxIdQueryData, + setConsentForInboxIdQueryData, +} from "./consent-for-inbox-id.query" + +export type IUpdateConsentForInboxIdMutationArgs = { + clientInboxId: IXmtpInboxId + peerInboxId: IXmtpInboxId + consent: IConsentState +} + +export function getUpdateConsentForInboxIdMutationOptions(): MutationOptions< + void, + Error, + IUpdateConsentForInboxIdMutationArgs, + { previousConsent: IConsentState } +> { + return { + mutationFn: async (args: IUpdateConsentForInboxIdMutationArgs) => { + const { clientInboxId, peerInboxId, consent } = args + + // eslint-disable-next-line custom-plugin/require-promise-error-handling + await setXmtpConsentStateForInboxId({ + clientInboxId, + peerInboxId, + consent, + }) + }, + onMutate: async (variables) => { + // Get the previous data before updating + const previousConsent = getConsentForInboxIdQueryData({ + clientInboxId: variables.clientInboxId, + inboxIdToCheck: variables.peerInboxId, + }) + + // Update the data optimistically + setConsentForInboxIdQueryData({ + clientInboxId: variables.clientInboxId, + inboxIdToCheck: variables.peerInboxId, + consent: variables.consent, + }) + + return { previousConsent } + }, + onError: (_, variables, context) => { + // On error, roll back to the previous value + if (context?.previousConsent) { + setConsentForInboxIdQueryData({ + clientInboxId: variables.clientInboxId, + inboxIdToCheck: variables.peerInboxId, + consent: context.previousConsent, + }) + } + }, + } +} + +export function executeUpdateConsentForInboxIdMutation(args: IUpdateConsentForInboxIdMutationArgs) { + const mutationObserver = new MutationObserver( + reactQueryClient, + getUpdateConsentForInboxIdMutationOptions(), + ) + return mutationObserver.mutate(args) +} + +export function useUpdateConsentForInboxIdMutation() { + return useMutation(getUpdateConsentForInboxIdMutationOptions()) +} diff --git a/features/consent/use-allow-dm.mutation.ts b/features/consent/use-allow-dm.mutation.ts index 53ec1e58..24d4ffc1 100644 --- a/features/consent/use-allow-dm.mutation.ts +++ b/features/consent/use-allow-dm.mutation.ts @@ -1,5 +1,6 @@ import { useMutation } from "@tanstack/react-query" import { useSafeCurrentSender } from "@/features/authentication/multi-inbox.store" +import { executeUpdateConsentForInboxIdMutation } from "@/features/consent/update-consent-for-inbox-id.mutation" import { addConversationToAllowedConsentConversationsQuery, removeConversationFromAllowedConsentConversationsQuery, @@ -12,10 +13,7 @@ import { getDmQueryData, setDmQueryData } from "@/features/dm/dm.query" import { IDm } from "@/features/dm/dm.types" import { IXmtpConversationId } from "@/features/xmtp/xmtp.types" import { updateObjectAndMethods } from "@/utils/update-object-and-methods" -import { - setXmtpConsentStateForInboxId, - updateXmtpConsentForGroupsForInbox, -} from "../xmtp/xmtp-consent/xmtp-consent" +import { updateXmtpConsentForConversationForInbox } from "../xmtp/xmtp-consent/xmtp-consent" export function useAllowDmMutation() { const currentSenderInboxId = useSafeCurrentSender().inboxId @@ -25,12 +23,12 @@ export function useAllowDmMutation() { const { xmtpConversationId } = args await Promise.all([ - updateXmtpConsentForGroupsForInbox({ + updateXmtpConsentForConversationForInbox({ clientInboxId: currentSenderInboxId, - groupIds: [xmtpConversationId], + conversationIds: [xmtpConversationId], consent: "allowed", }), - setXmtpConsentStateForInboxId({ + executeUpdateConsentForInboxIdMutation({ peerInboxId: currentSenderInboxId, consent: "allowed", clientInboxId: currentSenderInboxId, diff --git a/features/consent/use-allow-group.mutation.ts b/features/consent/use-allow-group.mutation.ts index 2d5e94b5..11b25950 100644 --- a/features/consent/use-allow-group.mutation.ts +++ b/features/consent/use-allow-group.mutation.ts @@ -1,6 +1,7 @@ import { IXmtpConversationId, IXmtpInboxId } from "@features/xmtp/xmtp.types" import { MutationObserver, MutationOptions, useMutation } from "@tanstack/react-query" import { getSafeCurrentSender } from "@/features/authentication/multi-inbox.store" +import { executeUpdateConsentForInboxIdMutation } from "@/features/consent/update-consent-for-inbox-id.mutation" import { addConversationToAllowedConsentConversationsQuery, removeConversationFromAllowedConsentConversationsQuery, @@ -18,10 +19,7 @@ import { import { reactQueryClient } from "@/utils/react-query/react-query.client" import { updateObjectAndMethods } from "@/utils/update-object-and-methods" import { IGroup } from "../groups/group.types" -import { - setXmtpConsentStateForInboxId, - updateXmtpConsentForGroupsForInbox, -} from "../xmtp/xmtp-consent/xmtp-consent" +import { updateXmtpConsentForConversationForInbox } from "../xmtp/xmtp-consent/xmtp-consent" type IAllowGroupMutationOptions = { clientInboxId: IXmtpInboxId @@ -31,8 +29,8 @@ type IAllowGroupMutationOptions = { type IAllowGroupReturnType = Awaited> type IAllowGroupArgs = { - includeAddedBy?: boolean - includeCreator?: boolean + includeAddedBy: boolean + includeCreator: boolean clientInboxId: IXmtpInboxId xmtpConversationId: IXmtpConversationId } @@ -69,14 +67,14 @@ async function allowGroup({ } await Promise.all([ - updateXmtpConsentForGroupsForInbox({ + updateXmtpConsentForConversationForInbox({ clientInboxId, - groupIds: [xmtpConversationId], + conversationIds: [xmtpConversationId], consent: "allowed", }), ...(inboxIdsToAllow.length > 0 ? [ - setXmtpConsentStateForInboxId({ + executeUpdateConsentForInboxIdMutation({ peerInboxId: clientInboxId, consent: "allowed", clientInboxId, diff --git a/features/consent/use-deny-dm.mutation.ts b/features/consent/use-deny-dm.mutation.ts index 43042379..fda21979 100644 --- a/features/consent/use-deny-dm.mutation.ts +++ b/features/consent/use-deny-dm.mutation.ts @@ -1,5 +1,6 @@ import { useMutation } from "@tanstack/react-query" import { useSafeCurrentSender } from "@/features/authentication/multi-inbox.store" +import { executeUpdateConsentForInboxIdMutation } from "@/features/consent/update-consent-for-inbox-id.mutation" import { addConversationToAllowedConsentConversationsQuery, removeConversationFromAllowedConsentConversationsQuery, @@ -11,10 +12,7 @@ import { import { getDmQueryData, setDmQueryData } from "@/features/dm/dm.query" import { IXmtpConversationId, IXmtpInboxId } from "@/features/xmtp/xmtp.types" import { updateObjectAndMethods } from "@/utils/update-object-and-methods" -import { - setXmtpConsentStateForInboxId, - updateXmtpConsentForGroupsForInbox, -} from "../xmtp/xmtp-consent/xmtp-consent" +import { updateXmtpConsentForConversationForInbox } from "../xmtp/xmtp-consent/xmtp-consent" export function useDenyDmMutation() { const currentSenderInboxId = useSafeCurrentSender().inboxId @@ -27,12 +25,12 @@ export function useDenyDmMutation() { const { peerInboxId, xmtpConversationId } = args await Promise.all([ - updateXmtpConsentForGroupsForInbox({ + updateXmtpConsentForConversationForInbox({ clientInboxId: currentSenderInboxId, - groupIds: [xmtpConversationId], + conversationIds: [xmtpConversationId], consent: "denied", }), - setXmtpConsentStateForInboxId({ + executeUpdateConsentForInboxIdMutation({ peerInboxId, consent: "denied", clientInboxId: currentSenderInboxId, diff --git a/features/consent/use-deny-group.mutation.ts b/features/consent/use-deny-group.mutation.ts index 4feb0ac4..711ba42c 100644 --- a/features/consent/use-deny-group.mutation.ts +++ b/features/consent/use-deny-group.mutation.ts @@ -11,7 +11,7 @@ import { getGroupQueryData, setGroupQueryData } from "@/features/groups/queries/ import { IXmtpConversationId, IXmtpInboxId } from "@/features/xmtp/xmtp.types" import { logger } from "@/utils/logger/logger" import { updateObjectAndMethods } from "@/utils/update-object-and-methods" -import { updateXmtpConsentForGroupsForInbox } from "../xmtp/xmtp-consent/xmtp-consent" +import { updateXmtpConsentForConversationForInbox } from "../xmtp/xmtp-consent/xmtp-consent" export const useDenyGroupMutation = (args: { clientInboxId: IXmtpInboxId @@ -21,9 +21,9 @@ export const useDenyGroupMutation = (args: { return useMutation({ mutationFn: async () => { - await updateXmtpConsentForGroupsForInbox({ + await updateXmtpConsentForConversationForInbox({ clientInboxId, - groupIds: [xmtpConversationId], + conversationIds: [xmtpConversationId], consent: "denied", }) return "denied" diff --git a/features/consent/use-group-consent-for-current-sender.ts b/features/consent/use-group-consent-for-current-sender.ts deleted file mode 100644 index d80e7152..00000000 --- a/features/consent/use-group-consent-for-current-sender.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { IXmtpConversationId, IXmtpInboxId } from "@features/xmtp/xmtp.types" -import { useQuery } from "@tanstack/react-query" -import { useCallback } from "react" -import { showSnackbar } from "@/components/snackbar/snackbar.service" -import { useAllowGroupMutation } from "@/features/consent/use-allow-group.mutation" -import { useDenyGroupMutation } from "@/features/consent/use-deny-group.mutation" -import { getGroupQueryOptions, useGroupQuery } from "@/features/groups/queries/group.query" -import { translate } from "@/i18n" -import { useSafeCurrentSender } from "../authentication/multi-inbox.store" -import { setXmtpConsentStateForInboxId } from "../xmtp/xmtp-consent/xmtp-consent" - -export type IGroupConsentOptions = { - includeCreator?: boolean - includeAddedBy?: boolean -} - -export const useGroupConsentForCurrentSender = (args: { - xmtpConversationId: IXmtpConversationId -}) => { - const { xmtpConversationId } = args - - const currentSender = useSafeCurrentSender() - - const { data: group, isLoading: isGroupLoading } = useGroupQuery({ - clientInboxId: currentSender.inboxId, - xmtpConversationId, - }) - - const { - data: groupConsent, - isLoading: isGroupConsentLoading, - isError, - } = useQuery({ - ...getGroupQueryOptions({ - clientInboxId: currentSender.inboxId, - xmtpConversationId, - caller: "useGroupConsentForCurrentAccount", - }), - select: (group) => group?.consentState, - }) - - const { mutateAsync: allowGroupMutation, isPending: isAllowingGroup } = useAllowGroupMutation({ - clientInboxId: currentSender.inboxId, - xmtpConversationId, - }) - - const { mutateAsync: denyGroupMutation, isPending: isDenyingGroup } = useDenyGroupMutation({ - clientInboxId: currentSender.inboxId, - xmtpConversationId, - }) - - const allowGroup = useCallback( - async (args: IGroupConsentOptions) => { - const { includeAddedBy, includeCreator } = args - - if (!group) { - throw new Error("Group is required") - } - - await allowGroupMutation({ - clientInboxId: currentSender.inboxId, - xmtpConversationId, - includeAddedBy, - includeCreator, - }) - }, - [allowGroupMutation, group, currentSender, xmtpConversationId], - ) - - const denyGroup = useCallback( - async (args: IGroupConsentOptions) => { - const { includeAddedBy, includeCreator } = args - - if (!group) { - showSnackbar({ - type: "error", - message: translate("group_not_found"), - }) - return - } - - await denyGroupMutation() - - const inboxIdsToDeny: IXmtpInboxId[] = [] - - if (includeAddedBy && group.addedByInboxId) { - inboxIdsToDeny.push(group.addedByInboxId) - } - - if (includeCreator && group.creatorInboxId) { - inboxIdsToDeny.push(group.creatorInboxId) - } - - if (inboxIdsToDeny.length > 0) { - await Promise.all( - inboxIdsToDeny.map((inboxId) => - setXmtpConsentStateForInboxId({ - peerInboxId: inboxId, - consent: "denied", - clientInboxId: currentSender.inboxId, - }), - ), - ) - } - }, - [denyGroupMutation, group, currentSender], - ) - - const isLoading = isGroupLoading || isGroupConsentLoading - - return { - consent: groupConsent, - isLoading, - isError, - allowGroup, - denyGroup, - isAllowingGroup, - isDenyingGroup, - } -} diff --git a/features/conversation/conversation-chat/conversation-consent-popup/conversation-consent-popup-dm.tsx b/features/conversation/conversation-chat/conversation-consent-popup/conversation-consent-popup-dm.tsx index 054eaed2..7acbdbf1 100644 --- a/features/conversation/conversation-chat/conversation-consent-popup/conversation-consent-popup-dm.tsx +++ b/features/conversation/conversation-chat/conversation-consent-popup/conversation-consent-popup-dm.tsx @@ -3,7 +3,7 @@ import React, { useCallback } from "react" import { showActionSheet } from "@/components/action-sheet" import { useSafeCurrentSender } from "@/features/authentication/multi-inbox.store" import { useAllowDmMutation } from "@/features/consent/use-allow-dm.mutation" -import { useDenyDmMutation } from "@/features/consent/use-deny-dm.mutation" +import { useDeleteConversationsMutation } from "@/features/conversation/conversation-requests-list/delete-conversations.mutation" import { useDmQuery } from "@/features/dm/dm.query" import { useRouter } from "@/navigation/use-navigation" import { captureErrorWithToast } from "@/utils/capture-error" @@ -28,10 +28,10 @@ export function ConversationConsentPopupDm() { const navigation = useRouter() - const { mutateAsync: denyDmConsentAsync } = useDenyDmMutation() + const { mutateAsync: deleteConversationsAsync } = useDeleteConversationsMutation() const { mutateAsync: allowDmConsentAsync } = useAllowDmMutation() - const handleBlock = useCallback(async () => { + const handleDelete = useCallback(async () => { if (!dm) { throw new Error("Dm not found") } @@ -41,28 +41,28 @@ export function ConversationConsentPopupDm() { options: [translate("Delete"), translate("Cancel")], cancelButtonIndex: 1, destructiveButtonIndex: 0, - title: translate("if_you_block_contact"), + title: `If you delete this conversation, you won't be able to see any messages from them anymore.`, }, callback: async (selectedIndex?: number) => { if (selectedIndex === 0) { try { - await denyDmConsentAsync({ - xmtpConversationId, - peerInboxId: dm.peerInboxId, + await deleteConversationsAsync({ + conversationIds: [xmtpConversationId], + alsoDenyInviterConsent: true, // If we delete here it's almost certain we also don't want to see any messages from them anymore }) navigation.pop() } catch (error) { captureErrorWithToast( new GenericError({ error, additionalMessage: "Error consenting" }), { - message: "Error deleting conversation" - } + message: "Error deleting conversation", + }, ) } } }, }) - }, [navigation, denyDmConsentAsync, dm, xmtpConversationId]) + }, [navigation, deleteConversationsAsync, dm, xmtpConversationId]) const handleAccept = useCallback(async () => { try { @@ -75,7 +75,7 @@ export function ConversationConsentPopupDm() { }) } catch (error) { captureErrorWithToast(new GenericError({ error, additionalMessage: "Error consenting" }), { - message: "Error joining conversation" + message: "Error joining conversation", }) } }, [allowDmConsentAsync, dm, xmtpConversationId]) @@ -92,7 +92,7 @@ export function ConversationConsentPopupDm() { variant="text" action="danger" text={translate("Delete")} - onPress={handleBlock} + onPress={handleDelete} /> {translate("They won't be notified if you delete it")} diff --git a/features/conversation/conversation-chat/conversation-consent-popup/conversation-consent-popup-group.tsx b/features/conversation/conversation-chat/conversation-consent-popup/conversation-consent-popup-group.tsx index 11e94d0e..44ca5123 100644 --- a/features/conversation/conversation-chat/conversation-consent-popup/conversation-consent-popup-group.tsx +++ b/features/conversation/conversation-chat/conversation-consent-popup/conversation-consent-popup-group.tsx @@ -1,58 +1,122 @@ import { translate } from "@i18n" import React, { useCallback } from "react" -import { useColorScheme } from "react-native" -import { useGroupConsentForCurrentSender } from "@/features/consent/use-group-consent-for-current-sender" +import { showActionSheet } from "@/components/action-sheet" +import { useSafeCurrentSender } from "@/features/authentication/multi-inbox.store" +import { ensureConsentForInboxIdQueryData } from "@/features/consent/consent-for-inbox-id.query" +import { useAllowGroupMutation } from "@/features/consent/use-allow-group.mutation" import { ConsentPopupButtonsContainer, ConversationConsentPopupButton, ConversationConsentPopupContainer, ConversationConsentPopupHelperText, } from "@/features/conversation/conversation-chat/conversation-consent-popup/conversation-consent-popup.design-system" -import { useGroupName } from "@/features/groups/hooks/use-group-name" -import { groupRemoveRestoreHandler } from "@/features/groups/utils/groupActionHandlers" +import { useDeleteConversationsMutation } from "@/features/conversation/conversation-requests-list/delete-conversations.mutation" +import { ensureGroupQueryData } from "@/features/groups/queries/group.query" +import { ensurePreferredDisplayInfo } from "@/features/preferred-display-info/use-preferred-display-info" import { useRouter } from "@/navigation/use-navigation" import { captureErrorWithToast } from "@/utils/capture-error" import { GenericError } from "@/utils/error" +import { shortDisplayName } from "@/utils/str" import { useCurrentXmtpConversationIdSafe } from "../conversation.store-context" export function ConversationConsentPopupGroup() { const xmtpConversationId = useCurrentXmtpConversationIdSafe() + const currentSender = useSafeCurrentSender() + const router = useRouter() - const navigation = useRouter() + const { mutateAsync: allowGroupAsync } = useAllowGroupMutation({ + clientInboxId: currentSender.inboxId, + xmtpConversationId, + }) - const colorScheme = useColorScheme() + const { mutateAsync: deleteConversationsAsync } = useDeleteConversationsMutation() - const { denyGroup, allowGroup } = useGroupConsentForCurrentSender({ xmtpConversationId }) + const handleDeleteGroup = useCallback(async () => { + try { + const group = await ensureGroupQueryData({ + clientInboxId: currentSender.inboxId, + xmtpConversationId, + caller: "ConversationConsentPopupGroup", + }) - const { groupName } = useGroupName({ xmtpConversationId }) + if (!group) { + throw new Error("Group not found while deleting group in ConversationConsentPopupGroup") + } - const handleDeclineGroup = useCallback(async () => { - groupRemoveRestoreHandler( - "unknown", // To display "Remove & Block inviter" - colorScheme, - groupName, - allowGroup, - denyGroup, - )((success: boolean) => { - if (success) { - navigation.pop() + const hasAllowedConsentForAddedBy = await ensureConsentForInboxIdQueryData({ + clientInboxId: currentSender.inboxId, + inboxIdToCheck: group.addedByInboxId, + caller: "ConversationConsentPopupGroup", + }) + + let options = [translate("Delete"), translate("Cancel")] + let destructiveButtonIndex: number | number[] = 0 + let cancelButtonIndex = 1 + + if (hasAllowedConsentForAddedBy === "allowed") { + const { displayName: addedByDisplayName } = await ensurePreferredDisplayInfo({ + inboxId: group.addedByInboxId, + caller: "ConversationConsentPopupGroup", + }) + options = [ + translate("Delete"), + `Delete and Block ${shortDisplayName(addedByDisplayName)} (invited you)`, + translate("Cancel"), + ] + destructiveButtonIndex = [0, 1] + cancelButtonIndex = 2 } - // If not successful, do nothing (user canceled) - }) - }, [groupName, colorScheme, allowGroup, denyGroup, navigation]) + + showActionSheet({ + options: { + options, + cancelButtonIndex, + destructiveButtonIndex, + title: `If you delete this conversation, you won't be able to see any messages from them anymore.`, + }, + callback: async (selectedIndex?: number) => { + if (selectedIndex === 0) { + await deleteConversationsAsync({ + conversationIds: [xmtpConversationId], + alsoDenyInviterConsent: false, + }) + router.goBack() + } else if (selectedIndex === 1 && hasAllowedConsentForAddedBy === "allowed") { + await deleteConversationsAsync({ + conversationIds: [xmtpConversationId], + alsoDenyInviterConsent: true, + }) + router.goBack() + } + }, + }) + } catch (error) { + captureErrorWithToast( + new GenericError({ error, additionalMessage: `Failed to delete group` }), + { + message: "Failed to delete group", + }, + ) + } + }, [deleteConversationsAsync, currentSender.inboxId, xmtpConversationId, router]) const onAccept = useCallback(async () => { try { - await allowGroup({ - includeCreator: false, + await allowGroupAsync({ + clientInboxId: currentSender.inboxId, + xmtpConversationId, includeAddedBy: false, + includeCreator: false, }) } catch (error) { - captureErrorWithToast(new GenericError({ error, additionalMessage: `Failed to allow group` }), { - message: "Failed to allow group" - }) + captureErrorWithToast( + new GenericError({ error, additionalMessage: `Failed to allow group` }), + { + message: "Failed to allow group", + }, + ) } - }, [allowGroup]) + }, [allowGroupAsync, currentSender.inboxId, xmtpConversationId]) return ( @@ -66,7 +130,7 @@ export function ConversationConsentPopupGroup() { variant="text" action="danger" text={translate("Delete")} - onPress={handleDeclineGroup} + onPress={handleDeleteGroup} /> {translate("No one is notified if you delete it")} diff --git a/features/conversation/conversation-create/mutations/create-conversation-and-send-first-message.mutation.ts b/features/conversation/conversation-create/mutations/create-conversation-and-send-first-message.mutation.ts index 3d7bc260..e6db4a82 100644 --- a/features/conversation/conversation-create/mutations/create-conversation-and-send-first-message.mutation.ts +++ b/features/conversation/conversation-create/mutations/create-conversation-and-send-first-message.mutation.ts @@ -1,6 +1,7 @@ import { IXmtpInboxId } from "@features/xmtp/xmtp.types" import { MutationOptions, useMutation } from "@tanstack/react-query" import { getSafeCurrentSender } from "@/features/authentication/multi-inbox.store" +import { executeUpdateConsentForInboxIdMutation } from "@/features/consent/update-consent-for-inbox-id.mutation" import { addConversationToAllowedConsentConversationsQuery } from "@/features/conversation/conversation-list/conversations-allowed-consent.query" import { setConversationQueryData } from "@/features/conversation/queries/conversation.query" import { convertXmtpConversationToConvosConversation } from "@/features/conversation/utils/convert-xmtp-conversation-to-convos-conversation" @@ -112,6 +113,17 @@ export const getCreateConversationAndSendFirstMessageMutationOptions = }).catch(captureError) } + // We allow those inboxIds if we invited them to chat + Promise.all( + variables.inboxIds.map(async (inboxId) => + executeUpdateConsentForInboxIdMutation({ + clientInboxId: currentSender.inboxId, + peerInboxId: inboxId, + consent: "allowed", + }), + ), + ).catch(captureError) + // Handle the new conversation setConversationQueryData({ clientInboxId: currentSender.inboxId, diff --git a/features/conversation/conversation-list/hooks/use-delete-dm.ts b/features/conversation/conversation-list/hooks/use-delete-dm.ts index 3b2f617d..e6f85e54 100644 --- a/features/conversation/conversation-list/hooks/use-delete-dm.ts +++ b/features/conversation/conversation-list/hooks/use-delete-dm.ts @@ -1,14 +1,8 @@ -import { useMutation } from "@tanstack/react-query" import { useCallback } from "react" import { showActionSheet } from "@/components/action-sheet" import { useSafeCurrentSender } from "@/features/authentication/multi-inbox.store" import { useDenyDmMutation } from "@/features/consent/use-deny-dm.mutation" -import { deleteConversationMetadata } from "@/features/conversation/conversation-metadata/conversation-metadata.api" -import { - getConversationMetadataQueryData, - updateConversationMetadataQueryData, -} from "@/features/conversation/conversation-metadata/conversation-metadata.query" -import { ensureDeviceIdentityForInboxId } from "@/features/convos-identities/convos-identities.service" +import { useDeleteConversationsMutation } from "@/features/conversation/conversation-requests-list/delete-conversations.mutation" import { useDmQuery } from "@/features/dm/dm.query" import { usePreferredDisplayInfo } from "@/features/preferred-display-info/use-preferred-display-info" import { IXmtpConversationId } from "@/features/xmtp/xmtp.types" @@ -35,38 +29,7 @@ export const useDeleteDm = ({ }) const { mutateAsync: denyDmConsentAsync } = useDenyDmMutation() - - const { mutateAsync: deleteDmAsync } = useMutation({ - mutationFn: async () => { - const deviceIdentity = await ensureDeviceIdentityForInboxId(currentSender.inboxId) - - return deleteConversationMetadata({ - deviceIdentityId: deviceIdentity.id, - xmtpConversationId, - }) - }, - onMutate: () => { - const previousDeleted = getConversationMetadataQueryData({ - clientInboxId: currentSender.inboxId, - xmtpConversationId, - })?.deleted - - updateConversationMetadataQueryData({ - clientInboxId: currentSender.inboxId, - xmtpConversationId, - updateData: { deleted: true }, - }) - - return { previousDeleted } - }, - onError: (error, _, context) => { - updateConversationMetadataQueryData({ - clientInboxId: currentSender.inboxId, - xmtpConversationId, - updateData: { deleted: context?.previousDeleted }, - }) - }, - }) + const { mutateAsync: deleteConversationsAsync } = useDeleteConversationsMutation() return useCallback(() => { const title = `${translate("delete_chat_with")} ${displayName}?` @@ -84,7 +47,10 @@ export const useDeleteDm = ({ label: translate("delete"), action: async () => { try { - await deleteDmAsync() + await deleteConversationsAsync({ + conversationIds: [xmtpConversationId], + alsoDenyInviterConsent: false, + }) } catch (error) { captureErrorWithToast( new GenericError({ error, additionalMessage: "Error deleting dm" }), @@ -99,11 +65,16 @@ export const useDeleteDm = ({ label: translate("delete_and_block"), action: async () => { try { - await deleteDmAsync() - await denyDmConsentAsync({ - peerInboxId: dm.peerInboxId, - xmtpConversationId, - }) + await Promise.all([ + deleteConversationsAsync({ + conversationIds: [xmtpConversationId], + alsoDenyInviterConsent: true, + }), + denyDmConsentAsync({ + peerInboxId: dm.peerInboxId, + xmtpConversationId, + }), + ]) } catch (error) { captureErrorWithToast( new GenericError({ error, additionalMessage: "Error deleting dm" }), @@ -133,5 +104,11 @@ export const useDeleteDm = ({ } }, }) - }, [displayName, deleteDmAsync, denyDmConsentAsync, dm?.peerInboxId, xmtpConversationId]) + }, [ + displayName, + deleteConversationsAsync, + denyDmConsentAsync, + dm?.peerInboxId, + xmtpConversationId, + ]) } diff --git a/features/conversation/conversation-list/hooks/use-delete-group.ts b/features/conversation/conversation-list/hooks/use-delete-group.ts index abb7e4ec..830cf5d9 100644 --- a/features/conversation/conversation-list/hooks/use-delete-group.ts +++ b/features/conversation/conversation-list/hooks/use-delete-group.ts @@ -9,7 +9,7 @@ import { } from "@/features/conversation/conversation-metadata/conversation-metadata.query" import { ensureDeviceIdentityForInboxId } from "@/features/convos-identities/convos-identities.service" import { getGroupQueryData } from "@/features/groups/queries/group.query" -import { updateXmtpConsentForGroupsForInbox } from "@/features/xmtp/xmtp-consent/xmtp-consent" +import { updateXmtpConsentForConversationForInbox } from "@/features/xmtp/xmtp-consent/xmtp-consent" import { IXmtpConversationId } from "@/features/xmtp/xmtp.types" import { translate } from "@/i18n" import { captureErrorWithToast } from "@/utils/capture-error" @@ -84,9 +84,9 @@ export const useDeleteGroup = (args: { xmtpConversationId: IXmtpConversationId } action: async () => { try { await deleteGroupAsync() - await updateXmtpConsentForGroupsForInbox({ + await updateXmtpConsentForConversationForInbox({ clientInboxId: currentSender.inboxId, - groupIds: [group.xmtpId], + conversationIds: [group.xmtpId], consent: "denied", }) } catch (error) { diff --git a/features/conversation/conversation-requests-list/delete-conversations.mutation.ts b/features/conversation/conversation-requests-list/delete-conversations.mutation.ts index 36760548..8c7e5a0b 100644 --- a/features/conversation/conversation-requests-list/delete-conversations.mutation.ts +++ b/features/conversation/conversation-requests-list/delete-conversations.mutation.ts @@ -1,57 +1,84 @@ import { useMutation } from "@tanstack/react-query" import { getSafeCurrentSender } from "@/features/authentication/multi-inbox.store" +import { executeUpdateConsentForInboxIdMutation } from "@/features/consent/update-consent-for-inbox-id.mutation" import { deleteConversationMetadata } from "@/features/conversation/conversation-metadata/conversation-metadata.api" import { updateConversationMetadataQueryData } from "@/features/conversation/conversation-metadata/conversation-metadata.query" import { getConversationQueryData } from "@/features/conversation/queries/conversation.query" import { isConversationGroup } from "@/features/conversation/utils/is-conversation-group" import { ensureDeviceIdentityForInboxId } from "@/features/convos-identities/convos-identities.service" -import { - setXmtpConsentStateForInboxId, - updateXmtpConsentForGroupsForInbox, -} from "@/features/xmtp/xmtp-consent/xmtp-consent" +import { updateXmtpConsentForConversationForInbox } from "@/features/xmtp/xmtp-consent/xmtp-consent" import { IXmtpConversationId } from "@/features/xmtp/xmtp.types" +type IDeleteConversationsMutationArgs = { + conversationIds: IXmtpConversationId[] + alsoDenyInviterConsent: boolean +} + export const useDeleteConversationsMutation = () => { return useMutation({ - mutationFn: async (conversationIds: IXmtpConversationId[]) => { + mutationFn: async (args: IDeleteConversationsMutationArgs) => { + const { conversationIds, alsoDenyInviterConsent } = args const currentSender = getSafeCurrentSender() const deviceIdentity = await ensureDeviceIdentityForInboxId(currentSender.inboxId) - await Promise.all([ - // There's a good chance that if we delete we also want to deny the conversation - updateXmtpConsentForGroupsForInbox({ - clientInboxId: currentSender.inboxId, - groupIds: conversationIds, - consent: "denied", - }), - // Also deny the user if the conversation is a DM - ...conversationIds.map((conversationId) => { - const conversation = getConversationQueryData({ - clientInboxId: currentSender.inboxId, - xmtpConversationId: conversationIds[0], - }) + const promises = [] + + // if (alsoDenyInviterConsent) { + // // Also deny the user that invited them + // promises.push( + // ...conversationIds.map((conversationId) => { + // const conversation = getConversationQueryData({ + // clientInboxId: currentSender.inboxId, + // xmtpConversationId: conversationId, + // }) - if (!conversation || isConversationGroup(conversation)) { - return Promise.resolve() - } + // if (!conversation) { + // throw new Error("Conversation not found while denying inviter consent") + // } - return setXmtpConsentStateForInboxId({ - peerInboxId: conversation.peerInboxId, - consent: "denied", - clientInboxId: currentSender.inboxId, - }) - }), + // if (isConversationGroup(conversation)) { + // return executeUpdateConsentForInboxIdMutation({ + // peerInboxId: conversation.addedByInboxId, + // consent: "denied", + // clientInboxId: currentSender.inboxId, + // }) + // } else { + // return executeUpdateConsentForInboxIdMutation({ + // peerInboxId: conversation.peerInboxId, + // consent: "denied", + // clientInboxId: currentSender.inboxId, + // }) + // } + // }), + // ) + // } + + // // Deny the conversation content to not stream any more messages from it + // promises.push( + // updateXmtpConsentForConversationForInbox({ + // clientInboxId: currentSender.inboxId, + // conversationIds: conversationIds, + // consent: "denied", + // }), + // ) + + // Delete the conversations + promises.push( ...conversationIds.map((conversationId) => deleteConversationMetadata({ deviceIdentityId: deviceIdentity.id, xmtpConversationId: conversationId, }), ), - ]) + ) + + await Promise.all(promises) }, - onMutate: async (conversationIds: IXmtpConversationId[]) => { + onMutate: async (args: IDeleteConversationsMutationArgs) => { + const { conversationIds } = args const currentSender = getSafeCurrentSender() + // Update the conversation metadata to be deleted conversationIds.forEach((conversationId) => { updateConversationMetadataQueryData({ clientInboxId: currentSender.inboxId, @@ -62,7 +89,8 @@ export const useDeleteConversationsMutation = () => { }) }) }, - onError: (_, conversationIds: IXmtpConversationId[]) => { + onError: (_, args: IDeleteConversationsMutationArgs) => { + const { conversationIds } = args const currentSender = getSafeCurrentSender() conversationIds.forEach((conversationId) => { diff --git a/features/conversation/queries/conversation.query.ts b/features/conversation/queries/conversation.query.ts index b6593394..b8ac99a5 100644 --- a/features/conversation/queries/conversation.query.ts +++ b/features/conversation/queries/conversation.query.ts @@ -148,6 +148,16 @@ export async function maybeUpdateConversationQueryLastMessage(args: { const { clientInboxId, xmtpConversationId, messageIds } = args try { + const conversation = getConversationQueryData({ + clientInboxId, + xmtpConversationId, + }) + + if (!conversation) { + // If we don't even have the conversation, we can't update the last message + return + } + const messages = await Promise.all( messageIds.map((messageId) => ensureConversationMessageQueryData({ @@ -159,17 +169,6 @@ export async function maybeUpdateConversationQueryLastMessage(args: { ), ) - const conversation = getConversationQueryData({ - clientInboxId, - xmtpConversationId, - }) - - if (!conversation) { - throw new Error( - "Conversation not found when wanting to update conversation last message with messages", - ) - } - // Find the most recent message from the new messages let mostRecentMessage = messages.filter(Boolean).sort((a, b) => a.sentMs - b.sentMs)[0] diff --git a/features/current-user/current-user.query.ts b/features/current-user/current-user.query.ts index c2ce0751..5288c47f 100644 --- a/features/current-user/current-user.query.ts +++ b/features/current-user/current-user.query.ts @@ -2,6 +2,7 @@ import { queryOptions, useQuery } from "@tanstack/react-query" import { useAuthenticationStore } from "@/features/authentication/authentication.store" import { IConvosCurrentUser } from "@/features/current-user/current-user.types" import { reactQueryClient } from "@/utils/react-query/react-query.client" +import { ensureQueryDataBetter } from "@/utils/react-query/react-query.helpers" import { getReactQueryKey } from "@/utils/react-query/react-query.utils" import { fetchCurrentUser } from "./current-user.api" @@ -44,5 +45,5 @@ export function getCurrentUserQueryData() { export function ensureCurrentUserQueryData(args: { caller: string }) { const { caller } = args - return reactQueryClient.ensureQueryData(getCurrentUserQueryOptions({ caller })) + return ensureQueryDataBetter(getCurrentUserQueryOptions({ caller })) } diff --git a/features/groups/utils/getGroupMemberActions.test.ts b/features/groups/utils/getGroupMemberActions.test.ts deleted file mode 100644 index 1cd2a1e7..00000000 --- a/features/groups/utils/getGroupMemberActions.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { PermissionPolicySet } from "@xmtp/react-native-sdk/build/lib/types/PermissionPolicySet" -import { getGroupMemberActions } from "./getGroupMemberActions" - -describe("getGroupMemberActions", () => { - test("should return correct actions when user can promote to admin", () => { - const result = getGroupMemberActions({ - groupPermissionLevel: { - addAdminPolicy: "allow", - } as PermissionPolicySet, - isCurrentUser: false, - isSuperAdmin: false, - isAdmin: false, - currentAccountIsSuperAdmin: true, - currentAccountIsAdmin: true, - }) - - expect(result.options).toContain("Promote to admin") - }) - - test("should return correct actions when user can promote to super admin", () => { - const result = getGroupMemberActions({ - groupPermissionLevel: { - addAdminPolicy: "allow", - } as PermissionPolicySet, - isCurrentUser: false, - isSuperAdmin: false, - isAdmin: false, - currentAccountIsSuperAdmin: true, - currentAccountIsAdmin: true, - }) - - expect(result.options).toContain("Promote to super admin") - }) - - test("should return correct actions when user can revoke admin", () => { - const result = getGroupMemberActions({ - groupPermissionLevel: { - removeAdminPolicy: "allow", - } as PermissionPolicySet, - isCurrentUser: false, - isSuperAdmin: false, - isAdmin: true, - currentAccountIsSuperAdmin: true, - currentAccountIsAdmin: true, - }) - - expect(result.options).toContain("Revoke admin") - }) - - test("should return correct actions when user can revoke super admin", () => { - const result = getGroupMemberActions({ - groupPermissionLevel: { - addAdminPolicy: "allow", - } as PermissionPolicySet, - isCurrentUser: false, - isSuperAdmin: true, - isAdmin: false, - currentAccountIsSuperAdmin: true, - currentAccountIsAdmin: true, - }) - - expect(result.options).toContain("Revoke super admin") - }) - - test("should return correct actions when user can remove from group", () => { - const result = getGroupMemberActions({ - groupPermissionLevel: { - removeMemberPolicy: "allow", - } as PermissionPolicySet, - isCurrentUser: false, - isSuperAdmin: false, - isAdmin: false, - currentAccountIsSuperAdmin: true, - currentAccountIsAdmin: true, - }) - - expect(result.options).toContain("Remove from group") - }) - - test("should not include admin actions for the current user", () => { - const result = getGroupMemberActions({ - groupPermissionLevel: { - addAdminPolicy: "allow", - } as PermissionPolicySet, - isCurrentUser: true, - isSuperAdmin: false, - isAdmin: false, - currentAccountIsSuperAdmin: true, - currentAccountIsAdmin: true, - }) - - expect(result.options).not.toContain("Promote to admin") - expect(result.options).not.toContain("Promote to super admin") - expect(result.options).not.toContain("Revoke admin") - expect(result.options).not.toContain("Revoke super admin") - expect(result.options).not.toContain("Remove from group") - }) -}) diff --git a/features/groups/utils/getGroupMemberActions.ts b/features/groups/utils/getGroupMemberActions.ts deleted file mode 100644 index a037718b..00000000 --- a/features/groups/utils/getGroupMemberActions.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { translate } from "@i18n" -import { PermissionPolicySet } from "@xmtp/react-native-sdk/build/lib/types/PermissionPolicySet" -import { userCanDoGroupActions } from "./user-can-do-group-actions" - -type GetGroupMemberActionsProps = { - groupPermissionLevel: PermissionPolicySet | undefined - isCurrentUser: boolean - isSuperAdmin: boolean - isAdmin: boolean - currentAccountIsSuperAdmin: boolean - currentAccountIsAdmin: boolean -} - -export const getGroupMemberActions = ({ - groupPermissionLevel, - isCurrentUser, - isSuperAdmin, - isAdmin, - currentAccountIsSuperAdmin, - currentAccountIsAdmin, -}: GetGroupMemberActionsProps) => { - const canRemove = - !isCurrentUser && - userCanDoGroupActions({ - groupPermissionPolicy: groupPermissionLevel, - action: "removeMemberPolicy", - isSuperAdmin: currentAccountIsSuperAdmin, - isAdmin: currentAccountIsAdmin, - }) - const canPromoteToSuperAdmin = !isSuperAdmin && !isCurrentUser && currentAccountIsSuperAdmin - const canPromoteToAdmin = - !isCurrentUser && - !isAdmin && - !isSuperAdmin && - userCanDoGroupActions({ - groupPermissionPolicy: groupPermissionLevel, - action: "addAdminPolicy", - isSuperAdmin: currentAccountIsSuperAdmin, - isAdmin: currentAccountIsAdmin, - }) - - const canRevokeAdmin = - !isCurrentUser && - isAdmin && - !isSuperAdmin && - userCanDoGroupActions({ - groupPermissionPolicy: groupPermissionLevel, - action: "removeAdminPolicy", - isSuperAdmin: currentAccountIsSuperAdmin, - isAdmin: currentAccountIsAdmin, - }) - const canRevokeSuperAdmin = !isCurrentUser && currentAccountIsSuperAdmin && isSuperAdmin - const options = [translate("group_screen_member_actions.profile_page")] - let cancelButtonIndex = 1 - let promoteAdminIndex: number | undefined = undefined - if (canPromoteToAdmin) { - promoteAdminIndex = options.length - options.push(translate("group_screen_member_actions.promote_to_admin")) - cancelButtonIndex++ - } - let promoteSuperAdminIndex: number | undefined = undefined - if (canPromoteToSuperAdmin) { - promoteSuperAdminIndex = options.length - options.push(translate("group_screen_member_actions.promote_to_super_admin")) - cancelButtonIndex++ - } - let revokeAdminIndex: number | undefined = undefined - if (canRevokeAdmin) { - revokeAdminIndex = options.length - options.push(translate("group_screen_member_actions.revoke_admin")) - cancelButtonIndex++ - } - let revokeSuperAdminIndex: number | undefined = undefined - if (canRevokeSuperAdmin) { - revokeSuperAdminIndex = options.length - options.push(translate("group_screen_member_actions.revoke_super_admin")) - cancelButtonIndex++ - } - let removeIndex: number | undefined = undefined - - if (canRemove) { - removeIndex = options.length - options.push(translate("group_screen_member_actions.remove_member")) - cancelButtonIndex++ - } - options.push(translate("group_screen_member_actions.cancel")) - const destructiveButtonIndex = canRemove ? options.length - 2 : undefined - - return { - options, - cancelButtonIndex, - promoteAdminIndex, - promoteSuperAdminIndex, - revokeAdminIndex, - revokeSuperAdminIndex, - removeIndex, - destructiveButtonIndex, - } -} diff --git a/features/groups/utils/groupActionHandlers.ts b/features/groups/utils/groupActionHandlers.ts deleted file mode 100644 index f9bd355e..00000000 --- a/features/groups/utils/groupActionHandlers.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { translate } from "@i18n" -import { ConsentState } from "@xmtp/react-native-sdk" -import { ColorSchemeName } from "react-native" -import { showActionSheet } from "@/components/action-sheet" - -type GroupAction = { - includeAddedBy: boolean - includeCreator: boolean -} - -type GroupActionHandler = (options: GroupAction) => void - -export const groupRemoveRestoreHandler = ( - consent: ConsentState | undefined, - colorScheme: ColorSchemeName, - groupName: string | undefined, - allowGroup: GroupActionHandler, - blockGroup: GroupActionHandler, -) => { - return (callback: (success: boolean) => void) => { - const showOptions = (options: string[], title: string, actions: (() => void)[]) => { - showActionSheet({ - options: { - options, - cancelButtonIndex: options.length - 1, - destructiveButtonIndex: consent === "denied" ? undefined : [0, 1], - title, - }, - callback: (selectedIndex?: number) => { - if (selectedIndex !== undefined && selectedIndex < actions.length) { - actions[selectedIndex]() - callback(true) - } else { - callback(false) - } - }, - }) - } - - if (consent === "denied") { - showOptions( - [translate("restore"), translate("restore_and_unblock_inviter"), translate("Cancel")], - `${translate("restore")} ${groupName}?`, - [ - () => allowGroup({ includeAddedBy: false, includeCreator: false }), - () => allowGroup({ includeAddedBy: true, includeCreator: false }), - ], - ) - } else { - showOptions( - [translate("remove"), translate("remove_and_block_inviter"), translate("Cancel")], - `${translate("remove")} ${groupName}?`, - [ - () => blockGroup({ includeAddedBy: false, includeCreator: false }), - () => blockGroup({ includeAddedBy: true, includeCreator: false }), - ], - ) - } - } -} diff --git a/features/preferred-display-info/use-preferred-display-info.ts b/features/preferred-display-info/use-preferred-display-info.ts index c7b98bb6..a441c6e8 100644 --- a/features/preferred-display-info/use-preferred-display-info.ts +++ b/features/preferred-display-info/use-preferred-display-info.ts @@ -52,13 +52,14 @@ export function usePreferredDisplayInfo(args: PreferredDisplayInfoArgs & { calle const currentSender = useSafeCurrentSender() const caller = `${callerArg}:usePreferredDisplayInfo` + const getXmtpInboxIdFromEthAddressOptions = getXmtpInboxIdFromEthAddressQueryOptions({ + clientInboxId: currentSender.inboxId, + targetEthAddress: ethAddressArg!, // ! because we check enabled + caller, + }) const { data: inboxIdFromEthAddress } = useQuery({ - ...getXmtpInboxIdFromEthAddressQueryOptions({ - clientInboxId: currentSender.inboxId, - targetEthAddress: ethAddressArg!, // ! because we check enabled - caller, - }), - enabled: enabled && !!ethAddressArg, + ...getXmtpInboxIdFromEthAddressOptions, + enabled: enabled && !!ethAddressArg && getXmtpInboxIdFromEthAddressOptions.enabled !== false, ...(freshData && { ...reactQueryFreshDataQueryOptions }), }) @@ -69,41 +70,43 @@ export function usePreferredDisplayInfo(args: PreferredDisplayInfoArgs & { calle inboxId, caller, }) - const { data: ethAddressesForXmtpInboxId } = useQuery({ ...ethAddressesOptions, enabled: enabled && ethAddressesOptions.enabled !== false, ...(freshData && { ...reactQueryFreshDataQueryOptions }), }) + const getProfileQueryOptions = getProfileQueryConfig({ xmtpId: inboxId, caller }) const { data: profile, isLoading: isLoadingProfile } = useQuery({ // Get Convos profile data - ...getProfileQueryConfig({ xmtpId: inboxId, caller }), - enabled, + ...getProfileQueryOptions, + enabled: enabled && getProfileQueryOptions.enabled !== false, ...(freshData && { ...reactQueryFreshDataQueryOptions }), }) + const getSocialProfilesForInboxIdOptions = getSocialProfilesForInboxIdQueryOptions({ + inboxId, + clientInboxId: currentSender.inboxId, + caller, + }) const { data: socialProfilesForInboxId, isLoading: isLoadingSocialProfilesForInboxId } = useQuery( { - ...getSocialProfilesForInboxIdQueryOptions({ - inboxId, - clientInboxId: currentSender.inboxId, - caller, - }), - enabled, + ...getSocialProfilesForInboxIdOptions, + enabled: enabled && getSocialProfilesForInboxIdOptions.enabled !== false, ...(freshData && { ...reactQueryFreshDataQueryOptions }), }, ) const ethAddress = ethAddressArg || ethAddressesForXmtpInboxId?.[0] + const getSocialProfilesForEthAddressOptions = getSocialProfilesForEthAddressQueryOptions({ + ethAddress, + caller, + }) const { data: socialProfilesForEthAddress, isLoading: isLoadingSocialProfilesForEthAddress } = useQuery({ - ...getSocialProfilesForEthAddressQueryOptions({ - ethAddress, - caller, - }), - enabled, + ...getSocialProfilesForEthAddressOptions, + enabled: enabled && getSocialProfilesForEthAddressOptions.enabled !== false, ...(freshData && { ...reactQueryFreshDataQueryOptions }), }) diff --git a/features/profiles/profile-me.tsx b/features/profiles/profile-me.tsx index 322cf936..0d03c08a 100644 --- a/features/profiles/profile-me.tsx +++ b/features/profiles/profile-me.tsx @@ -110,16 +110,16 @@ export function ProfileMe(props: { inboxId: IXmtpInboxId }) { > { - router.navigate("Blocked") + router.navigate("ChatsRequests") }} /> { - router.navigate("ChatsRequests") + router.navigate("Blocked") }} /> diff --git a/features/xmtp/xmtp-consent/xmtp-consent.ts b/features/xmtp/xmtp-consent/xmtp-consent.ts index 5db6c32f..1ee02fbc 100644 --- a/features/xmtp/xmtp-consent/xmtp-consent.ts +++ b/features/xmtp/xmtp-consent/xmtp-consent.ts @@ -9,59 +9,42 @@ export async function xmtpInboxIdCanMessageEthAddress(args: { ethAddress: IEthereumAddress }) { const { inboxId, ethAddress } = args + try { + const client = await getXmtpClientByInboxId({ + inboxId, + }) - const client = await getXmtpClientByInboxId({ - inboxId, - }) - - const canMessageResult = await wrapXmtpCallWithDuration("canMessage", () => - client.canMessage([{ kind: "ETHEREUM", identifier: ethAddress }]), - ) + const canMessageResult = await wrapXmtpCallWithDuration("canMessage", () => + client.canMessage([{ kind: "ETHEREUM", identifier: ethAddress }]), + ) - return canMessageResult[ethAddress.toLowerCase()] + return canMessageResult[ethAddress.toLowerCase()] + } catch (error) { + throw new XMTPError({ + error, + additionalMessage: "failed to check if inbox can message eth address", + }) + } } -// export const updateConsentForAddressesForAccount = async (args: { -// account: string -// addresses: string[] -// consent: ConsentState -// }) => { -// const { account, addresses, consent } = args - -// const client = await getXmtpClientByEthAddress({ -// ethAddress: account, -// }) - -// if (!client) { -// throw new Error("Client not found") -// } - -// const start = new Date().getTime() - -// if (consent === "allowed") { -// for (const address of addresses) { -// await client.preferences.setConsentState({ -// value: address, -// entryType: "address", -// state: "allowed", -// }) -// } -// } else if (consent === "denied") { -// for (const address of addresses) { -// await client.preferences.setConsentState({ -// value: address, -// entryType: "address", -// state: "denied", -// }) -// } -// } else { -// throw new Error(`Invalid consent type: ${consent}`) -// } +export async function getXmtpConsentStateForInboxId(args: { + clientInboxId: IXmtpInboxId + inboxIdToCheck: IXmtpInboxId +}) { + const { clientInboxId, inboxIdToCheck } = args -// const end = new Date().getTime() -// `[XMTPRN Contacts] Consented to addresses on protocol in ${(end - start) / 1000} sec`, -// ) -// } + try { + const client = await getXmtpClientByInboxId({ + inboxId: clientInboxId, + }) + return client.preferences.inboxIdConsentState(inboxIdToCheck) + } catch (error) { + throw new XMTPError({ + error, + additionalMessage: "failed to get XMTP consent state for inboxId", + }) + } +} export async function setXmtpConsentStateForInboxId(args: { peerInboxId: IXmtpInboxId @@ -90,21 +73,21 @@ export async function setXmtpConsentStateForInboxId(args: { } } -export const updateXmtpConsentForGroupsForInbox = async (args: { - groupIds: IXmtpConversationId[] +export const updateXmtpConsentForConversationForInbox = async (args: { + conversationIds: IXmtpConversationId[] consent: IXmtpConsentState clientInboxId: IXmtpInboxId }) => { - const { clientInboxId, groupIds, consent } = args + const { clientInboxId, conversationIds, consent } = args try { const client = await getXmtpClientByInboxId({ inboxId: clientInboxId, }) - for (const groupId of groupIds) { - await wrapXmtpCallWithDuration("setConsentState (group)", () => + for (const conversationId of conversationIds) { + await wrapXmtpCallWithDuration("setConsentState (conversation)", () => client.preferences.setConsentState({ - value: groupId, + value: conversationId, entryType: "conversation_id", state: consent, }), @@ -113,7 +96,7 @@ export const updateXmtpConsentForGroupsForInbox = async (args: { } catch (error) { throw new XMTPError({ error, - additionalMessage: "Failed to update consent for groups", + additionalMessage: "Failed to update consent for conversations", }) } } diff --git a/features/xmtp/xmtp-conversations/xmtp-conversations-sync.ts b/features/xmtp/xmtp-conversations/xmtp-conversations-sync.ts index 986b85e7..deac6712 100644 --- a/features/xmtp/xmtp-conversations/xmtp-conversations-sync.ts +++ b/features/xmtp/xmtp-conversations/xmtp-conversations-sync.ts @@ -100,10 +100,10 @@ export async function syncOneXmtpConversation(args: { export async function syncAllXmtpConversations(args: { clientInboxId: IXmtpInboxId - consentStates?: ConsentState[] + consentStates: ConsentState[] caller: string }) { - const { clientInboxId, consentStates = ["allowed", "unknown", "denied"], caller } = args + const { clientInboxId, consentStates, caller } = args const existingSyncPromise = syncAllConversationsPromisesCache.get(clientInboxId) if (existingSyncPromise) { diff --git a/features/xmtp/xmtp-disappearing-messages/xmtp-disappearing-messages.ts b/features/xmtp/xmtp-disappearing-messages/xmtp-disappearing-messages.ts index 7d13419a..5ea4fb16 100644 --- a/features/xmtp/xmtp-disappearing-messages/xmtp-disappearing-messages.ts +++ b/features/xmtp/xmtp-disappearing-messages/xmtp-disappearing-messages.ts @@ -77,14 +77,17 @@ export async function updateXmtpDisappearingMessageSettings(args: { inboxId: clientInboxId, }) - await wrapXmtpCallWithDuration("updateDisappearingMessageSettings", async () => { - return updateDisappearingMessageSettings( - client.installationId, - conversationId, - getTodayNs(), - retentionDurationInNs, - ) - }) + await wrapXmtpCallWithDuration( + `updateDisappearingMessageSettings ${conversationId} ${retentionDurationInNs}`, + async () => { + return updateDisappearingMessageSettings( + client.installationId, + conversationId, + getTodayNs(), + retentionDurationInNs, + ) + }, + ) } catch (error) { throw new XMTPError({ error, diff --git a/navigation/app-navigator.tsx b/navigation/app-navigator.tsx index c30d1ec6..26caf952 100644 --- a/navigation/app-navigator.tsx +++ b/navigation/app-navigator.tsx @@ -4,11 +4,11 @@ import * as Linking from "expo-linking" import React, { memo, useCallback } from "react" import { config } from "@/config" import { AppSettingsScreen } from "@/features/app-settings/app-settings.screen" +import { ArchivedConversationsScreen } from "@/features/archived-conversations/archived-conversations.screen" import { AuthOnboardingContactCardImportInfoScreen } from "@/features/auth-onboarding/screens/auth-onboarding-contact-card-import-info.screen" import { AuthOnboardingScreen } from "@/features/auth-onboarding/screens/auth-onboarding.screen" import { useAuthenticationStore } from "@/features/authentication/authentication.store" import { useHydrateAuth } from "@/features/authentication/hydrate-auth" -import { BlockedConversationsScreen } from "@/features/blocked-conversations/blocked-conversations.screen" import { ConversationScreen } from "@/features/conversation/conversation-chat/conversation.screen" import { ConversationListScreen } from "@/features/conversation/conversation-list/conversation-list.screen" import { ConversationRequestsListScreen } from "@/features/conversation/conversation-requests-list/conversation-requests-list.screen" @@ -207,7 +207,7 @@ function renderSignedInScreens(theme: ITheme) { // Fade animation when transitioning to authenticated state options={{ animation: "fade" }} /> - + { export function getCurrentRoute() { return navigationRef.getCurrentRoute() } + +export function getCurrentRouteParams(): + | NavigationParamList[T] + | undefined { + const currentRoute = navigationRef.getCurrentRoute() + if (!currentRoute) return undefined + return currentRoute.params as NavigationParamList[T] +} diff --git a/stores/app-state-store/app-state-store.service.ts b/stores/app-state-store/app-state-store.service.ts index 56c30244..0162e465 100644 --- a/stores/app-state-store/app-state-store.service.ts +++ b/stores/app-state-store/app-state-store.service.ts @@ -1,13 +1,10 @@ import { focusManager as reactQueryFocusManager } from "@tanstack/react-query" import { useEffect } from "react" import { AppStateStatus } from "react-native" +import { useAuthenticationStore } from "@/features/authentication/authentication.store" import { getAllSenders, getCurrentSender } from "@/features/authentication/multi-inbox.store" -import { - getAllowedConsentConversationsQueryData, - invalidateAllowedConsentConversationsQuery, -} from "@/features/conversation/conversation-list/conversations-allowed-consent.query" +import { invalidateAllowedConsentConversationsQuery } from "@/features/conversation/conversation-list/conversations-allowed-consent.query" import { invalidateUnknownConsentConversationsQuery } from "@/features/conversation/conversation-requests-list/conversations-unknown-consent.query" -import { invalidateConversationQuery } from "@/features/conversation/queries/conversation.query" import { fetchOrRefetchNotificationsPermissions } from "@/features/notifications/notifications-permissions.query" import { registerPushNotifications } from "@/features/notifications/notifications-register" import { startStreaming, stopStreaming } from "@/features/streams/streams" @@ -108,6 +105,14 @@ export function startListeningToAppStateStore() { const senders = getAllSenders() const currentSender = getCurrentSender() + const authStatus = useAuthenticationStore.getState().status + const isSignedIn = authStatus === "signedIn" + + // For now all actions below requires to be signed in + if (!isSignedIn) { + return + } + if (isOpenFromClosed || isOpenFromBackground) { // Tell react query we're now on "window focused" state reactQueryFocusManager.setFocused(true) diff --git a/utils/react-query/react-query.helpers.ts b/utils/react-query/react-query.helpers.ts index 46fa4f3e..4b26e115 100644 --- a/utils/react-query/react-query.helpers.ts +++ b/utils/react-query/react-query.helpers.ts @@ -96,7 +96,7 @@ export async function fetchWithoutDuplicatesQuery(args: QueryOptions): Pro * Prefetches a query if it's enabled */ export function prefetchReactQueryBetter(args: UseQueryOptions) { - if (!args.enabled) { + if ("enabled" in args && !args.enabled) { queryLogger.debug(`Skipping prefetch for ${args.queryKey} because it's disabled`) return } @@ -107,7 +107,7 @@ export function prefetchReactQueryBetter(args: UseQueryOptions) { * Refetches a query if it's enabled */ export function refetchReactQueryBetter(args: UseQueryOptions) { - if (!args.enabled) { + if ("enabled" in args && !args.enabled) { queryLogger.debug(`Skipping refetch for ${args.queryKey} because it's disabled`) return Promise.resolve() } @@ -115,7 +115,7 @@ export function refetchReactQueryBetter(args: UseQueryOptions) { } export function ensureQueryDataBetter(args: UseQueryOptions) { - if (!args.enabled) { + if ("enabled" in args && !args.enabled) { queryLogger.debug(`Skipping ensureQueryData for ${args.queryKey} because it's disabled`) throw new ReactQueryError({ error: new Error(`Can't call ensureQueryData because query ${args.queryKey} is disabled`), diff --git a/utils/react-query/react-query.utils.ts b/utils/react-query/react-query.utils.ts index d1682bb8..4b393875 100644 --- a/utils/react-query/react-query.utils.ts +++ b/utils/react-query/react-query.utils.ts @@ -2,15 +2,17 @@ import { queryLogger } from "@/utils/logger/logger" import { reactQueryClient } from "@/utils/react-query/react-query.client" import { reactQueryPersistingStorage } from "../storage/storages" -// Doing this because we onced added caller to the query key and we should never do that. -type DisallowedKey = "caller" - -export function getReactQueryKey>( - args: { baseStr: string } & { [K in Exclude]?: string }, +export function getReactQueryKey( + args: ArgType & + // We don't want people to pass in a "caller" key. + ("caller" extends keyof ArgType ? never : {}), ): string[] { - const { baseStr, ...rest } = args + const { baseStr, ...rest } = args as { baseStr: string; [key: string]: unknown } + + // Make sure caller isn't in the rest of the keys. const filteredEntries = Object.entries(rest).filter(([key]) => key !== "caller") - return [baseStr, ...filteredEntries.map(([key, value]) => `${key}: ${value}`)] + + return [baseStr, ...filteredEntries.map(([key, value]) => `${key}: ${String(value)}`)] } export function clearReacyQueryQueriesAndCache() { diff --git a/utils/time.utils.ts b/utils/time.utils.ts index 94488a48..d467ae71 100644 --- a/utils/time.utils.ts +++ b/utils/time.utils.ts @@ -38,3 +38,44 @@ export const TimeUtils = { hours: createHours, minutes: createMinutes, } as const + +// 1000 -> "1 second" +// 60000 -> "1 minute" +// 3600000 -> "1 hour" +// 86400000 -> "1 day" +// 604800000 -> "1 week" +// 2592000000 -> "1 month" +// 31536000000 -> "1 year" +export function getHumanReadableTimeFromMs(ms: number) { + if (ms < 1000) { + return "less than a second" + } + + const seconds = Math.floor(ms / 1000) + const minutes = Math.floor(seconds / 60) + const hours = Math.floor(minutes / 60) + const days = Math.floor(hours / 24) + const weeks = Math.floor(days / 7) + const months = Math.floor(days / 30) + const years = Math.floor(days / 365) + + if (years > 0) { + return `${years} ${years === 1 ? "year" : "years"}` + } + if (months > 0) { + return `${months} ${months === 1 ? "month" : "months"}` + } + if (weeks > 0) { + return `${weeks} ${weeks === 1 ? "week" : "weeks"}` + } + if (days > 0) { + return `${days} ${days === 1 ? "day" : "days"}` + } + if (hours > 0) { + return `${hours} ${hours === 1 ? "hour" : "hours"}` + } + if (minutes > 0) { + return `${minutes} ${minutes === 1 ? "minute" : "minutes"}` + } + return `${seconds} ${seconds === 1 ? "second" : "seconds"}` +}