about summary refs log tree commit diff
path: root/src/state/queries/handle-availability.ts
blob: 06fc6eebbe26d89679c264ce1d3af251f6229c09 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import {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'
import {Agent} from '../session/agent'

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(null, {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(null, {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
  }
}