about summary refs log tree commit diff
path: root/src/state
diff options
context:
space:
mode:
Diffstat (limited to 'src/state')
-rw-r--r--src/state/persisted/index.ts6
-rw-r--r--src/state/preferences/feed-tuners.tsx8
-rw-r--r--src/state/queries/handle-availability.ts126
-rw-r--r--src/state/queries/nuxs/__mocks__/index.ts25
-rw-r--r--src/state/queries/nuxs/definitions.ts10
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,
 }