about summary refs log tree commit diff
path: root/src/state/queries
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/queries')
-rw-r--r--src/state/queries/handle-availability.ts126
1 files changed, 126 insertions, 0 deletions
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
+  }
+}