diff --git a/src/components/dms/ChatInvite/Root.tsx b/src/components/dms/ChatInvite/Root.tsx index a2cfc558ea..025cae7a2e 100644 --- a/src/components/dms/ChatInvite/Root.tsx +++ b/src/components/dms/ChatInvite/Root.tsx @@ -10,7 +10,7 @@ import {type ButtonColor} from '#/components/Button' import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRightIcon} from '#/components/icons/Arrow' import {ArrowBoxRight_Stroke2_Corner3_Rounded as JoinIcon} from '#/components/icons/ArrowBoxRight' import {ChainLink_Stroke2_Corner0_Rounded as LinkIcon} from '#/components/icons/ChainLink' -import {CheckThick_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' +import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' import {type Props as SVGIconProps} from '#/components/icons/common' import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand' import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' diff --git a/src/state/queries/resolve-link.ts b/src/state/queries/resolve-link.ts index a6b21e0bcc..2ccd0af863 100644 --- a/src/state/queries/resolve-link.ts +++ b/src/state/queries/resolve-link.ts @@ -1,5 +1,5 @@ -import {type BskyAgent} from '@atproto/api' -import {type QueryClient, useQuery} from '@tanstack/react-query' +import {type AtpAgent} from '@atproto/api' +import {type QueryClient, queryOptions, useQuery} from '@tanstack/react-query' import {type ResolvedLink, resolveGif, resolveLink} from '#/lib/api/resolve' import {STALE} from '#/state/queries/index' @@ -12,29 +12,24 @@ export const RQKEY_LINK = (url: string) => [RQKEY_LINK_ROOT, url] export const RQKEY_GIF_ROOT = 'resolve-gif' export const RQKEY_GIF = (url: string) => [RQKEY_GIF_ROOT, url] -export function useResolveLinkQuery(url: string) { - const agent = useAgent() - - return useQuery({ +export function resolveLinkQueryOptions(agent: AtpAgent, url: string) { + return queryOptions({ staleTime: STALE.HOURS.ONE, queryKey: RQKEY_LINK(url), - queryFn: async () => { - return await resolveLink(agent, url) - }, + queryFn: () => resolveLink(agent, url), }) } + +export function useResolveLinkQuery(url: string) { + const agent = useAgent() + return useQuery(resolveLinkQueryOptions(agent, url)) +} export function fetchResolveLinkQuery( queryClient: QueryClient, - agent: BskyAgent, + agent: AtpAgent, url: string, ) { - return queryClient.fetchQuery({ - staleTime: STALE.HOURS.ONE, - queryKey: RQKEY_LINK(url), - queryFn: async () => { - return await resolveLink(agent, url) - }, - }) + return queryClient.fetchQuery(resolveLinkQueryOptions(agent, url)) } export function precacheResolveLinkQuery( queryClient: QueryClient, @@ -56,7 +51,7 @@ export function useResolveGifQuery(gif: Gif) { } export function fetchResolveGifQuery( queryClient: QueryClient, - agent: BskyAgent, + agent: AtpAgent, gif: Gif, ) { return queryClient.fetchQuery({ diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index e130472c72..ab8f4d9cc5 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -57,7 +57,7 @@ import { import {plural} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' -import {useQueryClient} from '@tanstack/react-query' +import {useQueries, useQueryClient} from '@tanstack/react-query' import * as apilib from '#/lib/api/index' import {EmbeddingDisabledError} from '#/lib/api/resolve' @@ -95,6 +95,7 @@ import { } from '#/state/preferences/languages' import {usePreferencesQuery} from '#/state/queries/preferences' import {useProfileQuery} from '#/state/queries/profile' +import {resolveLinkQueryOptions} from '#/state/queries/resolve-link' import {useAgent, useSession} from '#/state/session' import {useComposerControls} from '#/state/shell/composer' import {type ComposerOpts, type OnPostSuccessData} from '#/state/shell/composer' @@ -865,8 +866,25 @@ export const ComposePost = ({ } }, [thread, requireAltTextEnabled, l]) + // Subscribe to the resolve-link cache for any link URIs in the thread so we + // can detect chat invites that resolved to no preview (revoked/expired) and + // block publishing - otherwise the post would go out without the embed. + const linkUris = thread.posts + .filter(post => post.embed.link) + .map(post => post.embed.link!.uri) + const linkQueries = useQueries({ + queries: linkUris.map(uri => ({ + ...resolveLinkQueryOptions(agent, uri), + enabled: false, + })), + }) + const hasUnavailableChatInvite = linkQueries.some( + q => q.data?.type === 'chat-invite' && !q.data.view, + ) + const canPost = !missingAltError && + !hasUnavailableChatInvite && thread.posts.some(post => !isEmptyPost(post)) && thread.posts.every( post =>