diff --git a/eslint-suppressions.json b/eslint-suppressions.json index f2e677dc79..8a73625ce1 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -19,11 +19,6 @@ "count": 1 } }, - "src/ageAssurance/util.ts": { - "@typescript-eslint/no-floating-promises": { - "count": 1 - } - }, "src/alf/util/flatten.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -1942,12 +1937,6 @@ "src/state/session/agent.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 - }, - "@typescript-eslint/no-floating-promises": { - "count": 1 - }, - "@typescript-eslint/require-await": { - "count": 1 } }, "src/state/shell/color-mode.tsx": { diff --git a/src/ageAssurance/index.tsx b/src/ageAssurance/index.tsx index a5cca327ae..c035cbde77 100644 --- a/src/ageAssurance/index.tsx +++ b/src/ageAssurance/index.tsx @@ -93,7 +93,7 @@ function InnerProvider({children}: {children: React.ReactNode}) { const isAgeRestricted = s.access !== AgeAssuranceAccess.Full if (isAgeRestricted) { void getAndRegisterPushToken({isAgeRestricted}) - maybeRestrictChatSettings({agent}) + void maybeRestrictChatSettings({agent}) } }, [agent, getAndRegisterPushToken], diff --git a/src/ageAssurance/util.ts b/src/ageAssurance/util.ts index 310725db8f..8e9165cc86 100644 --- a/src/ageAssurance/util.ts +++ b/src/ageAssurance/util.ts @@ -12,7 +12,6 @@ import {restrictChatSettings} from '#/state/queries/messages/restrictChatSetting import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation' import { getDidFromAgentSession, - getOtherRequiredDataFromCache, useAgeAssuranceDataContext, } from '#/ageAssurance/data' import {AgeAssuranceAccess} from '#/ageAssurance/types' @@ -123,8 +122,6 @@ export const makeAgeRestrictedModerationPrefs = ( export function maybeRestrictChatSettings({agent}: {agent: AtpAgent}) { const did = getDidFromAgentSession(agent) if (!did) return - const data = getOtherRequiredDataFromCache({did}) - // ...update the chat setting record if allowIncoming is not already 'none'. - if (data?.actorDeclaration?.allowIncoming === 'none') return - restrictChatSettings({agent, did}) + // restrictChatSettings is a no-op if allowIncoming is already 'none'. + return restrictChatSettings({agent, did, restrictIncoming: true}) } diff --git a/src/state/birthdate.ts b/src/state/birthdate.ts index 1dd5bac1cc..46e71fa1cb 100644 --- a/src/state/birthdate.ts +++ b/src/state/birthdate.ts @@ -1,11 +1,11 @@ import {useMemo} from 'react' import {useMutation, useQueryClient} from '@tanstack/react-query' -import {restrictGroupChatSettings} from '#/state/queries/messages/restrictChatSettings' +import {restrictChatSettings} from '#/state/queries/messages/restrictChatSettings' import {preferencesQueryKey} from '#/state/queries/preferences' import {useAgent, useSession} from '#/state/session' import {usePatchAgeAssuranceOtherRequiredData} from '#/ageAssurance' -import {isUnderAge, maybeRestrictChatSettings} from '#/ageAssurance/util' +import {isUnderAge} from '#/ageAssurance/util' import {IS_DEV} from '#/env' import {account} from '#/storage' @@ -67,10 +67,14 @@ export function useBirthdateMutation() { }) if (isUnderAge(birthDate.toISOString(), 18)) { - maybeRestrictChatSettings({agent}) const did = agent.sessionManager.did if (did) { - await restrictGroupChatSettings({agent, did}) + await restrictChatSettings({ + agent, + did, + restrictIncoming: true, + restrictGroupInvites: true, + }) } } diff --git a/src/state/queries/messages/list-conversations.tsx b/src/state/queries/messages/list-conversations.tsx index 49454b2fc6..1067e36468 100644 --- a/src/state/queries/messages/list-conversations.tsx +++ b/src/state/queries/messages/list-conversations.tsx @@ -14,13 +14,13 @@ import { } from '@tanstack/react-query' import throttle from 'lodash.throttle' -import {useAgeAssurance} from '#/ageAssurance' import {DM_SERVICE_HEADERS} from '#/lib/constants' import {useCurrentConvoId} from '#/state/messages/current-convo-id' import {useMessagesEventBus} from '#/state/messages/events' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useAgent, useSession} from '#/state/session' import {parseConvoView} from '#/components/dms/util' +import {useAgeAssurance} from '#/ageAssurance' import * as bsky from '#/types/bsky' import {RQKEY as CONVO_KEY} from './conversation' import {useLeftConvos} from './leave-conversation' diff --git a/src/state/queries/messages/restrictChatSettings.ts b/src/state/queries/messages/restrictChatSettings.ts index 89acf83028..5e252b5505 100644 --- a/src/state/queries/messages/restrictChatSettings.ts +++ b/src/state/queries/messages/restrictChatSettings.ts @@ -9,20 +9,51 @@ import { } from '#/ageAssurance/data' /** - * Helper to update the chat settings record. + * Updates the chat actor declaration record to restrict who can contact the + * user. Both restrictions write to the same record (`rkey: 'self'`), so this + * is a single helper to avoid two concurrent `putRecord` calls racing each + * other and clobbering one another's changes. + * + * - `restrictIncoming`: sets `allowIncoming: 'none'` (used when a user isn't + * age-assured). + * - `restrictGroupInvites`: sets `allowGroupInvites: 'none'` (used for under-18 + * users, who per spec cannot participate in group chats). + * + * Dimensions that aren't being restricted preserve their cached value, falling + * back to the lexicon defaults when the cache is empty. */ export async function restrictChatSettings({ agent, did, + restrictIncoming = false, + restrictGroupInvites = false, }: { agent: AtpAgent did: string + restrictIncoming?: boolean + restrictGroupInvites?: boolean }): Promise { + const cached = getOtherRequiredDataFromCache({did})?.actorDeclaration + + const record: ChatBskyActorDeclaration.Main = { + $type: 'chat.bsky.actor.declaration', + allowIncoming: restrictIncoming + ? 'none' + : (cached?.allowIncoming ?? 'following'), + allowGroupInvites: restrictGroupInvites + ? 'none' + : cached?.allowGroupInvites, + } + + // Nothing to do if the record already reflects the desired restrictions. + if ( + cached?.allowIncoming === record.allowIncoming && + cached?.allowGroupInvites === record.allowGroupInvites + ) { + return + } + try { - const record: ChatBskyActorDeclaration.Main = { - $type: 'chat.bsky.actor.declaration', - allowIncoming: 'none', - } await networkRetry(3, () => agent.com.atproto.repo.putRecord({ repo: did, @@ -40,44 +71,3 @@ export async function restrictChatSettings({ logger.error(`restrictChatSettings: failed to set chat declaration`) } } - -/** - * Locks the user out of being added to group chats by setting - * `allowGroupInvites: 'none'` on the chat actor declaration. Used for under-18 - * users, who per spec cannot participate in group chats. - * - * Preserves the existing `allowIncoming` value if one is cached, otherwise - * defaults to 'following' (the lexicon default) since the field is required. - */ -export async function restrictGroupChatSettings({ - agent, - did, -}: { - agent: AtpAgent - did: string -}): Promise { - const cached = getOtherRequiredDataFromCache({did})?.actorDeclaration - if (cached?.allowGroupInvites === 'none') return - - try { - const record: ChatBskyActorDeclaration.Main = { - $type: 'chat.bsky.actor.declaration', - allowIncoming: cached?.allowIncoming ?? 'following', - allowGroupInvites: 'none', - } - await networkRetry(3, () => - agent.com.atproto.repo.putRecord({ - repo: did, - collection: 'chat.bsky.actor.declaration', - rkey: 'self', - record, - }), - ) - setOtherRequiredDataActorDeclarationCache({ - did, - actorDeclaration: record, - }) - } catch { - logger.error(`restrictGroupChatSettings: failed to set chat declaration`) - } -} diff --git a/src/state/session/agent.ts b/src/state/session/agent.ts index ff57002566..bfcbdc1df2 100644 --- a/src/state/session/agent.ts +++ b/src/state/session/agent.ts @@ -22,10 +22,7 @@ import { import {getAge} from '#/lib/strings/time' import {logger} from '#/logger' import {snoozeBirthdateUpdateAllowedForDid} from '#/state/birthdate' -import { - restrictChatSettings, - restrictGroupChatSettings, -} from '#/state/queries/messages/restrictChatSettings' +import {restrictChatSettings} from '#/state/queries/messages/restrictChatSettings' import {snoozeEmailConfirmationPrompt} from '#/state/shell/reminders' import { prefetchAgeAssuranceData, @@ -224,11 +221,15 @@ export async function createAgentAndCreateAccount( // wait for AA data to load first, then check state aa.then(async () => { const state = getAndComputeAgeAssuranceState({did: account.did}) - if (state.access !== AgeAssuranceAccess.Full) { - restrictChatSettings({agent, did: account.did}) - } - if (getAge(birthDate) < 18) { - restrictGroupChatSettings({agent, did: account.did}) + const restrictIncoming = state.access !== AgeAssuranceAccess.Full + const restrictGroupInvites = getAge(birthDate) < 18 + if (restrictIncoming || restrictGroupInvites) { + await restrictChatSettings({ + agent, + did: account.did, + restrictIncoming, + restrictGroupInvites, + }) } }), ]).then(promises => {