From a0dd4c45ca60eabd4f5c72660e7fe4e20474da06 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 12 Dec 2025 21:31:18 +0200 Subject: [PATCH] create profile record before upload --- .../contacts/screens/GetContacts.tsx | 67 ++++++++++++++++++- src/screens/Onboarding/StepFinished/index.tsx | 9 ++- 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/src/components/contacts/screens/GetContacts.tsx b/src/components/contacts/screens/GetContacts.tsx index 7057819e1f..5df967a8a5 100644 --- a/src/components/contacts/screens/GetContacts.tsx +++ b/src/components/contacts/screens/GetContacts.tsx @@ -1,15 +1,27 @@ +import {useContext} from 'react' import {Alert, View} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' import * as Contacts from 'expo-contacts' -import {AppBskyContactImportContacts} from '@atproto/api' +import type AtpAgent from '@atproto/api' +import { + type AppBskyActorProfile, + AppBskyContactImportContacts, + type Un$Typed, +} from '@atproto/api' import {msg, t, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useMutation, useQueryClient} from '@tanstack/react-query' +import {uploadBlob} from '#/lib/api' import {cleanError, isNetworkError} from '#/lib/strings/errors' import {logger} from '#/logger' import {findContactsStatusQueryKey} from '#/state/queries/find-contacts' import {useAgent} from '#/state/session' +import { + Context as OnboardingContext, + type OnboardingAction, + type OnboardingState, +} from '#/screens/Onboarding/state' import {atoms as a, ios, tokens, useGutters} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Layout from '#/components/Layout' @@ -43,9 +55,26 @@ export function GetContacts({ const insets = useSafeAreaInsets() const gutters = useGutters([0, 'wide']) const queryClient = useQueryClient() + const maybeOnboardingContext = useContext(OnboardingContext) const {mutate: uploadContacts, isPending: isUploadPending} = useMutation({ mutationFn: async (contacts: Contacts.ExistingContact[]) => { + /** + * `importContacts` triggers a notification for the people you match with, + * however we prevent notifications coming from users without profiles. + * If you're using this as the onboarding flow, we need to create a profile + * record before this. + * + * When you finish onboarding, we'll upsert again - bit wasteful but fine. + */ + if (context === 'Onboarding' && maybeOnboardingContext) { + try { + await createProfileRecord(agent, maybeOnboardingContext) + } catch (error) { + logger.debug('Error creating profile record:', {safeMessage: error}) + } + } + const {phoneNumbers, indexToContactId} = normalizeContactBook( contacts, state.phoneCountryCode, @@ -202,11 +231,11 @@ export function GetContacts({ Bluesky helps friends find each other by creating an encoded digital - fingerprint, called a "hash," and then looking for matching hashes. + fingerprint, called a "hash", and then looking for matching hashes. - • We never store plain phone numbers + • We never keep plain phone numbers We delete hashes after matches are made @@ -288,3 +317,35 @@ function showPermissionDeniedAlert() { ], ) } + +/** + * Copied from `#/screens/Onboarding/StepFinished/index.tsx` + */ +async function createProfileRecord( + agent: AtpAgent, + onboardingContext: { + state: OnboardingState + dispatch: React.Dispatch + }, +) { + const profileStepResults = onboardingContext.state.profileStepResults + const {imageUri, imageMime} = profileStepResults + const blobPromise = + imageUri && imageMime ? uploadBlob(agent, imageUri, imageMime) : undefined + + await agent.upsertProfile(async existing => { + let next: Un$Typed = existing ?? {} + + if (blobPromise) { + const res = await blobPromise + if (res.data.blob) { + next.avatar = res.data.blob + } + } + + next.displayName = '' + + next.createdAt = new Date().toISOString() + return next + }) +} diff --git a/src/screens/Onboarding/StepFinished/index.tsx b/src/screens/Onboarding/StepFinished/index.tsx index abf21d51d5..2c6dfd3198 100644 --- a/src/screens/Onboarding/StepFinished/index.tsx +++ b/src/screens/Onboarding/StepFinished/index.tsx @@ -161,11 +161,10 @@ export function StepFinished() { } next.displayName = '' - // HACKFIX - // creating a bunch of identical profile objects is breaking the relay - // tossing this unspecced field onto it to reduce the size of the problem - // -prf - next.createdAt = new Date().toISOString() + + if (!next.createdAt) { + next.createdAt = new Date().toISOString() + } return next })