phase 4: migrate account flows, trending queries, notifications, and report subjects off the bridge
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import {useMemo} from 'react'
|
||||
import {setPersonalDetails} from '@bsky.app/sdk'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {restrictChatSettings} from '#/state/queries/messages/restrictChatSettings'
|
||||
import {preferencesQueryKey} from '#/state/queries/preferences'
|
||||
import {useAgent, usePdsClient, useSession} from '#/state/session'
|
||||
import {usePdsClient, useSession} from '#/state/session'
|
||||
import {usePatchAgeAssuranceOtherRequiredData} from '#/ageAssurance'
|
||||
import {isUnderAge} from '#/ageAssurance/util'
|
||||
import {IS_DEV} from '#/env'
|
||||
@@ -54,14 +55,14 @@ export function useIsBirthdateUpdateAllowed() {
|
||||
|
||||
export function useBirthdateMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const {currentAccount} = useSession()
|
||||
const pdsClient = usePdsClient()
|
||||
const patchOtherRequiredData = usePatchAgeAssuranceOtherRequiredData()
|
||||
|
||||
return useMutation<void, unknown, {birthDate: Date}>({
|
||||
mutationFn: async ({birthDate}: {birthDate: Date}) => {
|
||||
const bday = birthDate.toISOString()
|
||||
await agent.setPersonalDetails({birthDate: bday})
|
||||
await pdsClient.call(setPersonalDetails, {birthDate})
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -80,7 +81,9 @@ export function useBirthdateMutation() {
|
||||
* birthdate, which may change the user's age assurance access level.
|
||||
*/
|
||||
void patchOtherRequiredData({birthdate: bday})
|
||||
snoozeBirthdateUpdateAllowedForDid(agent.sessionManager.did!)
|
||||
if (currentAccount) {
|
||||
snoozeBirthdateUpdateAllowedForDid(currentAccount.did)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Vendored
+7
-5
@@ -5,9 +5,11 @@ import {
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {type AtUriString} from '@atproto/syntax'
|
||||
|
||||
import * as persisted from '#/state/persisted'
|
||||
import {useAgent, useSession} from '../session'
|
||||
import {app} from '#/lexicons'
|
||||
import {usePdsClient, useSession} from '../session'
|
||||
|
||||
type StateContext = Map<string, boolean>
|
||||
type SetStateContext = (uri: string, value: boolean) => void
|
||||
@@ -56,7 +58,7 @@ export function useSetThreadMute() {
|
||||
}
|
||||
|
||||
function useMigrateMutes(setThreadMute: SetStateContext) {
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -87,8 +89,8 @@ function useMigrateMutes(setThreadMute: SetStateContext) {
|
||||
|
||||
setThreadMute(root, true)
|
||||
|
||||
await agent.api.app.bsky.graph
|
||||
.muteThread({root})
|
||||
await pdsClient
|
||||
.call(app.bsky.graph.muteThread, {root: root as AtUriString})
|
||||
// not a big deal if this fails, since the post might have been deleted
|
||||
.catch(console.error)
|
||||
}
|
||||
@@ -100,5 +102,5 @@ function useMigrateMutes(setThreadMute: SetStateContext) {
|
||||
cancelled = true
|
||||
}
|
||||
}
|
||||
}, [agent, currentAccount, setThreadMute])
|
||||
}, [pdsClient, currentAccount, setThreadMute])
|
||||
}
|
||||
|
||||
+18
-12
@@ -7,6 +7,7 @@ import {
|
||||
useRef,
|
||||
} from 'react'
|
||||
import {AppState, type AppStateStatus} from 'react-native'
|
||||
import {type Service} from '@atproto/lex-client'
|
||||
import {type AtUriString} from '@atproto/syntax'
|
||||
import throttle from 'lodash.throttle'
|
||||
|
||||
@@ -22,8 +23,8 @@ import {
|
||||
} from '#/state/queries/post-feed'
|
||||
import {getItemsForFeedback} from '#/view/com/posts/PostFeed'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {type app} from '#/lexicons'
|
||||
import {useAgent} from './session'
|
||||
import {app} from '#/lexicons'
|
||||
import {useAppviewClient} from './session'
|
||||
|
||||
export const FEEDBACK_FEEDS = [...PROD_FEEDS, ...STAGING_FEEDS]
|
||||
|
||||
@@ -66,7 +67,7 @@ export function useFeedFeedback(
|
||||
) {
|
||||
const ax = useAnalytics()
|
||||
const logger = ax.logger.useChild(ax.logger.Context.FeedFeedback)
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
|
||||
const feed =
|
||||
!!feedSourceInfo && isFeedSourceFeedInfo(feedSourceInfo)
|
||||
@@ -151,15 +152,20 @@ export function useFeedFeedback(
|
||||
return
|
||||
}
|
||||
|
||||
// Send to the feed
|
||||
agent.app.bsky.feed
|
||||
.sendInteractions(
|
||||
{interactions: interactionsToSend, feed: feed?.uri},
|
||||
/*
|
||||
* Send to the feed generator via a per-call `service` option, which
|
||||
* lex-client turns into the `atproto-proxy` header. This overrides the
|
||||
* appview client's own service target for this one call.
|
||||
*/
|
||||
appviewClient
|
||||
.call(
|
||||
app.bsky.feed.sendInteractions,
|
||||
{
|
||||
encoding: 'application/json',
|
||||
headers: {
|
||||
'atproto-proxy': `${proxyDid}#bsky_fg`,
|
||||
},
|
||||
interactions: interactionsToSend,
|
||||
feed: feed?.uri as AtUriString | undefined,
|
||||
},
|
||||
{
|
||||
service: `${proxyDid}#bsky_fg` as Service,
|
||||
},
|
||||
)
|
||||
.catch(() => {}) // ignore upstream errors
|
||||
@@ -173,7 +179,7 @@ export function useFeedFeedback(
|
||||
)
|
||||
throttledFlushAggregatedStats()
|
||||
logger.debug('flushed')
|
||||
}, [agent, throttledFlushAggregatedStats, proxyDid, enabled, feed])
|
||||
}, [appviewClient, throttledFlushAggregatedStats, proxyDid, enabled, feed])
|
||||
|
||||
const sendToFeed = useMemo(
|
||||
() =>
|
||||
|
||||
@@ -595,42 +595,25 @@ export class Convo {
|
||||
this.convo = parseConvoView(convo, this.senderUserDid) ?? this.convo
|
||||
if (this.convo) {
|
||||
for (const member of this.convo.members) {
|
||||
// `this.convo` comes from `parseConvoView` in the still-old-typed
|
||||
// `#/components/dms/util` (migrates in a later task); bridge its member
|
||||
// shape to the lexicon `ProfileViewBasic` we store. TODO(phase4): drop
|
||||
// toLex once dms/util migrates.
|
||||
this.relatedProfiles.set(
|
||||
member.did,
|
||||
bsky.toLex<chat.bsky.actor.defs.ProfileViewBasic>(member),
|
||||
)
|
||||
this.relatedProfiles.set(member.did, member)
|
||||
}
|
||||
}
|
||||
this.applyProfileShadows()
|
||||
}
|
||||
|
||||
/*
|
||||
* The partial merges into `this.convo.view` and is re-parsed by the
|
||||
* still-old-typed `parseConvoView` (`#/components/dms/util`, migrates in a
|
||||
* later task), and its callers build it from old-typed `this.convo.details` /
|
||||
* members. So this boundary stays in the old view world - typing the param
|
||||
* off `ConvoWithDetails['view']` keeps it internally consistent without a
|
||||
* per-call `toLex`. TODO(phase4): flip to `chat.bsky.convo.defs.ConvoView`
|
||||
* once dms/util migrates.
|
||||
* The partial merges into `this.convo.view` and is re-parsed by
|
||||
* `parseConvoView`, and its callers build it from `this.convo.details` /
|
||||
* members. The stored view is the lexicon `ConvoView`, so the partial is
|
||||
* typed off it directly.
|
||||
*/
|
||||
private updateConvo(convo: Partial<ConvoWithDetails['view']>) {
|
||||
private updateConvo(convo: Partial<chat.bsky.convo.defs.ConvoView>) {
|
||||
if (this.convo) {
|
||||
this.convo =
|
||||
parseConvoView({...this.convo.view, ...convo}, this.senderUserDid) ??
|
||||
this.convo
|
||||
for (const member of this.convo.members) {
|
||||
// `this.convo` comes from `parseConvoView` in the still-old-typed
|
||||
// `#/components/dms/util` (migrates in a later task); bridge its member
|
||||
// shape to the lexicon `ProfileViewBasic` we store. TODO(phase4): drop
|
||||
// toLex once dms/util migrates.
|
||||
this.relatedProfiles.set(
|
||||
member.did,
|
||||
bsky.toLex<chat.bsky.actor.defs.ProfileViewBasic>(member),
|
||||
)
|
||||
this.relatedProfiles.set(member.did, member)
|
||||
}
|
||||
this.applyProfileShadows()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {type AtIdentifierString} from '@atproto/syntax'
|
||||
import {type QueryClient, useInfiniteQuery} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
export const RQKEY_ROOT = 'actor-starter-packs'
|
||||
export const RQKEY_WITH_MEMBERSHIP_ROOT = 'actor-starter-packs-with-membership'
|
||||
@@ -17,17 +19,16 @@ export function useActorStarterPacksQuery({
|
||||
did?: string
|
||||
enabled?: boolean
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
|
||||
return useInfiniteQuery({
|
||||
queryKey: RQKEY(did),
|
||||
queryFn: async ({pageParam}: {pageParam?: string}) => {
|
||||
const res = await agent.app.bsky.graph.getActorStarterPacks({
|
||||
actor: did!,
|
||||
return await appviewClient.call(app.bsky.graph.getActorStarterPacks, {
|
||||
actor: did! as AtIdentifierString,
|
||||
limit: 10,
|
||||
cursor: pageParam,
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
enabled: Boolean(did) && enabled,
|
||||
initialPageParam: undefined,
|
||||
@@ -42,17 +43,19 @@ export function useActorStarterPacksWithMembershipsQuery({
|
||||
did?: string
|
||||
enabled?: boolean
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
|
||||
return useInfiniteQuery({
|
||||
queryKey: RQKEY_WITH_MEMBERSHIP(did),
|
||||
queryFn: async ({pageParam}: {pageParam?: string}) => {
|
||||
const res = await agent.app.bsky.graph.getStarterPacksWithMembership({
|
||||
actor: did!,
|
||||
limit: 10,
|
||||
cursor: pageParam,
|
||||
})
|
||||
return res.data
|
||||
return await appviewClient.call(
|
||||
app.bsky.graph.getStarterPacksWithMembership,
|
||||
{
|
||||
actor: did! as AtIdentifierString,
|
||||
limit: 10,
|
||||
cursor: pageParam,
|
||||
},
|
||||
)
|
||||
},
|
||||
enabled: Boolean(did) && enabled,
|
||||
initialPageParam: undefined,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import {Client} from '@atproto/lex-client'
|
||||
import {type DatetimeString, type HandleString} from '@atproto/syntax'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {
|
||||
@@ -8,20 +10,8 @@ import {
|
||||
import {useDebouncedValue} from '#/lib/hooks/useDebouncedValue'
|
||||
import {createFullHandle} from '#/lib/strings/handles'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {Agent} from '../session/agent'
|
||||
|
||||
/*
|
||||
* `com.atproto.temp.checkHandleAvailability` is an entryway-only endpoint that
|
||||
* isn't generated into `#/lexicons`, so we describe its result union locally.
|
||||
* The response is discriminated by `$type`; we narrow against these shapes
|
||||
* rather than a branded lexicon guard.
|
||||
*/
|
||||
type CheckHandleAvailabilityResult =
|
||||
| {$type: 'com.atproto.temp.checkHandleAvailability#resultAvailable'}
|
||||
| {
|
||||
$type: 'com.atproto.temp.checkHandleAvailability#resultUnavailable'
|
||||
suggestions: {handle: string; method: string}[]
|
||||
}
|
||||
import {com} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
export const RQKEY_handleAvailability = (
|
||||
handle: string,
|
||||
@@ -90,24 +80,28 @@ export async function checkHandleAvailability(
|
||||
},
|
||||
) {
|
||||
if (serviceDid === BSKY_SERVICE_DID) {
|
||||
const agent = new Agent(null, {service: BSKY_SERVICE})
|
||||
// entryway has a special API for handle availability
|
||||
const {data} = await agent.com.atproto.temp.checkHandleAvailability({
|
||||
handle,
|
||||
birthDate,
|
||||
const client = new Client({service: BSKY_SERVICE})
|
||||
const data = await client.call(com.atproto.temp.checkHandleAvailability, {
|
||||
handle: handle as HandleString,
|
||||
birthDate: birthDate as DatetimeString | undefined,
|
||||
email,
|
||||
})
|
||||
|
||||
const result = data.result as CheckHandleAvailabilityResult
|
||||
const result = data.result
|
||||
|
||||
if (
|
||||
result.$type ===
|
||||
'com.atproto.temp.checkHandleAvailability#resultAvailable'
|
||||
bsky.isType(
|
||||
com.atproto.temp.checkHandleAvailability.resultAvailable,
|
||||
result,
|
||||
)
|
||||
) {
|
||||
return {available: true} as const
|
||||
} else if (
|
||||
result.$type ===
|
||||
'com.atproto.temp.checkHandleAvailability#resultUnavailable'
|
||||
bsky.isType(
|
||||
com.atproto.temp.checkHandleAvailability.resultUnavailable,
|
||||
result,
|
||||
)
|
||||
) {
|
||||
return {
|
||||
available: false,
|
||||
@@ -120,13 +114,13 @@ export async function checkHandleAvailability(
|
||||
}
|
||||
} else {
|
||||
// 3rd party PDSes won't have this API so just try and resolve the handle
|
||||
const agent = new Agent(null, {service: PUBLIC_BSKY_SERVICE})
|
||||
const client = new Client({service: PUBLIC_BSKY_SERVICE})
|
||||
try {
|
||||
const res = await agent.resolveHandle({
|
||||
handle,
|
||||
const res = await client.call(com.atproto.identity.resolveHandle, {
|
||||
handle: handle as HandleString,
|
||||
})
|
||||
|
||||
if (res.data.did) {
|
||||
if (res.did) {
|
||||
return {available: false} as const
|
||||
}
|
||||
} catch {}
|
||||
|
||||
+21
-11
@@ -1,8 +1,10 @@
|
||||
import {useCallback} from 'react'
|
||||
import {type AtIdentifierString, type HandleString} from '@atproto/syntax'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient, usePdsClient} from '#/state/session'
|
||||
import {app, com} from '#/lexicons'
|
||||
|
||||
const handleQueryKeyRoot = 'handle'
|
||||
const fetchHandleQueryKey = (handleOrDid: string) => [
|
||||
@@ -14,7 +16,7 @@ const fetchDidQueryKey = (handleOrDid: string) => [didQueryKeyRoot, handleOrDid]
|
||||
|
||||
export function useFetchHandle() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
|
||||
return useCallback(
|
||||
async (handleOrDid: string) => {
|
||||
@@ -22,13 +24,16 @@ export function useFetchHandle() {
|
||||
const res = await queryClient.fetchQuery({
|
||||
staleTime: STALE.MINUTES.FIVE,
|
||||
queryKey: fetchHandleQueryKey(handleOrDid),
|
||||
queryFn: () => agent.getProfile({actor: handleOrDid}),
|
||||
queryFn: () =>
|
||||
appviewClient.call(app.bsky.actor.getProfile, {
|
||||
actor: handleOrDid as AtIdentifierString,
|
||||
}),
|
||||
})
|
||||
return res.data.handle
|
||||
return res.handle
|
||||
}
|
||||
return handleOrDid
|
||||
},
|
||||
[queryClient, agent],
|
||||
[queryClient, appviewClient],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -36,11 +41,13 @@ export function useUpdateHandleMutation(opts?: {
|
||||
onSuccess?: (handle: string) => void
|
||||
}) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({handle}: {handle: string}) => {
|
||||
await agent.updateHandle({handle})
|
||||
await pdsClient.call(com.atproto.identity.updateHandle, {
|
||||
handle: handle as HandleString,
|
||||
})
|
||||
},
|
||||
onSuccess(_data, variables) {
|
||||
opts?.onSuccess?.(variables.handle)
|
||||
@@ -53,7 +60,7 @@ export function useUpdateHandleMutation(opts?: {
|
||||
|
||||
export function useFetchDid() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
|
||||
return useCallback(
|
||||
async (handleOrDid: string) => {
|
||||
@@ -63,13 +70,16 @@ export function useFetchDid() {
|
||||
queryFn: async () => {
|
||||
let identifier = handleOrDid
|
||||
if (!identifier.startsWith('did:')) {
|
||||
const res = await agent.resolveHandle({handle: identifier})
|
||||
identifier = res.data.did
|
||||
const res = await appviewClient.call(
|
||||
com.atproto.identity.resolveHandle,
|
||||
{handle: identifier as HandleString},
|
||||
)
|
||||
identifier = res.did
|
||||
}
|
||||
return identifier
|
||||
},
|
||||
})
|
||||
},
|
||||
[queryClient, agent],
|
||||
[queryClient, appviewClient],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
import {type AtUriString} from '@atproto/syntax'
|
||||
import {deleteLike, like} from '@bsky.app/sdk'
|
||||
import {useMutation} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent} from '#/state/session'
|
||||
import {usePdsClient} from '#/state/session'
|
||||
|
||||
export function useLikeMutation() {
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
return useMutation({
|
||||
mutationFn: async ({uri, cid}: {uri: string; cid: string}) => {
|
||||
const res = await agent.like(uri, cid)
|
||||
const res = await pdsClient.call(like, {
|
||||
uri: uri as AtUriString,
|
||||
cid,
|
||||
})
|
||||
return {uri: res.uri}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useUnlikeMutation() {
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
return useMutation({
|
||||
mutationFn: async ({uri}: {uri: string}) => {
|
||||
await agent.deleteLike(uri)
|
||||
await pdsClient.call(deleteLike, uri as AtUriString)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -745,17 +745,18 @@ export function ListConvosProviderInner({
|
||||
/*
|
||||
* `log.message` can also be a deleted-message view per the
|
||||
* log union, which the strict MessageAndReactionView type
|
||||
* rejects - the old types absorbed this via the open-union
|
||||
* catch-all. Keep the pre-migration runtime behavior (always
|
||||
* store the view we got) and assert the cache type.
|
||||
* rejects - the raw ConvoView cache type only admits a live
|
||||
* MessageView here. Keep the pre-migration runtime behavior
|
||||
* (always store the view we got) and assert into the cache
|
||||
* type; the runtime value is unchanged.
|
||||
*/
|
||||
lastReaction: bsky.toLex<
|
||||
NonNullable<chat.bsky.convo.defs.ConvoView['lastReaction']>
|
||||
>({
|
||||
lastReaction: {
|
||||
$type: 'chat.bsky.convo.defs#messageAndReactionView',
|
||||
reaction: log.reaction,
|
||||
message: log.message,
|
||||
}),
|
||||
} as NonNullable<
|
||||
chat.bsky.convo.defs.ConvoView['lastReaction']
|
||||
>,
|
||||
rev: log.rev,
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {removeNuxs, upsertNux} from '@bsky.app/sdk'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {type AppNux, type Nux} from '#/state/queries/nuxs/definitions'
|
||||
@@ -6,7 +7,7 @@ import {
|
||||
preferencesQueryKey,
|
||||
usePreferencesQuery,
|
||||
} from '#/state/queries/preferences'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {usePdsClient} from '#/state/session'
|
||||
|
||||
export {Nux} from '#/state/queries/nuxs/definitions'
|
||||
|
||||
@@ -42,11 +43,11 @@ export function useNuxs():
|
||||
|
||||
// if (__DEV__) {
|
||||
// const queryClient = useQueryClient()
|
||||
// const agent = useAgent()
|
||||
// const pdsClient = usePdsClient()
|
||||
|
||||
// // @ts-ignore
|
||||
// window.clearNux = async (ids: string[]) => {
|
||||
// await agent.bskyAppRemoveNuxs(ids)
|
||||
// await pdsClient.call(removeNuxs, ids)
|
||||
// // triggers a refetch
|
||||
// await queryClient.invalidateQueries({
|
||||
// queryKey: preferencesQueryKey,
|
||||
@@ -97,12 +98,12 @@ export function useNux<T extends Nux>(
|
||||
|
||||
export function useSaveNux() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
retry: 3,
|
||||
mutationFn: async (nux: AppNux) => {
|
||||
await agent.bskyAppUpsertNux(serializeAppNux(nux))
|
||||
await pdsClient.call(upsertNux, serializeAppNux(nux))
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -113,12 +114,12 @@ export function useSaveNux() {
|
||||
|
||||
export function useResetNuxs() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
retry: 3,
|
||||
mutationFn: async (ids: string[]) => {
|
||||
await agent.bskyAppRemoveNuxs(ids)
|
||||
await pdsClient.call(removeNuxs, ids)
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import {useState} from 'react'
|
||||
import {type DidDocument, getPdsEndpoint} from '@atproto/common-web'
|
||||
import {Client} from '@atproto/lex-client'
|
||||
import {type HandleString} from '@atproto/syntax'
|
||||
import {useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DEFAULT_SERVICE, PUBLIC_BSKY_SERVICE} from '#/lib/constants'
|
||||
@@ -7,7 +9,7 @@ import {useDebouncedValue} from '#/lib/hooks/useDebouncedValue'
|
||||
import {isNetworkError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {Agent} from '#/state/session/agent'
|
||||
import {com} from '#/lexicons'
|
||||
|
||||
const RQKEY_ROOT = 'pds-detection'
|
||||
export const RQKEY = (identifier: string) => [RQKEY_ROOT, identifier]
|
||||
@@ -147,16 +149,24 @@ export async function resolvePdsForIdentifier(
|
||||
identifier: string,
|
||||
): Promise<{did: string; pdsUrl: string | null} | null> {
|
||||
const norm = normalizeIdentifier(identifier)
|
||||
const agent = new Agent(null, {service: PUBLIC_BSKY_SERVICE})
|
||||
/*
|
||||
* Unauthenticated throwaway client pointed at the public appview -
|
||||
* resolveHandle is a public read.
|
||||
*/
|
||||
const client = new Client({service: PUBLIC_BSKY_SERVICE})
|
||||
try {
|
||||
let did: string
|
||||
if (norm.startsWith('did:')) {
|
||||
did = norm
|
||||
} else {
|
||||
const res = await withResolveTimeout(signal =>
|
||||
agent.resolveHandle({handle: norm}, {signal}),
|
||||
client.call(
|
||||
com.atproto.identity.resolveHandle,
|
||||
{handle: norm as HandleString},
|
||||
{signal},
|
||||
),
|
||||
)
|
||||
did = res.data.did
|
||||
did = res.did
|
||||
}
|
||||
logger.debug('pds-detection: resolved identifier to DID', {
|
||||
identifier: norm,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
type ServiceConfig = {
|
||||
checkEmailConfirmed: boolean
|
||||
@@ -13,18 +14,24 @@ type ServiceConfig = {
|
||||
}
|
||||
|
||||
export function useServiceConfigQuery() {
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
return useQuery<ServiceConfig>({
|
||||
refetchOnWindowFocus: true,
|
||||
staleTime: STALE.MINUTES.FIVE,
|
||||
queryKey: ['service-config'],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const {data} = await agent.api.app.bsky.unspecced.getConfig()
|
||||
const data = await appviewClient.call(app.bsky.unspecced.getConfig)
|
||||
return {
|
||||
checkEmailConfirmed: Boolean(data.checkEmailConfirmed),
|
||||
// @ts-expect-error not included in types atm
|
||||
topicsEnabled: Boolean(data.topicsEnabled),
|
||||
/*
|
||||
* `topicsEnabled` is served by the appview but is not (yet) part of
|
||||
* the getConfig lexicon schema, so read it through a narrow local
|
||||
* cast rather than the generated body type.
|
||||
*/
|
||||
topicsEnabled: Boolean(
|
||||
(data as {topicsEnabled?: boolean}).topicsEnabled,
|
||||
),
|
||||
liveNow: data.liveNow ?? [],
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -7,16 +7,15 @@ import {
|
||||
import {getContentLanguages} from '#/state/preferences/languages'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {type app} from '#/lexicons'
|
||||
import {toLex} from '#/types/bsky'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
export const DEFAULT_LIMIT = 15
|
||||
|
||||
export const createGetSuggestedFeedsQueryKey = () => ['suggested-feeds']
|
||||
|
||||
export function useGetSuggestedFeedsQuery({enabled}: {enabled?: boolean}) {
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const savedFeeds = preferences?.savedFeeds
|
||||
|
||||
@@ -26,7 +25,8 @@ export function useGetSuggestedFeedsQuery({enabled}: {enabled?: boolean}) {
|
||||
queryKey: createGetSuggestedFeedsQueryKey(),
|
||||
queryFn: async () => {
|
||||
const contentLangs = getContentLanguages().join(',')
|
||||
const {data} = await agent.app.bsky.unspecced.getSuggestedFeeds(
|
||||
const data = await appviewClient.call(
|
||||
app.bsky.unspecced.getSuggestedFeeds,
|
||||
{
|
||||
limit: DEFAULT_LIMIT,
|
||||
},
|
||||
@@ -38,17 +38,11 @@ export function useGetSuggestedFeedsQuery({enabled}: {enabled?: boolean}) {
|
||||
},
|
||||
)
|
||||
|
||||
/*
|
||||
* TODO(phase4): drop toLex once getSuggestedFeeds migrates off the bridge
|
||||
* agent (intentionally left on the bridge in Phase 3).
|
||||
*/
|
||||
return {
|
||||
feeds: toLex<app.bsky.feed.defs.GeneratorView[]>(
|
||||
data.feeds.filter(feed => {
|
||||
const isSaved = !!savedFeeds?.find(s => s.value === feed.uri)
|
||||
return !isSaved
|
||||
}),
|
||||
),
|
||||
feeds: data.feeds.filter(feed => {
|
||||
const isSaved = !!savedFeeds?.find(s => s.value === feed.uri)
|
||||
return !isSaved
|
||||
}),
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -5,9 +5,8 @@ import {logger} from '#/logger'
|
||||
import {getContentLanguages} from '#/state/preferences/languages'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {type app} from '#/lexicons'
|
||||
import {toLex} from '#/types/bsky'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
export type QueryProps = {
|
||||
category?: string | null
|
||||
@@ -28,7 +27,7 @@ export const createGetSuggestedOnboardingUsersQueryKey = (
|
||||
]
|
||||
|
||||
export function useGetSuggestedOnboardingUsersQuery(props: QueryProps) {
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
|
||||
return useQuery({
|
||||
@@ -40,7 +39,8 @@ export function useGetSuggestedOnboardingUsersQuery(props: QueryProps) {
|
||||
|
||||
const overrideInterests = props.overrideInterests.join(',')
|
||||
|
||||
const {data} = await agent.app.bsky.unspecced.getSuggestedOnboardingUsers(
|
||||
const data = await appviewClient.call(
|
||||
app.bsky.unspecced.getSuggestedOnboardingUsers,
|
||||
{
|
||||
category: props.category ?? undefined,
|
||||
limit: props.limit || 10,
|
||||
@@ -56,15 +56,7 @@ export function useGetSuggestedOnboardingUsersQuery(props: QueryProps) {
|
||||
if (!data.recIdStr) {
|
||||
logger.debug('getSuggestedOnboardingUsers response missing recIdStr')
|
||||
}
|
||||
/*
|
||||
* TODO(phase4): drop toLex once getSuggestedOnboardingUsers migrates off
|
||||
* the bridge agent (this unspecced endpoint is intentionally left on the
|
||||
* bridge in Phase 3, so it returns old `@atproto/api` view types).
|
||||
*/
|
||||
return toLex<{
|
||||
actors: app.bsky.actor.defs.ProfileView[]
|
||||
recId: string | undefined
|
||||
}>({...data, recId: data.recIdStr})
|
||||
return {...data, recId: data.recIdStr}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,9 +8,8 @@ import {logger} from '#/logger'
|
||||
import {getContentLanguages} from '#/state/preferences/languages'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {type app} from '#/lexicons'
|
||||
import {toLex} from '#/types/bsky'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
export type QueryProps = {
|
||||
limit?: number
|
||||
@@ -23,7 +22,7 @@ export const createGetSuggestedUsersForDiscoverQueryKey = (props: {
|
||||
}) => [getSuggestedUsersForDiscoverQueryKeyRoot, props.limit]
|
||||
|
||||
export function useGetSuggestedUsersForDiscoverQuery(props: QueryProps = {}) {
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
|
||||
return useQuery({
|
||||
@@ -33,30 +32,22 @@ export function useGetSuggestedUsersForDiscoverQuery(props: QueryProps = {}) {
|
||||
const contentLangs = getContentLanguages().join(',')
|
||||
const userInterests = aggregateUserInterests(preferences)
|
||||
|
||||
const {data} =
|
||||
await agent.app.bsky.unspecced.getSuggestedUsersForDiscover(
|
||||
{
|
||||
limit: props.limit || 10,
|
||||
const data = await appviewClient.call(
|
||||
app.bsky.unspecced.getSuggestedUsersForDiscover,
|
||||
{
|
||||
limit: props.limit || 10,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
...createBskyTopicsHeader(userInterests),
|
||||
'Accept-Language': contentLangs,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
...createBskyTopicsHeader(userInterests),
|
||||
'Accept-Language': contentLangs,
|
||||
},
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
if (!data.recIdStr) {
|
||||
logger.debug('getSuggestedUsersForDiscover response missing recIdStr')
|
||||
}
|
||||
/*
|
||||
* TODO(phase4): drop toLex once getSuggestedUsersForDiscover migrates off
|
||||
* the bridge agent (this unspecced endpoint is intentionally left on the
|
||||
* bridge in Phase 3, so it returns old `@atproto/api` view types).
|
||||
*/
|
||||
return toLex<{
|
||||
actors: app.bsky.actor.defs.ProfileView[]
|
||||
recId: string | undefined
|
||||
}>({...data, recId: data.recIdStr})
|
||||
return {...data, recId: data.recIdStr}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,9 +8,8 @@ import {logger} from '#/logger'
|
||||
import {getContentLanguages} from '#/state/preferences/languages'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {type app} from '#/lexicons'
|
||||
import {toLex} from '#/types/bsky'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
export type QueryProps = {
|
||||
category?: string | null
|
||||
@@ -24,7 +23,7 @@ export const createGetSuggestedUsersForExploreQueryKey = (
|
||||
) => [getSuggestedUsersForExploreQueryKeyRoot, props.category, props.limit]
|
||||
|
||||
export function useGetSuggestedUsersForExploreQuery(props: QueryProps = {}) {
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
|
||||
return useQuery({
|
||||
@@ -34,7 +33,8 @@ export function useGetSuggestedUsersForExploreQuery(props: QueryProps = {}) {
|
||||
const contentLangs = getContentLanguages().join(',')
|
||||
const userInterests = aggregateUserInterests(preferences)
|
||||
|
||||
const {data} = await agent.app.bsky.unspecced.getSuggestedUsersForExplore(
|
||||
const data = await appviewClient.call(
|
||||
app.bsky.unspecced.getSuggestedUsersForExplore,
|
||||
{
|
||||
category: props.category ?? undefined,
|
||||
limit: props.limit || 10,
|
||||
@@ -50,14 +50,7 @@ export function useGetSuggestedUsersForExploreQuery(props: QueryProps = {}) {
|
||||
if (!data.recIdStr) {
|
||||
logger.debug('getSuggestedUsersForExplore response missing recIdStr')
|
||||
}
|
||||
/*
|
||||
* TODO(phase4): drop toLex once getSuggestedUsersForExplore migrates off
|
||||
* the bridge agent (intentionally left on the bridge in Phase 3).
|
||||
*/
|
||||
return toLex<{
|
||||
actors: app.bsky.actor.defs.ProfileView[]
|
||||
recId: string | undefined
|
||||
}>({...data, recId: data.recIdStr})
|
||||
return {...data, recId: data.recIdStr}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,9 +8,8 @@ import {logger} from '#/logger'
|
||||
import {getContentLanguages} from '#/state/preferences/languages'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {type app} from '#/lexicons'
|
||||
import {toLex} from '#/types/bsky'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
export type QueryProps = {
|
||||
category?: string | null
|
||||
@@ -26,7 +25,7 @@ export const createGetSuggestedUsersForSeeMoreQueryKey = (props: {
|
||||
}) => [getSuggestedUsersForSeeMoreQueryKeyRoot, props.category, props.limit]
|
||||
|
||||
export function useGetSuggestedUsersForSeeMoreQuery(props: QueryProps = {}) {
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
|
||||
return useQuery({
|
||||
@@ -40,7 +39,8 @@ export function useGetSuggestedUsersForSeeMoreQuery(props: QueryProps = {}) {
|
||||
const contentLangs = getContentLanguages().join(',')
|
||||
const userInterests = aggregateUserInterests(preferences)
|
||||
|
||||
const {data} = await agent.app.bsky.unspecced.getSuggestedUsersForSeeMore(
|
||||
const data = await appviewClient.call(
|
||||
app.bsky.unspecced.getSuggestedUsersForSeeMore,
|
||||
{
|
||||
category: props.category ?? undefined,
|
||||
limit: props.limit || 50,
|
||||
@@ -56,14 +56,7 @@ export function useGetSuggestedUsersForSeeMoreQuery(props: QueryProps = {}) {
|
||||
if (!data.recIdStr) {
|
||||
logger.debug('getSuggestedUsersForSeeMore response missing recIdStr')
|
||||
}
|
||||
/*
|
||||
* TODO(phase4): drop toLex once this unspecced endpoint migrates off the
|
||||
* bridge agent (intentionally left on the bridge in Phase 3).
|
||||
*/
|
||||
return toLex<{
|
||||
actors: app.bsky.actor.defs.ProfileView[]
|
||||
recId: string | undefined
|
||||
}>({...data, recId: data.recIdStr})
|
||||
return {...data, recId: data.recIdStr}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,9 +7,8 @@ import {
|
||||
import {getContentLanguages} from '#/state/preferences/languages'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {type app} from '#/lexicons'
|
||||
import {toLex} from '#/types/bsky'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
export const createOnboardingSuggestedStarterPacksQueryKey = (
|
||||
interests?: string[],
|
||||
@@ -22,7 +21,7 @@ export function useOnboardingSuggestedStarterPacksQuery({
|
||||
enabled?: boolean
|
||||
overrideInterests?: string[]
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const contentLangs = getContentLanguages().join(',')
|
||||
|
||||
@@ -31,27 +30,19 @@ export function useOnboardingSuggestedStarterPacksQuery({
|
||||
staleTime: STALE.MINUTES.THREE,
|
||||
queryKey: createOnboardingSuggestedStarterPacksQueryKey(overrideInterests),
|
||||
queryFn: async () => {
|
||||
const {data} =
|
||||
await agent.app.bsky.unspecced.getOnboardingSuggestedStarterPacks(
|
||||
{limit: 6},
|
||||
{
|
||||
headers: {
|
||||
...createBskyTopicsHeader(
|
||||
overrideInterests
|
||||
? overrideInterests.join(',')
|
||||
: aggregateUserInterests(preferences),
|
||||
),
|
||||
'Accept-Language': contentLangs,
|
||||
},
|
||||
return await appviewClient.call(
|
||||
app.bsky.unspecced.getOnboardingSuggestedStarterPacks,
|
||||
{limit: 6},
|
||||
{
|
||||
headers: {
|
||||
...createBskyTopicsHeader(
|
||||
overrideInterests
|
||||
? overrideInterests.join(',')
|
||||
: aggregateUserInterests(preferences),
|
||||
),
|
||||
'Accept-Language': contentLangs,
|
||||
},
|
||||
)
|
||||
/*
|
||||
* TODO(phase4): drop toLex once getOnboardingSuggestedStarterPacks
|
||||
* migrates off the bridge agent (intentionally left on the bridge in
|
||||
* Phase 3).
|
||||
*/
|
||||
return toLex<app.bsky.unspecced.getOnboardingSuggestedStarterPacks.$OutputBody>(
|
||||
data,
|
||||
},
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -7,9 +7,8 @@ import {
|
||||
import {getContentLanguages} from '#/state/preferences/languages'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {type app} from '#/lexicons'
|
||||
import {toLex} from '#/types/bsky'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
export const createSuggestedStarterPacksQueryKey = (interests?: string[]) => [
|
||||
'suggested-starter-packs',
|
||||
@@ -23,7 +22,7 @@ export function useSuggestedStarterPacksQuery({
|
||||
enabled?: boolean
|
||||
overrideInterests?: string[]
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const contentLangs = getContentLanguages().join(',')
|
||||
|
||||
@@ -32,8 +31,9 @@ export function useSuggestedStarterPacksQuery({
|
||||
staleTime: STALE.MINUTES.THREE,
|
||||
queryKey: createSuggestedStarterPacksQueryKey(overrideInterests),
|
||||
queryFn: async () => {
|
||||
const {data} = await agent.app.bsky.unspecced.getSuggestedStarterPacks(
|
||||
undefined,
|
||||
return await appviewClient.call(
|
||||
app.bsky.unspecced.getSuggestedStarterPacks,
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
...createBskyTopicsHeader(
|
||||
@@ -45,13 +45,6 @@ export function useSuggestedStarterPacksQuery({
|
||||
},
|
||||
},
|
||||
)
|
||||
/*
|
||||
* TODO(phase4): drop toLex once getSuggestedStarterPacks migrates off the
|
||||
* bridge agent (intentionally left on the bridge in Phase 3).
|
||||
*/
|
||||
return toLex<app.bsky.unspecced.getSuggestedStarterPacks.$OutputBody>(
|
||||
data,
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import {Client} from '@atproto/lex-client'
|
||||
import {PasswordSession} from '@atproto/lex-password-session'
|
||||
|
||||
import {isJwtExpired} from '#/lib/jwt'
|
||||
import {type TemporaryPushClient} from '#/lib/notifications/notifications'
|
||||
import * as persisted from '#/state/persisted'
|
||||
import {
|
||||
networkAwareFetch,
|
||||
sessionAccountToSessionData,
|
||||
SessionAgent,
|
||||
} from './session-core'
|
||||
import {networkAwareFetch, sessionAccountToSessionData} from './session-core'
|
||||
import {type SessionAccount} from './types'
|
||||
|
||||
/*
|
||||
@@ -33,22 +31,30 @@ export function isSessionExpired(account: SessionAccount) {
|
||||
* Creates and resumes a throwaway session for every stored account.
|
||||
* Intended to send push token revocations just before logout.
|
||||
*
|
||||
* Each returned {@link SessionAgent} wraps a temporary `PasswordSession`
|
||||
* Each returned {@link TemporaryPushClient} wraps a temporary `PasswordSession`
|
||||
* resumed over the network to obtain a valid access token. These sessions are
|
||||
* deliberately hook-free (no `onUpdated`/`onDeleted`): they must NEVER persist
|
||||
* or race the active session. They are used once for the unregister call and
|
||||
* discarded (reclaimed by GC), so we never call `logout()` on them.
|
||||
*
|
||||
* Each session is wrapped in a plain account-shaped `Client` (no proxy header)
|
||||
* paired with the account's service origin and handle, matching the contract
|
||||
* {@link unregisterPushToken} consumes.
|
||||
*/
|
||||
export async function createTemporaryAgentsAndResume(
|
||||
accounts: SessionAccount[],
|
||||
): Promise<SessionAgent[]> {
|
||||
): Promise<TemporaryPushClient[]> {
|
||||
const settled = await Promise.allSettled(
|
||||
accounts.map(async account => {
|
||||
const session = await PasswordSession.resume(
|
||||
sessionAccountToSessionData(account),
|
||||
{fetch: networkAwareFetch},
|
||||
)
|
||||
return new SessionAgent(session)
|
||||
return {
|
||||
client: new Client(session),
|
||||
service: session.session.service,
|
||||
handle: session.session.handle,
|
||||
} satisfies TemporaryPushClient
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user