Bluesky app fork with some witchin' additions 💫

create profile record before upload (#9547)

authored by samuel.fm and committed by

GitHub 9971e91f 8cee40be

+68 -8
+64 -3
src/components/contacts/screens/GetContacts.tsx
··· 1 import {Alert, View} from 'react-native' 2 import {useSafeAreaInsets} from 'react-native-safe-area-context' 3 import * as Contacts from 'expo-contacts' 4 - import {AppBskyContactImportContacts} from '@atproto/api' 5 import {msg, t, Trans} from '@lingui/macro' 6 import {useLingui} from '@lingui/react' 7 import {useMutation, useQueryClient} from '@tanstack/react-query' 8 9 import {cleanError, isNetworkError} from '#/lib/strings/errors' 10 import {logger} from '#/logger' 11 import {findContactsStatusQueryKey} from '#/state/queries/find-contacts' 12 import {useAgent} from '#/state/session' 13 import {atoms as a, ios, tokens, useGutters} from '#/alf' 14 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 15 import * as Layout from '#/components/Layout' ··· 43 const insets = useSafeAreaInsets() 44 const gutters = useGutters([0, 'wide']) 45 const queryClient = useQueryClient() 46 47 const {mutate: uploadContacts, isPending: isUploadPending} = useMutation({ 48 mutationFn: async (contacts: Contacts.ExistingContact[]) => { 49 const {phoneNumbers, indexToContactId} = normalizeContactBook( 50 contacts, 51 state.phoneCountryCode, ··· 202 <Text style={style}> 203 <Trans> 204 Bluesky helps friends find each other by creating an encoded digital 205 - fingerprint, called a "hash," and then looking for matching hashes. 206 </Trans> 207 </Text> 208 <Text style={style}> 209 - &bull; <Trans>We never store plain phone numbers</Trans> 210 </Text> 211 <Text style={style}> 212 &bull; <Trans>We delete hashes after matches are made</Trans> ··· 288 ], 289 ) 290 }
··· 1 + import {useContext} from 'react' 2 import {Alert, View} from 'react-native' 3 import {useSafeAreaInsets} from 'react-native-safe-area-context' 4 import * as Contacts from 'expo-contacts' 5 + import type AtpAgent from '@atproto/api' 6 + import { 7 + type AppBskyActorProfile, 8 + AppBskyContactImportContacts, 9 + type Un$Typed, 10 + } from '@atproto/api' 11 import {msg, t, Trans} from '@lingui/macro' 12 import {useLingui} from '@lingui/react' 13 import {useMutation, useQueryClient} from '@tanstack/react-query' 14 15 + import {uploadBlob} from '#/lib/api' 16 import {cleanError, isNetworkError} from '#/lib/strings/errors' 17 import {logger} from '#/logger' 18 import {findContactsStatusQueryKey} from '#/state/queries/find-contacts' 19 import {useAgent} from '#/state/session' 20 + import { 21 + Context as OnboardingContext, 22 + type OnboardingAction, 23 + type OnboardingState, 24 + } from '#/screens/Onboarding/state' 25 import {atoms as a, ios, tokens, useGutters} from '#/alf' 26 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 27 import * as Layout from '#/components/Layout' ··· 55 const insets = useSafeAreaInsets() 56 const gutters = useGutters([0, 'wide']) 57 const queryClient = useQueryClient() 58 + const maybeOnboardingContext = useContext(OnboardingContext) 59 60 const {mutate: uploadContacts, isPending: isUploadPending} = useMutation({ 61 mutationFn: async (contacts: Contacts.ExistingContact[]) => { 62 + /** 63 + * `importContacts` triggers a notification for the people you match with, 64 + * however we prevent notifications coming from users without profiles. 65 + * If you're using this as the onboarding flow, we need to create a profile 66 + * record before this. 67 + * 68 + * When you finish onboarding, we'll upsert again - bit wasteful but fine. 69 + */ 70 + if (context === 'Onboarding' && maybeOnboardingContext) { 71 + try { 72 + await createProfileRecord(agent, maybeOnboardingContext) 73 + } catch (error) { 74 + logger.debug('Error creating profile record:', {safeMessage: error}) 75 + } 76 + } 77 + 78 const {phoneNumbers, indexToContactId} = normalizeContactBook( 79 contacts, 80 state.phoneCountryCode, ··· 231 <Text style={style}> 232 <Trans> 233 Bluesky helps friends find each other by creating an encoded digital 234 + fingerprint, called a "hash", and then looking for matching hashes. 235 </Trans> 236 </Text> 237 <Text style={style}> 238 + &bull; <Trans>We never keep plain phone numbers</Trans> 239 </Text> 240 <Text style={style}> 241 &bull; <Trans>We delete hashes after matches are made</Trans> ··· 317 ], 318 ) 319 } 320 + 321 + /** 322 + * Copied from `#/screens/Onboarding/StepFinished/index.tsx` 323 + */ 324 + async function createProfileRecord( 325 + agent: AtpAgent, 326 + onboardingContext: { 327 + state: OnboardingState 328 + dispatch: React.Dispatch<OnboardingAction> 329 + }, 330 + ) { 331 + const profileStepResults = onboardingContext.state.profileStepResults 332 + const {imageUri, imageMime} = profileStepResults 333 + const blobPromise = 334 + imageUri && imageMime ? uploadBlob(agent, imageUri, imageMime) : undefined 335 + 336 + await agent.upsertProfile(async existing => { 337 + let next: Un$Typed<AppBskyActorProfile.Record> = existing ?? {} 338 + 339 + if (blobPromise) { 340 + const res = await blobPromise 341 + if (res.data.blob) { 342 + next.avatar = res.data.blob 343 + } 344 + } 345 + 346 + next.displayName = '' 347 + 348 + next.createdAt = new Date().toISOString() 349 + return next 350 + }) 351 + }
+4 -5
src/screens/Onboarding/StepFinished/index.tsx
··· 161 } 162 163 next.displayName = '' 164 - // HACKFIX 165 - // creating a bunch of identical profile objects is breaking the relay 166 - // tossing this unspecced field onto it to reduce the size of the problem 167 - // -prf 168 - next.createdAt = new Date().toISOString() 169 return next 170 }) 171
··· 161 } 162 163 next.displayName = '' 164 + 165 + if (!next.createdAt) { 166 + next.createdAt = new Date().toISOString() 167 + } 168 return next 169 }) 170