diff --git a/package.json b/package.json
index 77908a1615..a936f2ee54 100644
--- a/package.json
+++ b/package.json
@@ -70,7 +70,7 @@
"icons:optimize": "svgo -f ./assets/icons"
},
"dependencies": {
- "@atproto/api": "^0.15.26",
+ "@atproto/api": "^0.16.0",
"@bitdrift/react-native": "^0.6.8",
"@braintree/sanitize-url": "^6.0.2",
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
@@ -219,7 +219,7 @@
"zod": "^3.20.2"
},
"devDependencies": {
- "@atproto/dev-env": "^0.3.155",
+ "@atproto/dev-env": "^0.3.158",
"@babel/core": "^7.26.0",
"@babel/preset-env": "^7.26.0",
"@babel/runtime": "^7.26.0",
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index b77c884fe3..15785d2610 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -5,7 +5,7 @@ export const LOCAL_DEV_SERVICE =
Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583'
export const STAGING_SERVICE = 'https://staging.bsky.dev'
export const BSKY_SERVICE = 'https://bsky.social'
-export const BSKY_SERVICE_HANDLE_ENDSWITH = '.bsky.social'
+export const BSKY_SERVICE_DID = 'did:web:bsky.social'
export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'
export const DEFAULT_SERVICE = BSKY_SERVICE
const HELP_DESK_LANG = 'en-us'
diff --git a/src/lib/strings/handles.ts b/src/lib/strings/handles.ts
index 5b1a6f9474..02b9943d31 100644
--- a/src/lib/strings/handles.ts
+++ b/src/lib/strings/handles.ts
@@ -2,8 +2,6 @@
// https://github.com/bluesky-social/indigo/blob/main/atproto/syntax/handle.go#L10
import {forceLTR} from '#/lib/strings/bidi'
-export * from './reserved'
-
const VALIDATE_REGEX =
/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/
diff --git a/src/lib/strings/reserved.ts b/src/lib/strings/reserved.ts
deleted file mode 100644
index a0954ed35f..0000000000
--- a/src/lib/strings/reserved.ts
+++ /dev/null
@@ -1,1066 +0,0 @@
-// reserved handles for Bluesky PBC's PDS implementation
-// copied from: https://github.com/bluesky-social/atproto/blob/main/packages/pds/src/handle/reserved.ts
-
-const atpSpecific = [
- 'at',
- 'atp',
- 'plc',
- 'pds',
- 'did',
- 'repo',
- 'tid',
- 'nsid',
- 'xrpc',
- 'lex',
- 'lexicon',
- 'bsky',
- 'bluesky',
- 'handle',
-]
-
-// naively pulled from: https://radwebhosting.com/client_area/knowledgebase/196/List-of-Banned-Subdomain-Prefixes.html
-const commonlyReserved = [
- 'about',
- 'abuse',
- 'access',
- 'account',
- 'accounts',
- 'acme',
- 'activate',
- 'activities',
- 'activity',
- 'ad',
- 'add',
- 'address',
- 'adm',
- 'admanager',
- 'admin',
- 'administration',
- 'administrator',
- 'administrators',
- 'admins',
- 'ads',
- 'adsense',
- 'adult',
- 'advertising',
- 'adwords',
- 'affiliate',
- 'affiliatepage',
- 'affiliates',
- 'afp',
- 'ajax',
- 'all',
- 'alpha',
- 'analysis',
- 'analytics',
- 'android',
- 'anon',
- 'anonymous',
- 'answer',
- 'answers',
- 'ap',
- 'api',
- 'apis',
- 'app',
- 'appengine',
- 'appnews',
- 'apps',
- 'archive',
- 'archives',
- 'article',
- 'asdf',
- 'asset',
- 'assets',
- 'auth',
- 'authentication',
- 'avatar',
- 'backup',
- 'bank',
- 'banner',
- 'banners',
- 'base',
- 'beginners',
- 'beta',
- 'billing',
- 'bin',
- 'binaries',
- 'binary',
- 'blackberry',
- 'blog',
- 'blogs',
- 'blogsearch',
- 'board',
- 'book',
- 'bookmark',
- 'bookmarks',
- 'books',
- 'bot',
- 'bots',
- 'bug',
- 'bugs',
- 'business',
- 'buy',
- 'buzz',
- 'cache',
- 'calendar',
- 'call',
- 'campaign',
- 'cancel',
- 'captcha',
- 'career',
- 'careers',
- 'cart',
- 'catalog',
- 'catalogs',
- 'categories',
- 'category',
- 'cdn',
- 'cgi',
- 'cgi-bin',
- 'changelog',
- 'chart',
- 'charts',
- 'chat',
- 'check',
- 'checked',
- 'checking',
- 'checkout',
- 'client',
- 'cliente',
- 'clients',
- 'clients1',
- 'cnarne',
- 'code',
- 'comercial',
- 'comment',
- 'comments',
- 'communities',
- 'community',
- 'company',
- 'compare',
- 'compras',
- 'config',
- 'configuration',
- 'confirm',
- 'confirmation',
- 'connect',
- 'contact',
- 'contacts',
- 'contactus',
- 'contact-us',
- 'contact_us',
- 'content',
- 'contest',
- 'contribute',
- 'contributor',
- 'contributors',
- 'coppa',
- 'copyright',
- 'copyrights',
- 'core',
- 'corp',
- 'countries',
- 'country',
- 'cpanel',
- 'create',
- 'css',
- 'cssproxy',
- 'customise',
- 'customize',
- 'dashboard',
- 'data',
- 'db',
- 'default',
- 'delete',
- 'demo',
- 'design',
- 'designer',
- 'desktop',
- 'destroy',
- 'dev',
- 'devel',
- 'developer',
- 'developers',
- 'devs',
- 'diagram',
- 'diary',
- 'dict',
- 'dictionary',
- 'die',
- 'dir',
- 'directory',
- 'direct_messages',
- 'direct-messages',
- 'dist',
- 'diversity',
- 'dl',
- 'dmca',
- 'doc',
- 'docs',
- 'documentation',
- 'documentations',
- 'documents',
- 'domain',
- 'domains',
- 'donate',
- 'download',
- 'downloads',
- 'e',
- 'e-mail',
- 'earth',
- 'ecommerce',
- 'edit',
- 'edits',
- 'editor',
- 'edu',
- 'education',
- 'email',
- 'embed',
- 'embedded',
- 'employment',
- 'employments',
- 'empty',
- 'enable',
- 'encrypted',
- 'end',
- 'engine',
- 'enterprise',
- 'enterprises',
- 'entries',
- 'entry',
- 'error',
- 'errorlog',
- 'errors',
- 'eval',
- 'event',
- 'example',
- 'examplecommunity',
- 'exampleopenid',
- 'examplesyn',
- 'examplesyndicated',
- 'exampleusername',
- 'exchange',
- 'exit',
- 'explore',
- 'faq',
- 'faqs',
- 'favorite',
- 'favorites',
- 'favourite',
- 'favourites',
- 'feature',
- 'features',
- 'feed',
- 'feedback',
- 'feedburner',
- 'feedproxy',
- 'feeds',
- 'file',
- 'files',
- 'finance',
- 'folder',
- 'folders',
- 'first',
- 'following',
- 'forgot',
- 'form',
- 'forms',
- 'forum',
- 'forums',
- 'founder',
- 'free',
- 'friend',
- 'friends',
- 'ftp',
- 'fuck',
- 'fun',
- 'fusion',
- 'gadget',
- 'gadgets',
- 'game',
- 'games',
- 'gears',
- 'general',
- 'geographic',
- 'get',
- 'gettingstarted',
- 'gift',
- 'gifts',
- 'gist',
- 'git',
- 'github',
- 'gmail',
- 'go',
- 'golang',
- 'goto',
- 'gov',
- 'graph',
- 'graphs',
- 'group',
- 'groups',
- 'guest',
- 'guests',
- 'guide',
- 'guides',
- 'hack',
- 'hacks',
- 'head',
- 'help',
- 'home',
- 'homepage',
- 'host',
- 'hosting',
- 'hostmaster',
- 'hostname',
- 'howto',
- 'how-to',
- 'how_to',
- 'html',
- 'htrnl',
- 'http',
- 'httpd',
- 'https',
- 'i',
- 'iamges',
- 'icon',
- 'icons',
- 'id',
- 'idea',
- 'ideas',
- 'im',
- 'image',
- 'images',
- 'img',
- 'imap',
- 'inbox',
- 'inboxes',
- 'index',
- 'indexes',
- 'info',
- 'information',
- 'inquiry',
- 'intranet',
- 'investor',
- 'investors',
- 'invitation',
- 'invitations',
- 'invite',
- 'invoice',
- 'invoices',
- 'imac',
- 'ios',
- 'ipad',
- 'iphone',
- 'irc',
- 'irnages',
- 'irng',
- 'is',
- 'issue',
- 'issues',
- 'it',
- 'item',
- 'items',
- 'java',
- 'javascript',
- 'job',
- 'jobs',
- 'join',
- 'js',
- 'json',
- 'jump',
- 'kb',
- 'knowledge-base',
- 'knowledgebase',
- 'lab',
- 'labs',
- 'language',
- 'languages',
- 'last',
- 'ldap_status',
- 'ldap-status',
- 'ldapstatus',
- 'legal',
- 'license',
- 'licenses',
- 'link',
- 'links',
- 'linux',
- 'list',
- 'lists',
- 'livejournal',
- 'lj',
- 'local',
- 'locale',
- 'location',
- 'log',
- 'log-in',
- 'log-out',
- 'login',
- 'logout',
- 'logs',
- 'log_in',
- 'log_out',
- 'm',
- 'mac',
- 'macos',
- 'macosx',
- 'mac-os',
- 'mac-os-x',
- 'mac_os_x',
- 'mail',
- 'mailer',
- 'mailing',
- 'main',
- 'maintenance',
- 'manage',
- 'manager',
- 'manual',
- 'map',
- 'maps',
- 'marketing',
- 'master',
- 'me',
- 'media',
- 'member',
- 'members',
- 'memories',
- 'memory',
- 'merchandise',
- 'message',
- 'messages',
- 'messenger',
- 'mg',
- 'microblog',
- 'microblogs',
- 'mine',
- 'mis',
- 'misc',
- 'mms',
- 'mob',
- 'mobile',
- 'model',
- 'models',
- 'money',
- 'movie',
- 'movies',
- 'mp3',
- 'mp4',
- 'msg',
- 'msn',
- 'music',
- 'mx',
- 'my',
- 'mymme',
- 'mysql',
- 'name',
- 'named',
- 'nan',
- 'navi',
- 'navigation',
- 'net',
- 'network',
- 'networks',
- 'new',
- 'news',
- 'newsletter',
- 'nick',
- 'nickname',
- 'nil',
- 'none',
- 'notes',
- 'noticias',
- 'notification',
- 'notifications',
- 'notify',
- 'ns',
- 'ns1',
- 'ns2',
- 'ns3',
- 'ns4',
- 'ns5',
- 'null',
- 'oauth',
- 'oauth-clients',
- 'oauth_clients',
- 'ocsp',
- 'offer',
- 'offers',
- 'official',
- 'old',
- 'online',
- 'openid',
- 'operator',
- 'option',
- 'options',
- 'order',
- 'orders',
- 'org',
- 'organization',
- 'organizations',
- 'other',
- 'overview',
- 'owner',
- 'owners',
- 'p0rn',
- 'pack',
- 'page',
- 'pager',
- 'pages',
- 'paid',
- 'panel',
- 'partner',
- 'partnerpage',
- 'partners',
- 'password',
- 'patch',
- 'pay',
- 'payment',
- 'people',
- 'perl',
- 'person',
- 'phone',
- 'photo',
- 'photoalbum',
- 'photos',
- 'php',
- 'phpmyadmin',
- 'phppgadmin',
- 'phpredisadmin',
- 'pic',
- 'pics',
- 'picture',
- 'pictures',
- 'ping',
- 'pixel',
- 'places',
- 'plan',
- 'plans',
- 'plugin',
- 'plugins',
- 'podcasts',
- 'policies',
- 'policy',
- 'pop',
- 'pop3',
- 'popular',
- 'porn',
- 'portal',
- 'portals',
- 'post',
- 'postfix',
- 'postmaster',
- 'posts',
- 'pr',
- 'pr0n',
- 'premium',
- 'press',
- 'price',
- 'pricing',
- 'principles',
- 'print',
- 'privacy',
- 'privacy-policy',
- 'privacypolicy',
- 'privacy_policy',
- 'private',
- 'prod',
- 'product',
- 'production',
- 'products',
- 'profile',
- 'profiles',
- 'project',
- 'projects',
- 'promo',
- 'promotions',
- 'proxies',
- 'proxy',
- 'pub',
- 'public',
- 'purchase',
- 'purpose',
- 'put',
- 'python',
- 'queries',
- 'query',
- 'radio',
- 'random',
- 'ranking',
- 'read',
- 'reader',
- 'readme',
- 'recent',
- 'recruit',
- 'recruitment',
- 'redirect',
- 'register',
- 'registration',
- 'release',
- 'remove',
- 'replies',
- 'report',
- 'reports',
- 'repositories',
- 'repository',
- 'req',
- 'request',
- 'requests',
- 'research',
- 'reset',
- 'resolve',
- 'resolver',
- 'review',
- 'rnail',
- 'rnicrosoft',
- 'roc',
- 'root',
- 'rss',
- 'ruby',
- 'rule',
- 'sag',
- 'sale',
- 'sales',
- 'sample',
- 'samples',
- 'sandbox',
- 'save',
- 'scholar',
- 'school',
- 'schools',
- 'script',
- 'scripts',
- 'search',
- 'secure',
- 'security',
- 'self',
- 'seminars',
- 'send',
- 'server',
- 'server-info',
- 'server_info',
- 'server-status',
- 'server_status',
- 'servers',
- 'service',
- 'services',
- 'session',
- 'sessions',
- 'setting',
- 'settings',
- 'setup',
- 'share',
- 'shop',
- 'shopping',
- 'shortcut',
- 'shortcuts',
- 'show',
- 'sign-in',
- 'sign-up',
- 'signin',
- 'signout',
- 'signup',
- 'sign_in',
- 'sign_up',
- 'site',
- 'sitemap',
- 'sitemaps',
- 'sitenews',
- 'sites',
- 'sketchup',
- 'sky',
- 'slash',
- 'slashinvoice',
- 'slut',
- 'smartphone',
- 'sms',
- 'smtp',
- 'soap',
- 'software',
- 'sorry',
- 'source',
- 'spec',
- 'special',
- 'spreadsheet',
- 'spreadsheets',
- 'sql',
- 'src',
- 'srntp',
- 'ssh',
- 'ssl',
- 'ssladmin',
- 'ssladministrator',
- 'sslwebmaster',
- 'ssytem',
- 'staff',
- 'stage',
- 'staging',
- 'start',
- 'stat',
- 'state',
- 'static',
- 'statistics',
- 'stats',
- 'status',
- 'store',
- 'stores',
- 'stories',
- 'style',
- 'styleguide',
- 'styles',
- 'stylesheet',
- 'stylesheets',
- 'subdomain',
- 'subscribe',
- 'subscription',
- 'subscriptions',
- 'suggest',
- 'suggestqueries',
- 'support',
- 'survey',
- 'surveys',
- 'surveytool',
- 'svn',
- 'swf',
- 'syn',
- 'sync',
- 'syndicated',
- 'sys',
- 'sysadmin',
- 'sysadministrator',
- 'sysadmins',
- 'system',
- 'tablet',
- 'tablets',
- 'tag',
- 'tags',
- 'talk',
- 'talkgadget',
- 'task',
- 'tasks',
- 'team',
- 'teams',
- 'tech',
- 'telnet',
- 'term',
- 'terms',
- 'terms-of-service',
- 'termsofservice',
- 'terms_of_service',
- 'test',
- 'testing',
- 'tests',
- 'text',
- 'theme',
- 'themes',
- 'thread',
- 'threads',
- 'ticket',
- 'tickets',
- 'tmp',
- 'todo',
- 'to-do',
- 'to_do',
- 'toml',
- 'tool',
- 'toolbar',
- 'toolbars',
- 'tools',
- 'top',
- 'topic',
- 'topics',
- 'tos',
- 'tour',
- 'trac',
- 'translate',
- 'trace',
- 'translation',
- 'translations',
- 'translator',
- 'trends',
- 'tutorial',
- 'tux',
- 'tv',
- 'twitter',
- 'txt',
- 'ul',
- 'undef',
- 'unfollow',
- 'unsubscribe',
- 'update',
- 'updates',
- 'upgrade',
- 'upgrades',
- 'upi',
- 'upload',
- 'uploads',
- 'url',
- 'usage',
- 'user',
- 'username',
- 'usernames',
- 'users',
- 'uuid',
- 'validation',
- 'validations',
- 'ver',
- 'version',
- 'video',
- 'videos',
- 'video-stats',
- 'visitor',
- 'visitors',
- 'voice',
- 'volunteer',
- 'volunteers',
- 'w',
- 'watch',
- 'wave',
- 'weather',
- 'web',
- 'webdisk',
- 'webhook',
- 'webhooks',
- 'webmail',
- 'webmaster',
- 'webmasters',
- 'webrnail',
- 'website',
- 'websites',
- 'welcome',
- 'whm',
- 'whois',
- 'widget',
- 'widgets',
- 'wifi',
- 'wiki',
- 'wikis',
- 'win',
- 'windows',
- 'word',
- 'work',
- 'works',
- 'workshop',
- 'wpad',
- 'ww',
- 'wws',
- 'www',
- 'wwws',
- 'wwww',
- 'xfn',
- 'xhtml',
- 'xhtrnl',
- 'xml',
- 'xmpp',
- 'xpg',
- 'xxx',
- 'yaml',
- 'year',
- 'yml',
- 'you',
- 'yourdomain',
- 'yourname',
- 'yoursite',
- 'yourusername',
-]
-
-const famousAccounts = [
- // reserving some large twitter accounts (top 100 by followers according to wikidata dump)
- '10ronaldinho',
- '3gerardpique',
- 'aclu',
- 'adele',
- 'akshaykumar',
- 'aliaa08',
- 'aliciakeys',
- 'amitshah',
- 'andresiniesta8',
- 'anushkasharma',
- 'arianagrande',
- 'arrahman',
- 'arvindkejriwal',
- 'avrillavigne',
- 'barackobama',
- 'bbcbreaking',
- 'bbcworld',
- 'beingsalmankhan',
- 'billgates',
- 'britneyspears',
- 'brunomars',
- 'bts_bighit',
- 'bts_twt',
- 'championsleague',
- 'chrisbrown',
- 'cnnbrk',
- 'coldplay',
- 'conanobrien',
- 'cristiano',
- 'danieltosh',
- 'davidguetta',
- 'ddlovato',
- 'deepikapadukone',
- 'drake',
- 'elisapie',
- 'ellendegeneres',
- 'elonmusk',
- 'eminem',
- 'emmawatson',
- 'fcbarcelona',
- 'foxnews',
- 'harry_styles',
- 'hillaryclinton',
- 'iamsrk',
- 'ihrithik',
- 'imvkohli',
- 'instagram',
- 'jimmyfallon',
- 'jlo',
- 'joebiden',
- 'jtimberlake',
- 'justinbieber',
- 'kaka',
- 'kanyewest',
- 'katyperry',
- 'kendalljenner',
- 'kevinhart4real',
- 'khloekardashian',
- 'kimkardashian',
- 'kingjames',
- 'kourtneykardash',
- 'kyliejenner',
- 'ladygaga',
- 'liampayne',
- 'liltunechi',
- 'manutd',
- 'mariahcarey',
- 'mileycyrus',
- 'mohamadalarefe',
- 'narendramodi',
- 'nasa',
- 'nba',
- 'neymarjr',
- 'nfl',
- 'niallofficial',
- 'nickiminaj',
- 'npr',
- 'nytimes',
- 'onedirection',
- 'oprah',
- 'pink',
- 'pitbull',
- 'playstation',
- 'pmoindia',
- 'premierleague',
- 'priyankachopra',
- 'realdonaldtrump',
- 'ricky_martin',
- 'rihanna',
- 'sachin_rt',
- 'selenagomez',
- 'shakira',
- 'shawnmendes',
- 'sportscenter',
- 'srbachchan',
- 'subhisharma100',
- 'taylorswift13',
- 'theeconomist',
- 'twitter',
- 'virendersehwag',
- 'whitehouse45',
- 'wizkhalifa',
- 'youtube',
- 'zaynmalik',
-
- // some top instagram (https://en.wikipedia.org/wiki/List_of_most-followed_Instagram_accounts)
- 'beyonce',
- 'billieeilish',
- 'leomessi',
- 'natgeo',
- 'nike',
- 'snoopdogg',
- 'taylorswift',
- 'therock',
-
- // ... and a couple more prominent accounts, subjectively
- '10downingstreet',
- 'aoc',
- 'carterjwm',
- 'dril',
- 'gretathunberg',
- 'kamalaharris',
- 'kremlinrussia_e',
- 'potus',
- 'rondesantisfl',
- 'ukraine',
- 'washingtonpost',
- 'yousuck2020',
- 'zelenskyyua',
-
- // top japan-specific accounts
- 'akiko_lawson',
- 'ariyoshihiroiki',
- 'asahi',
- 'dozle_official',
- 'famima_now',
- 'ff_xiv_jp',
- 'fujitv',
- 'gigazine',
- 'hajimesyacho',
- 'hikakin',
- 'jocx',
- 'jotx',
- 'kiyo_saiore',
- 'mainichi',
- 'matsu_bouzu',
- 'naomiosaka',
- 'nhk',
- 'nikkei',
- 'nintendo',
- 'ntv',
- 'oowareware1945',
- 'pamyurin',
- 'poke_times',
- 'rolaworld',
- 'seikintv',
- 'starbucksjapan',
- 'tbs',
- 'tbs_pr',
- 'tvasahi',
- 'tvtokyo',
- 'yokoono',
- 'yomiuri_online',
-
- // top brazil-specific accounts
- 'brasildefato',
- 'claudialeitte',
- 'correio',
- 'em_com',
- 'estadao',
- 'folha',
- 'gazetadopovo',
- 'ivetesangalo',
- 'jairbolsonaro',
- 'jornaldobrasil',
- 'jornaloglobo',
- 'lucianohuck',
- 'lulaoficial',
- 'marcosmion',
- 'paulocoelho',
- 'portalr7',
- 'rede_globo',
- 'zerohora',
-]
-
-/**
- * Check if a handle is will be rejected by Bluesky PBC's PDS implementation
- * Note: other PDSes may have different restrictions, so we should only apply this check
- * if signing up to `bsky.social`
- *
- * See: https://github.com/bluesky-social/atproto/blob/main/packages/pds/src/handle/reserved.ts
- */
-export function isHandleReserved(prefix: string): boolean {
- return (
- atpSpecific.includes(prefix) ||
- commonlyReserved.includes(prefix) ||
- famousAccounts.includes(prefix)
- )
-}
diff --git a/src/screens/Signup/StepHandle.tsx b/src/screens/Signup/StepHandle.tsx
index f892bc7fad..980389450c 100644
--- a/src/screens/Signup/StepHandle.tsx
+++ b/src/screens/Signup/StepHandle.tsx
@@ -2,17 +2,18 @@ import {useState} from 'react'
import {View} from 'react-native'
import Animated, {
FadeIn,
+ FadeInDown,
FadeOut,
+ FadeOutDown,
LayoutAnimationConfig,
LinearTransition,
} from 'react-native-reanimated'
+import {type ComAtprotoTempCheckHandleAvailability} from '@atproto/api'
import {msg, Plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
-import {BSKY_SERVICE_HANDLE_ENDSWITH} from '#/lib/constants'
import {
createFullHandle,
- isHandleReserved,
MAX_SERVICE_HANDLE_LENGTH,
validateServiceHandle,
} from '#/lib/strings/handles'
@@ -22,14 +23,18 @@ import {useAgent} from '#/state/session'
import {ScreenTransition} from '#/screens/Login/ScreenTransition'
import {useSignupContext} from '#/screens/Signup/state'
import {atoms as a, native, useTheme} from '#/alf'
+import {borderRadius} from '#/alf/tokens'
+import {Button} from '#/components/Button'
import * as TextField from '#/components/forms/TextField'
import {useThrottledValue} from '#/components/hooks/useThrottledValue'
import {At_Stroke2_Corner0_Rounded as AtIcon} from '#/components/icons/At'
+import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
import {Text} from '#/components/Typography'
import {BackNextButtons} from './BackNextButtons'
export function StepHandle() {
const {_} = useLingui()
+ const t = useTheme()
const {state, dispatch} = useSignupContext()
const agent = useAgent()
const [draftValue, setDraftValue] = useState(state.handle)
@@ -37,11 +42,14 @@ export function StepHandle() {
const validCheck = validateServiceHandle(draftValue, state.userDomain)
- const {data: isHandleAvailable, isLoading} = useHandleAvailabilityQuery(
- draftValue,
- state.userDomain,
- validCheck.overall,
- )
+ const {data: isHandleAvailable, isLoading} = useHandleAvailabilityQuery({
+ username: draftValue,
+ serviceDid: state.serviceDescription?.did ?? 'UNKNOWN',
+ serviceDomain: state.userDomain,
+ birthDate: state.dateOfBirth.toISOString(),
+ email: state.email,
+ enabled: validCheck.overall,
+ })
const onNextPress = async () => {
if (!isHandleAvailable?.available) return
@@ -56,19 +64,6 @@ export function StepHandle() {
return
}
- if (
- state.userDomain === BSKY_SERVICE_HANDLE_ENDSWITH &&
- isHandleReserved(handle)
- ) {
- dispatch({
- type: 'setError',
- value: _(msg`That username is not available`),
- field: 'handle',
- })
- logger.metric('signup:handleReserved', {}, {statsig: true})
- return
- }
-
try {
dispatch({type: 'setIsLoading', value: true})
@@ -159,6 +154,9 @@ export function StepHandle() {
{draftValue}
)}
+ {isHandleAvailable?.available && (
+
+ )}
@@ -171,21 +169,31 @@ export function StepHandle() {
{isHandleAvailable &&
!isHandleAvailable.available &&
validCheck.overall && (
-
-
- {isHandleAvailable?.reason === 'taken' ? (
+ <>
+
+
- {createFullHandle(draftValue, state.userDomain)} is
- taken
-
- ) : (
-
- {createFullHandle(draftValue, state.userDomain)} is
- invalid
+ {createFullHandle(draftValue, state.userDomain)} is not
+ available
+
+
+ {isHandleAvailable.suggestions &&
+ isHandleAvailable.suggestions.length > 0 && (
+ {
+ setDraftValue(
+ suggestion.handle.slice(
+ 0,
+ state.userDomain.length * -1,
+ ),
+ )
+ // TODO: add logging
+ }}
+ />
)}
-
-
+ >
)}
{(!validCheck.handleChars || !validCheck.hyphenStartOrEnd) && (
@@ -247,3 +255,57 @@ function RequirementText({children}: {children: React.ReactNode}) {
)
}
+
+function HandleSuggestions({
+ suggestions,
+ onSelect,
+}: {
+ suggestions: ComAtprotoTempCheckHandleAvailability.Suggestion[]
+ onSelect: (
+ suggestions: ComAtprotoTempCheckHandleAvailability.Suggestion,
+ ) => void
+}) {
+ const t = useTheme()
+ const {_} = useLingui()
+
+ return (
+
+ {suggestions.map((suggestion, index) => (
+
+ ))}
+
+ )
+}
diff --git a/src/state/queries/handle-availablility.ts b/src/state/queries/handle-availablility.ts
index e1056d45be..e5d9f208f1 100644
--- a/src/state/queries/handle-availablility.ts
+++ b/src/state/queries/handle-availablility.ts
@@ -1,63 +1,113 @@
+import {useMemo} from 'react'
+import {Agent, ComAtprotoTempCheckHandleAvailability} from '@atproto/api'
import {useQuery} from '@tanstack/react-query'
-import {BSKY_SERVICE_HANDLE_ENDSWITH} from '#/lib/constants'
-import {createFullHandle, isHandleReserved} from '#/lib/strings/handles'
+import {
+ BSKY_SERVICE,
+ BSKY_SERVICE_DID,
+ PUBLIC_BSKY_SERVICE,
+} from '#/lib/constants'
+import {createFullHandle} from '#/lib/strings/handles'
import {logger} from '#/logger'
-import {useAgent} from '#/state/session'
import {useDebouncedValue} from '#/components/live/utils'
+import * as bsky from '#/types/bsky'
-export const RQKEY_handleAvailability = (handle: string) => [
- 'handle-availability',
- handle,
-]
+export const RQKEY_handleAvailability = (
+ handle: string,
+ domain: string,
+ serviceDid: string,
+) => ['handle-availability', handle, domain, serviceDid]
export function useHandleAvailabilityQuery(
- name: string,
- domain: string,
- enabled: boolean,
+ {
+ username,
+ serviceDomain,
+ serviceDid,
+ enabled,
+ birthDate,
+ email,
+ }: {
+ username: string
+ serviceDomain: string
+ serviceDid: string
+ enabled: boolean
+ birthDate?: string
+ email?: string
+ },
debounceDelayMs = 500,
) {
- name = name.trim()
- const handle = createFullHandle(name, domain)
- const debouncedHandle = useDebouncedValue(handle, debounceDelayMs)
- const agent = useAgent()
+ const name = username.trim()
+ const debouncedHandle = useDebouncedValue(name, debounceDelayMs)
+ const agent = useMemo(() => {
+ if (serviceDid === BSKY_SERVICE_DID) {
+ return new Agent({service: BSKY_SERVICE})
+ } else {
+ return new Agent({service: PUBLIC_BSKY_SERVICE})
+ }
+ }, [serviceDid])
return useQuery({
- enabled: enabled && handle === debouncedHandle,
- queryKey: RQKEY_handleAvailability(debouncedHandle),
+ enabled: enabled && name === debouncedHandle,
+ queryKey: RQKEY_handleAvailability(
+ debouncedHandle,
+ serviceDomain,
+ serviceDid,
+ ),
queryFn: async () => {
- const frontSegment = debouncedHandle.split('.')[0]
- if (
- domain === BSKY_SERVICE_HANDLE_ENDSWITH &&
- isHandleReserved(frontSegment)
- ) {
+ const handle = createFullHandle(name, serviceDomain)
+ if (serviceDid === BSKY_SERVICE_DID) {
+ // entryway has a special API for handle availability
+ const {data} = await agent.com.atproto.temp.checkHandleAvailability({
+ handle,
+ birthDate,
+ email,
+ })
+
+ if (
+ bsky.dangerousIsType(
+ data.result,
+ ComAtprotoTempCheckHandleAvailability.isResultAvailable,
+ )
+ ) {
+ return {available: true} as const
+ } else if (
+ bsky.dangerousIsType(
+ data.result,
+ ComAtprotoTempCheckHandleAvailability.isResultUnavailable,
+ )
+ ) {
+ return {
+ available: false,
+ suggestions: data.result.suggestions,
+ } as const
+ } else {
+ throw new Error(
+ `Unexpected result of \`checkHandleAvailability\`: ${JSON.stringify(data.result)}`,
+ )
+ }
+ } else {
+ // 3rd party services won't have this API so just try and resolve the handle
+ try {
+ const res = await agent.resolveHandle({
+ handle,
+ })
+
+ if (res.data.did) {
+ logger.metric(
+ 'signup:handleReserved',
+ {typeahead: true},
+ {statsig: true},
+ )
+ return {available: false} as const
+ }
+ } catch {}
logger.metric(
- 'signup:handleReserved',
+ 'signup:handleAvailable',
{typeahead: true},
{statsig: true},
)
- return {available: false, reason: 'reserved'} as const
+ return {available: true} as const
}
- try {
- const res = await agent.resolveHandle({
- handle: debouncedHandle,
- })
-
- if (res.data.did) {
- logger.metric(
- 'signup:handleReserved',
- {typeahead: true},
- {statsig: true},
- )
- return {available: false, reason: 'taken'} as const
- }
- } catch {}
- logger.metric(
- 'signup:handleAvailable',
- {typeahead: true},
- {statsig: true},
- )
- return {available: true} as const
},
})
}
diff --git a/yarn.lock b/yarn.lock
index 8079c90bfc..3595ee8fed 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -63,10 +63,10 @@
"@atproto/xrpc" "^0.7.1"
"@atproto/xrpc-server" "^0.9.0"
-"@atproto/api@^0.15.26":
- version "0.15.26"
- resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.15.26.tgz#452019d6d0753d4caa0f7941e8e87e9f8bfbee52"
- integrity sha512-AdXGjeCpLZiP9YMGi4YOdK1ayqkBhklmGfSG8UefqR6tTHth59PZvYs5KiwLnFhedt2Xljt3eUlhkn14Y48wEA==
+"@atproto/api@^0.16.0":
+ version "0.16.0"
+ resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.16.0.tgz#55ea5200131dfe18ce2944042e16c6923ff9b606"
+ integrity sha512-PQHeae6mz/L1YirUslfci7bknfg3RrSZjXpYwzLICxIOvqGKIkOi0+qukC2Py238RhXRo8YZ9dCuole9HQBXDw==
dependencies:
"@atproto/common-web" "^0.4.2"
"@atproto/lexicon" "^0.4.12"
@@ -94,14 +94,14 @@
multiformats "^9.9.0"
uint8arrays "3.0.0"
-"@atproto/bsky@^0.0.172":
- version "0.0.172"
- resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.172.tgz#963e3e2bc661e05c03fcb58788b2621d1afa6a3e"
- integrity sha512-P/oDJ4i4TuRBV8pQg8hht147jl+OruDYAXdVmUaHRMWPnry+godnfp6vzph9W9Y03DN+G320Z63dCfvFZquSUQ==
+"@atproto/bsky@^0.0.175":
+ version "0.0.175"
+ resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.175.tgz#e37671cba8014e9d6b4b41f229568fca8f3f6adf"
+ integrity sha512-i5UEwdaff3clAuL/+xBVFB1RuFDD/NviDUBCl35c5L1D2YcvjzwQPEn73fs+UKHMgH5J+TeYwnpOs8uckJpDQg==
dependencies:
"@atproto-labs/fetch-node" "0.1.9"
"@atproto-labs/xrpc-utils" "0.0.17"
- "@atproto/api" "^0.15.26"
+ "@atproto/api" "^0.16.0"
"@atproto/common" "^0.4.11"
"@atproto/crypto" "^0.4.4"
"@atproto/did" "^0.1.5"
@@ -129,6 +129,7 @@
jose "^5.0.1"
key-encoder "^2.0.3"
kysely "^0.22.0"
+ leo-profanity "^1.8.0"
multiformats "^9.9.0"
murmurhash "^2.0.1"
p-queue "^6.6.2"
@@ -221,20 +222,20 @@
"@noble/hashes" "^1.6.1"
uint8arrays "3.0.0"
-"@atproto/dev-env@^0.3.155":
- version "0.3.155"
- resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.155.tgz#26f865b92e9241d3e39c1c81e38c8422f2df26e2"
- integrity sha512-KNrArdTAfrZQsRsFnwLC1Vgqh27brD5G2ZI0E3qF9BXQw2iiCH1r46IAhhggugL6xo3VXYucmM6/HGX+bKyavw==
+"@atproto/dev-env@^0.3.158":
+ version "0.3.158"
+ resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.158.tgz#26666e462ea4a0bd6461b634a544041cedb70032"
+ integrity sha512-JllbsaEYH+abs5bTXMt2PcTu7TbHnZeGQRAXguUQZji3s3bdbNfCWsceCiKbR7O3w9X1JVFg1nz4OOl7CachSg==
dependencies:
- "@atproto/api" "^0.15.26"
- "@atproto/bsky" "^0.0.172"
+ "@atproto/api" "^0.16.0"
+ "@atproto/bsky" "^0.0.175"
"@atproto/bsync" "^0.0.20"
"@atproto/common-web" "^0.4.2"
"@atproto/crypto" "^0.4.4"
"@atproto/identity" "^0.4.8"
"@atproto/lexicon" "^0.4.12"
- "@atproto/ozone" "^0.1.131"
- "@atproto/pds" "^0.4.161"
+ "@atproto/ozone" "^0.1.133"
+ "@atproto/pds" "^0.4.163"
"@atproto/sync" "^0.1.29"
"@atproto/syntax" "^0.4.0"
"@atproto/xrpc-server" "^0.9.0"
@@ -350,12 +351,12 @@
"@atproto/jwk" "0.4.0"
zod "^3.23.8"
-"@atproto/ozone@^0.1.131":
- version "0.1.131"
- resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.131.tgz#b097c0274f424afd6af4e891cf78c563395b65b3"
- integrity sha512-xGp0KPK89SXwtXHYza5kYNkwizCdXvn1sq0+5gwR6U8qNrvw0ibxBVMgtHTmqo9633tGD0c0woza1j+r5adm1w==
+"@atproto/ozone@^0.1.133":
+ version "0.1.133"
+ resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.133.tgz#3c8c6a846b1ea1fced11b9ad799d795ad52a9c90"
+ integrity sha512-Ta9U7y8VCN7M9abo2W3SFtJpnEiid6ZP4UC4FVOXkgUSXHCU3kE/UdfIf9SyoZwpDJOjrElSj9GYXN/U8DWuIQ==
dependencies:
- "@atproto/api" "^0.15.26"
+ "@atproto/api" "^0.16.0"
"@atproto/common" "^0.4.11"
"@atproto/crypto" "^0.4.4"
"@atproto/identity" "^0.4.8"
@@ -380,14 +381,14 @@
undici "^6.14.1"
ws "^8.12.0"
-"@atproto/pds@^0.4.161":
- version "0.4.161"
- resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.161.tgz#1f74675d5d5e4ca56361c89926571646b4c641ee"
- integrity sha512-az1PGUCwIEx/b1neWBh9lgv27nbtczinGazI/ccG5A0AOMps1MXpTIJgEz5yJ1c6mSrhZlP0sL1EO6uC89irNA==
+"@atproto/pds@^0.4.163":
+ version "0.4.163"
+ resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.163.tgz#ec52e8194843ccdb08f7c28f16ce294a09d748b1"
+ integrity sha512-k3qgBCuVb1twc7D61qH9Z9j93uV1Mg7aq7qEOZ9I+BjiiIyyXECfwIAhw/U3EHL+lDVDmkAyir8F7H4t3j/QNQ==
dependencies:
"@atproto-labs/fetch-node" "0.1.9"
"@atproto-labs/xrpc-utils" "0.0.17"
- "@atproto/api" "^0.15.26"
+ "@atproto/api" "^0.16.0"
"@atproto/aws" "^0.2.25"
"@atproto/common" "^0.4.11"
"@atproto/crypto" "^0.4.4"
@@ -11877,6 +11878,11 @@ freeport-async@^2.0.0:
resolved "https://registry.yarnpkg.com/freeport-async/-/freeport-async-2.0.0.tgz#6adf2ec0c629d11abff92836acd04b399135bab4"
integrity sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ==
+french-badwords-list@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/french-badwords-list/-/french-badwords-list-1.0.7.tgz#8ed2442cb20fa2781c38b5a75c944f8e91a73c08"
+ integrity sha512-H1ziKs2PJh2+UXZ9oCGJ/rRQpsI9NBykGf2Sc7WaKaj1OnWFuBXfsvANTdRcfVmOghGQaUmRyZ1hJOPbDpy04Q==
+
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
@@ -14088,6 +14094,14 @@ launch-editor@^2.6.0:
picocolors "^1.0.0"
shell-quote "^1.7.3"
+leo-profanity@^1.8.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/leo-profanity/-/leo-profanity-1.8.0.tgz#2ce6522e27c093d02253cdb9755b74cc7ffccf90"
+ integrity sha512-TATupbZhT3oFCtsiEXzgXcLvW6cDPhDtN+0yQAibjwBdSw3WJFLKM0DZ18eEW8KeFTT10x8gCP+DDZz0cI4Bfg==
+ optionalDependencies:
+ french-badwords-list "^1.0.7"
+ russian-bad-words "^0.5.0"
+
leven@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
@@ -17575,6 +17589,11 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
+russian-bad-words@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/russian-bad-words/-/russian-bad-words-0.5.0.tgz#d4abcdc55f7af37606cdbac44bcb9e0bde6c3855"
+ integrity sha512-euNvEYki6iYYpkNbeudW+lEMMYGEmN7EBwVF8ezlbv0bZoQpVYB7W10cCeUIGV7Ed50sJynLQ0c559q5iI0ejQ==
+
rxjs@^6.6.0:
version "6.6.7"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"