diff options
Diffstat (limited to 'src/state')
-rw-r--r-- | src/state/persisted/index.ts | 6 | ||||
-rw-r--r-- | src/state/preferences/feed-tuners.tsx | 8 | ||||
-rw-r--r-- | src/state/queries/handle-availability.ts | 126 | ||||
-rw-r--r-- | src/state/queries/nuxs/__mocks__/index.ts | 25 | ||||
-rw-r--r-- | src/state/queries/nuxs/definitions.ts | 10 |
5 files changed, 171 insertions, 4 deletions
diff --git a/src/state/persisted/index.ts b/src/state/persisted/index.ts index 51d757ad8..8c043a342 100644 --- a/src/state/persisted/index.ts +++ b/src/state/persisted/index.ts @@ -3,11 +3,12 @@ import AsyncStorage from '@react-native-async-storage/async-storage' import {logger} from '#/logger' import { defaults, - Schema, + type Schema, tryParse, tryStringify, } from '#/state/persisted/schema' -import {PersistedApi} from './types' +import {device} from '#/storage' +import {type PersistedApi} from './types' import {normalizeData} from './util' export type {PersistedAccount, Schema} from '#/state/persisted/schema' @@ -53,6 +54,7 @@ onUpdate satisfies PersistedApi['onUpdate'] export async function clearStorage() { try { await AsyncStorage.removeItem(BSKY_STORAGE) + device.removeAll() } catch (e: any) { logger.error(`persisted store: failed to clear`, {message: e.toString()}) } diff --git a/src/state/preferences/feed-tuners.tsx b/src/state/preferences/feed-tuners.tsx index 3ed60e598..feeab6f9a 100644 --- a/src/state/preferences/feed-tuners.tsx +++ b/src/state/preferences/feed-tuners.tsx @@ -1,7 +1,7 @@ import {useMemo} from 'react' import {FeedTuner} from '#/lib/api/feed-manip' -import {FeedDescriptor} from '../queries/post-feed' +import {type FeedDescriptor} from '../queries/post-feed' import {usePreferencesQuery} from '../queries/preferences' import {useSession} from '../session' import {useLanguagePrefs} from './languages' @@ -19,7 +19,10 @@ export function useFeedTuners(feedDesc: FeedDescriptor) { } } if (feedDesc.startsWith('feedgen')) { - return [FeedTuner.preferredLangOnly(langPrefs.contentLanguages)] + return [ + FeedTuner.preferredLangOnly(langPrefs.contentLanguages), + FeedTuner.removeMutedThreads, + ] } if (feedDesc === 'following' || feedDesc.startsWith('list')) { const feedTuners = [FeedTuner.removeOrphans] @@ -40,6 +43,7 @@ export function useFeedTuners(feedDesc: FeedDescriptor) { feedTuners.push(FeedTuner.removeQuotePosts) } feedTuners.push(FeedTuner.dedupThreads) + feedTuners.push(FeedTuner.removeMutedThreads) return feedTuners } diff --git a/src/state/queries/handle-availability.ts b/src/state/queries/handle-availability.ts new file mode 100644 index 000000000..9391f5d09 --- /dev/null +++ b/src/state/queries/handle-availability.ts @@ -0,0 +1,126 @@ +import {Agent, ComAtprotoTempCheckHandleAvailability} from '@atproto/api' +import {useQuery} from '@tanstack/react-query' + +import { + BSKY_SERVICE, + BSKY_SERVICE_DID, + PUBLIC_BSKY_SERVICE, +} from '#/lib/constants' +import {createFullHandle} from '#/lib/strings/handles' +import {logger} from '#/logger' +import {useDebouncedValue} from '#/components/live/utils' +import * as bsky from '#/types/bsky' + +export const RQKEY_handleAvailability = ( + handle: string, + domain: string, + serviceDid: string, +) => ['handle-availability', {handle, domain, serviceDid}] + +export function useHandleAvailabilityQuery( + { + username, + serviceDomain, + serviceDid, + enabled, + birthDate, + email, + }: { + username: string + serviceDomain: string + serviceDid: string + enabled: boolean + birthDate?: string + email?: string + }, + debounceDelayMs = 500, +) { + const name = username.trim() + const debouncedHandle = useDebouncedValue(name, debounceDelayMs) + + return { + debouncedUsername: debouncedHandle, + enabled: enabled && name === debouncedHandle, + query: useQuery({ + enabled: enabled && name === debouncedHandle, + queryKey: RQKEY_handleAvailability( + debouncedHandle, + serviceDomain, + serviceDid, + ), + queryFn: async () => { + const handle = createFullHandle(name, serviceDomain) + return await checkHandleAvailability(handle, serviceDid, { + email, + birthDate, + typeahead: true, + }) + }, + }), + } +} + +export async function checkHandleAvailability( + handle: string, + serviceDid: string, + { + email, + birthDate, + typeahead, + }: { + email?: string + birthDate?: string + typeahead?: boolean + }, +) { + if (serviceDid === BSKY_SERVICE_DID) { + const agent = new Agent({service: BSKY_SERVICE}) + // entryway has a special API for handle availability + const {data} = await agent.com.atproto.temp.checkHandleAvailability({ + handle, + birthDate, + email, + }) + + if ( + bsky.dangerousIsType<ComAtprotoTempCheckHandleAvailability.ResultAvailable>( + data.result, + ComAtprotoTempCheckHandleAvailability.isResultAvailable, + ) + ) { + logger.metric('signup:handleAvailable', {typeahead}, {statsig: true}) + + return {available: true} as const + } else if ( + bsky.dangerousIsType<ComAtprotoTempCheckHandleAvailability.ResultUnavailable>( + data.result, + ComAtprotoTempCheckHandleAvailability.isResultUnavailable, + ) + ) { + logger.metric('signup:handleTaken', {typeahead}, {statsig: true}) + return { + available: false, + suggestions: data.result.suggestions, + } as const + } else { + throw new Error( + `Unexpected result of \`checkHandleAvailability\`: ${JSON.stringify(data.result)}`, + ) + } + } else { + // 3rd party PDSes won't have this API so just try and resolve the handle + const agent = new Agent({service: PUBLIC_BSKY_SERVICE}) + try { + const res = await agent.resolveHandle({ + handle, + }) + + if (res.data.did) { + logger.metric('signup:handleTaken', {typeahead}, {statsig: true}) + return {available: false} as const + } + } catch {} + logger.metric('signup:handleAvailable', {typeahead}, {statsig: true}) + return {available: true} as const + } +} diff --git a/src/state/queries/nuxs/__mocks__/index.ts b/src/state/queries/nuxs/__mocks__/index.ts new file mode 100644 index 000000000..c718b1594 --- /dev/null +++ b/src/state/queries/nuxs/__mocks__/index.ts @@ -0,0 +1,25 @@ +import {jest} from '@jest/globals' + +export {Nux} from '#/state/queries/nuxs/definitions' + +export const useNuxs = jest.fn(() => { + return { + nuxs: undefined, + status: 'loading' as const, + } +}) + +export const useNux = jest.fn((id: string) => { + return { + nux: undefined, + status: 'loading' as const, + } +}) + +export const useSaveNux = jest.fn(() => { + return {} +}) + +export const useResetNuxs = jest.fn(() => { + return {} +}) diff --git a/src/state/queries/nuxs/definitions.ts b/src/state/queries/nuxs/definitions.ts index 3d5c132f2..7577d6b20 100644 --- a/src/state/queries/nuxs/definitions.ts +++ b/src/state/queries/nuxs/definitions.ts @@ -9,6 +9,11 @@ export enum Nux { ActivitySubscriptions = 'ActivitySubscriptions', AgeAssuranceDismissibleNotice = 'AgeAssuranceDismissibleNotice', AgeAssuranceDismissibleFeedBanner = 'AgeAssuranceDismissibleFeedBanner', + + /* + * Blocking announcements. New IDs are required for each new announcement. + */ + PolicyUpdate202508 = 'PolicyUpdate202508', } export const nuxNames = new Set(Object.values(Nux)) @@ -38,6 +43,10 @@ export type AppNux = BaseNux< id: Nux.AgeAssuranceDismissibleFeedBanner data: undefined } + | { + id: Nux.PolicyUpdate202508 + data: undefined + } > export const NuxSchemas: Record<Nux, zod.ZodObject<any> | undefined> = { @@ -47,4 +56,5 @@ export const NuxSchemas: Record<Nux, zod.ZodObject<any> | undefined> = { [Nux.ActivitySubscriptions]: undefined, [Nux.AgeAssuranceDismissibleNotice]: undefined, [Nux.AgeAssuranceDismissibleFeedBanner]: undefined, + [Nux.PolicyUpdate202508]: undefined, } |