remove group utils from Convo agent, use ConvoWithDetails

This commit is contained in:
Samuel Newman
2026-04-24 10:41:59 +03:00
parent 877d5b0d9c
commit a20c576df9
9 changed files with 154 additions and 199 deletions
+2 -2
View File
@@ -183,7 +183,7 @@ export let MessageContextMenu = ({
control={reportControl} control={reportControl}
subject={{ subject={{
view: 'message', view: 'message',
convoId: convo.convo.id, convoId: convo.convo.view.id,
message, message,
}} }}
onAfterSubmit={() => { onAfterSubmit={() => {
@@ -197,7 +197,7 @@ export let MessageContextMenu = ({
control={blockOrDeleteControl} control={blockOrDeleteControl}
currentScreen="conversation" currentScreen="conversation"
params={{ params={{
convoId: convo.convo.id, convoId: convo.convo.view.id,
message, message,
}} }}
/> />
+2 -2
View File
@@ -68,13 +68,13 @@ export type DirectConvoMember = ChatBskyActorDefs.ProfileViewBasic & {
export type ConvoWithDetails = {view: ChatBskyConvoDefs.ConvoView} & ( export type ConvoWithDetails = {view: ChatBskyConvoDefs.ConvoView} & (
| { | {
kind: 'group' kind: 'group'
details: ChatBskyConvoDefs.GroupConvo details: $Typed<ChatBskyConvoDefs.GroupConvo>
primaryMember: GroupConvoMember // the owner primaryMember: GroupConvoMember // the owner
members: Array<GroupConvoMember> members: Array<GroupConvoMember>
} }
| { | {
kind: 'direct' kind: 'direct'
details: ChatBskyConvoDefs.DirectConvo details: $Typed<ChatBskyConvoDefs.DirectConvo>
primaryMember: DirectConvoMember // the other user primaryMember: DirectConvoMember // the other user
members: Array<DirectConvoMember> members: Array<DirectConvoMember>
} }
@@ -23,7 +23,7 @@ import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {AvatarBubbles} from '#/components/AvatarBubbles' import {AvatarBubbles} from '#/components/AvatarBubbles'
import {Button, type ButtonColor, ButtonIcon} from '#/components/Button' import {Button, type ButtonColor, ButtonIcon} from '#/components/Button'
import * as Dialog from '#/components/Dialog' import * as Dialog from '#/components/Dialog'
import {type ConvoWithDetails, parseConvoView} from '#/components/dms/util' import {type ConvoWithDetails} from '#/components/dms/util'
import {Error} from '#/components/Error' import {Error} from '#/components/Error'
import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeftIcon} from '#/components/icons/ArrowBoxLeft' import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeftIcon} from '#/components/icons/ArrowBoxLeft'
import { import {
@@ -110,8 +110,6 @@ function SettingsInner({convoId}: {convoId: string}) {
const {currentAccount} = useSession() const {currentAccount} = useSession()
const convo = convoState.convo const convo = convoState.convo
? parseConvoView(convoState.convo, currentAccount?.did)
: null
const primaryMember = convo?.primaryMember const primaryMember = convo?.primaryMember
const isOwner = !!primaryMember && primaryMember.did === currentAccount?.did const isOwner = !!primaryMember && primaryMember.did === currentAccount?.did
@@ -44,7 +44,7 @@ export function ChatStatusInfo({convoState}: {convoState: ActiveConvoStates}) {
{otherUser && ( {otherUser && (
<RejectMenu <RejectMenu
label={_(msg`Block or report`)} label={_(msg`Block or report`)}
convo={convoState.convo} convo={convoState.convo.view}
profile={otherUser} profile={otherUser}
color="negative_subtle" color="negative_subtle"
size="small" size="small"
@@ -53,14 +53,14 @@ export function ChatStatusInfo({convoState}: {convoState: ActiveConvoStates}) {
)} )}
<DeleteChatButton <DeleteChatButton
label={_(msg`Delete`)} label={_(msg`Delete`)}
convo={convoState.convo} convo={convoState.convo.view}
color="secondary" color="secondary"
size="small" size="small"
currentScreen="conversation" currentScreen="conversation"
onPress={leaveConvoControl.open} onPress={leaveConvoControl.open}
/> />
<LeaveConvoPrompt <LeaveConvoPrompt
convoId={convoState.convo.id} convoId={convoState.convo.view.id}
control={leaveConvoControl} control={leaveConvoControl}
currentScreen="conversation" currentScreen="conversation"
hasMessages={false} hasMessages={false}
@@ -69,7 +69,7 @@ export function ChatStatusInfo({convoState}: {convoState: ActiveConvoStates}) {
<View style={[a.w_full, a.flex_row]}> <View style={[a.w_full, a.flex_row]}>
<AcceptChatButton <AcceptChatButton
onAcceptConvo={onAcceptChat} onAcceptConvo={onAcceptChat}
convo={convoState.convo} convo={convoState.convo.view}
color="primary_subtle" color="primary_subtle"
size="small" size="small"
currentScreen="conversation" currentScreen="conversation"
@@ -377,7 +377,7 @@ export function MessagesList({
profile={convoState.convo.members.find( profile={convoState.convo.members.find(
member => member.did === item.message.sender.did, member => member.did === item.message.sender.did,
)} )}
isGroupChat={convoState.isGroup()} isGroupChat={convoState.convo.kind === 'group'}
/> />
) )
} else if (item.type === 'deleted-message') { } else if (item.type === 'deleted-message') {
@@ -446,8 +446,9 @@ export function MessagesList({
ListHeaderComponent={ ListHeaderComponent={
<> <>
<MaybeLoader isLoading={convoState.isFetchingHistory} /> <MaybeLoader isLoading={convoState.isFetchingHistory} />
{convoState.isGroup() && convoState.hasAllHistory ? ( {convoState.convo?.kind === 'group' &&
<MessagesListInfoPanel convoState={convoState} /> convoState.hasAllHistory ? (
<MessagesListInfoPanel convo={convoState.convo} />
) : null} ) : null}
</> </>
} }
@@ -575,7 +576,7 @@ function getFooterState(
} }
} }
if (convoState.convo.status === 'request' && !hasAcceptOverride) { if (convoState.convo.view.status === 'request' && !hasAcceptOverride) {
return 'request' return 'request'
} }
@@ -2,7 +2,6 @@ import {View} from 'react-native'
import {Plural, Trans, useLingui} from '@lingui/react/macro' import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {logger} from '#/logger' import {logger} from '#/logger'
import {type ConvoState} from '#/state/messages/convo/types'
import {useAddGroupMembers} from '#/state/queries/messages/add-group-members' import {useAddGroupMembers} from '#/state/queries/messages/add-group-members'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
@@ -10,14 +9,18 @@ import {AvatarBubbles} from '#/components/AvatarBubbles'
import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog' import * as Dialog from '#/components/Dialog'
import {AddMembersFlow} from '#/components/dms/AddMembersFlow' import {AddMembersFlow} from '#/components/dms/AddMembersFlow'
import {parseConvoView} from '#/components/dms/util' import {type ConvoWithDetails} from '#/components/dms/util'
import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {InviteLinkDialog} from './InviteLinkDialog' import {InviteLinkDialog} from './InviteLinkDialog'
export function MessagesListInfoPanel({convoState}: {convoState: ConvoState}) { export function MessagesListInfoPanel({
convo,
}: {
convo: Extract<ConvoWithDetails, {kind: 'group'}>
}) {
const t = useTheme() const t = useTheme()
const {t: l} = useLingui() const {t: l} = useLingui()
@@ -26,33 +29,25 @@ export function MessagesListInfoPanel({convoState}: {convoState: ConvoState}) {
const {currentAccount} = useSession() const {currentAccount} = useSession()
const convoId = convoState.convo?.id const convoId = convo.view.id
const {mutate: addGroupMembers} = useAddGroupMembers(convoId, { const {mutate: addGroupMembers} = useAddGroupMembers(convoId, {
onError: e => { onError: e => {
logger.error('Failed to add group chat members', {message: e}) logger.error('Failed to add group chat members', {message: e})
Toast.show(l`Failed to add members`, {type: 'error'}) Toast.show(l`Failed to add members`, {type: 'error'})
}, },
}) })
const convo = convoState.convo
? parseConvoView(convoState.convo, currentAccount?.did)
: null
const groupConvo = convo?.kind === 'group' ? convo : null
// TODO Enable this once the feature is working end-to-end. -dsb // TODO Enable this once the feature is working end-to-end. -dsb
// const joinLink = groupConvo?.details.joinLink // const joinLink = groupConvo?.details.joinLink
const isJoinLinkEnabled = false const isJoinLinkEnabled = false
// (isOwner && groupConvo) || // (isOwner && groupConvo) ||
// (!isOwner && groupConvo && joinLink?.enabledStatus === 'enabled') // (!isOwner && groupConvo && joinLink?.enabledStatus === 'enabled')
const isOwner = const isOwner = convo?.primaryMember.did === currentAccount?.did
currentAccount?.did == null
? false
: convoState.getPrimaryMember?.()?.did === currentAccount.did
// TODO Get this from @api/atproto -dsb // TODO Get this from @api/atproto -dsb
const isLinkEnabled = false const isLinkEnabled = false
const groupName = convoState.getGroupInfo?.()?.name const members = (convo?.members ?? []).filter(
const members = (convoState?.convo?.members ?? []).filter(
profile => profile.did !== currentAccount?.did, profile => profile.did !== currentAccount?.did,
) )
@@ -87,9 +82,9 @@ export function MessagesListInfoPanel({convoState}: {convoState: ConvoState}) {
<> <>
<View style={[a.align_center, a.justify_center]}> <View style={[a.align_center, a.justify_center]}>
<AvatarBubbles animate={true} profiles={members} /> <AvatarBubbles animate={true} profiles={members} />
{groupName ? ( {convo.details.name ? (
<Text style={[a.text_2xl, a.font_bold, a.mt_lg, t.atoms.text]}> <Text style={[a.text_2xl, a.font_bold, a.mt_lg, t.atoms.text]}>
{groupName} {convo.details.name}
</Text> </Text>
) : null} ) : null}
{names ? ( {names ? (
@@ -144,13 +139,11 @@ export function MessagesListInfoPanel({convoState}: {convoState: ConvoState}) {
</View> </View>
) : null} ) : null}
</View> </View>
{groupConvo ? ( <InviteLinkDialog
<InviteLinkDialog isOwner={isOwner}
isOwner={isOwner} convo={convo}
convo={groupConvo} control={inviteLinkControl}
control={inviteLinkControl} />
/>
) : null}
<Dialog.Outer <Dialog.Outer
control={addMembersControl} control={addMembersControl}
testID="addChatMembersDialog" testID="addChatMembersDialog"
+110 -124
View File
@@ -1,6 +1,6 @@
import { import {
type AtpAgent, type AtpAgent,
ChatBskyActorDefs, type ChatBskyActorDefs,
ChatBskyConvoDefs, ChatBskyConvoDefs,
type ChatBskyConvoGetLog, type ChatBskyConvoGetLog,
type ChatBskyConvoSendMessage, type ChatBskyConvoSendMessage,
@@ -37,8 +37,12 @@ import {
} from '#/state/messages/convo/types' } from '#/state/messages/convo/types'
import {type MessagesEventBus} from '#/state/messages/events/agent' import {type MessagesEventBus} from '#/state/messages/events/agent'
import {type MessagesEventBusError} from '#/state/messages/events/types' import {type MessagesEventBusError} from '#/state/messages/events/types'
import {
type ConvoWithDetails,
type GroupConvoMember,
parseConvoView,
} from '#/components/dms/util'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import * as bsky from '#/types/bsky'
const logger = Logger.create(Logger.Context.ConversationAgent) const logger = Logger.create(Logger.Context.ConversationAgent)
@@ -115,7 +119,7 @@ export class Convo {
private emitter = new EventEmitter<{event: [ConvoEvent]}>() private emitter = new EventEmitter<{event: [ConvoEvent]}>()
convoId: string convoId: string
convo: ChatBskyConvoDefs.ConvoView | undefined convo: ConvoWithDetails | undefined
sender: ChatBskyActorDefs.ProfileViewBasic | undefined sender: ChatBskyActorDefs.ProfileViewBasic | undefined
recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined
snapshot: ConvoState | undefined snapshot: ConvoState | undefined
@@ -131,6 +135,7 @@ export class Convo {
this.setupPlaceholderData(params.placeholderData) this.setupPlaceholderData(params.placeholderData)
} }
this.setConvo = this.setConvo.bind(this)
this.subscribe = this.subscribe.bind(this) this.subscribe = this.subscribe.bind(this)
this.getSnapshot = this.getSnapshot.bind(this) this.getSnapshot = this.getSnapshot.bind(this)
this.sendMessage = this.sendMessage.bind(this) this.sendMessage = this.sendMessage.bind(this)
@@ -142,9 +147,6 @@ export class Convo {
this.markConvoAccepted = this.markConvoAccepted.bind(this) this.markConvoAccepted = this.markConvoAccepted.bind(this)
this.addReaction = this.addReaction.bind(this) this.addReaction = this.addReaction.bind(this)
this.removeReaction = this.removeReaction.bind(this) this.removeReaction = this.removeReaction.bind(this)
this.isGroup = this.isGroup.bind(this)
this.getGroupInfo = this.getGroupInfo.bind(this)
this.getPrimaryMember = this.getPrimaryMember.bind(this)
this.updateGroupName = this.updateGroupName.bind(this) this.updateGroupName = this.updateGroupName.bind(this)
this.updateGroupMembers = this.updateGroupMembers.bind(this) this.updateGroupMembers = this.updateGroupMembers.bind(this)
this.updateJoinLink = this.updateJoinLink.bind(this) this.updateJoinLink = this.updateJoinLink.bind(this)
@@ -175,6 +177,15 @@ export class Convo {
} }
private generateSnapshot(): ConvoState { private generateSnapshot(): ConvoState {
const methods = {
deleteMessage: this.deleteMessage,
sendMessage: this.sendMessage,
fetchMessageHistory: this.fetchMessageHistory,
markConvoAccepted: this.markConvoAccepted,
addReaction: this.addReaction,
removeReaction: this.removeReaction,
}
switch (this.status) { switch (this.status) {
case ConvoStatus.Initializing: { case ConvoStatus.Initializing: {
return { return {
@@ -193,14 +204,50 @@ export class Convo {
markConvoAccepted: undefined, markConvoAccepted: undefined,
addReaction: undefined, addReaction: undefined,
removeReaction: undefined, removeReaction: undefined,
isGroup: this.isGroup,
getGroupInfo: this.getGroupInfo,
getPrimaryMember: this.getPrimaryMember,
} }
} }
case ConvoStatus.Disabled: case ConvoStatus.Disabled: {
case ConvoStatus.Suspended: return {
case ConvoStatus.Backgrounded: status: this.status,
items: this.getItems(),
convo: this.convo!,
error: undefined,
sender: this.sender!,
recipients: this.recipients!,
isFetchingHistory: this.isFetchingHistory,
// Explicit null check since the value is initially undefined.
hasAllHistory: this.oldestRev === null,
...methods,
}
}
case ConvoStatus.Suspended: {
return {
status: this.status,
items: this.getItems(),
convo: this.convo!,
error: undefined,
sender: this.sender!,
recipients: this.recipients!,
isFetchingHistory: this.isFetchingHistory,
// Explicit null check since the value is initially undefined.
hasAllHistory: this.oldestRev === null,
...methods,
}
}
case ConvoStatus.Backgrounded: {
return {
status: this.status,
items: this.getItems(),
convo: this.convo!,
error: undefined,
sender: this.sender!,
recipients: this.recipients!,
isFetchingHistory: this.isFetchingHistory,
// Explicit null check since the value is initially undefined.
hasAllHistory: this.oldestRev === null,
...methods,
}
}
case ConvoStatus.Ready: { case ConvoStatus.Ready: {
return { return {
status: this.status, status: this.status,
@@ -212,15 +259,7 @@ export class Convo {
isFetchingHistory: this.isFetchingHistory, isFetchingHistory: this.isFetchingHistory,
// Explicit null check since the value is initially undefined. // Explicit null check since the value is initially undefined.
hasAllHistory: this.oldestRev === null, hasAllHistory: this.oldestRev === null,
deleteMessage: this.deleteMessage, ...methods,
sendMessage: this.sendMessage,
fetchMessageHistory: this.fetchMessageHistory,
markConvoAccepted: this.markConvoAccepted,
addReaction: this.addReaction,
removeReaction: this.removeReaction,
isGroup: this.isGroup,
getGroupInfo: this.getGroupInfo,
getPrimaryMember: this.getPrimaryMember,
} }
} }
case ConvoStatus.Error: { case ConvoStatus.Error: {
@@ -239,9 +278,6 @@ export class Convo {
markConvoAccepted: undefined, markConvoAccepted: undefined,
addReaction: undefined, addReaction: undefined,
removeReaction: undefined, removeReaction: undefined,
isGroup: undefined,
getGroupInfo: undefined,
getPrimaryMember: undefined,
} }
} }
default: { default: {
@@ -261,9 +297,6 @@ export class Convo {
markConvoAccepted: undefined, markConvoAccepted: undefined,
addReaction: undefined, addReaction: undefined,
removeReaction: undefined, removeReaction: undefined,
isGroup: this.isGroup,
getGroupInfo: this.getGroupInfo,
getPrimaryMember: this.getPrimaryMember,
} }
} }
} }
@@ -501,6 +534,10 @@ export class Convo {
} }
} }
private setConvo(convo: ChatBskyConvoDefs.ConvoView) {
this.convo = parseConvoView(convo, this.senderUserDid) ?? this.convo
}
/** /**
* Initialises the convo with placeholder data, if provided. We still refetch it before rendering the convo, * Initialises the convo with placeholder data, if provided. We still refetch it before rendering the convo,
* but this allows us to render the convo header immediately. * but this allows us to render the convo header immediately.
@@ -508,7 +545,7 @@ export class Convo {
private setupPlaceholderData( private setupPlaceholderData(
data: NonNullable<ConvoParams['placeholderData']>, data: NonNullable<ConvoParams['placeholderData']>,
) { ) {
this.convo = data.convo this.setConvo(data.convo)
this.sender = data.convo.members.find(m => m.did === this.senderUserDid) this.sender = data.convo.members.find(m => m.did === this.senderUserDid)
this.recipients = data.convo.members.filter( this.recipients = data.convo.members.filter(
m => m.did !== this.senderUserDid, m => m.did !== this.senderUserDid,
@@ -519,7 +556,7 @@ export class Convo {
try { try {
const {convo, sender, recipients} = await this.fetchConvo() const {convo, sender, recipients} = await this.fetchConvo()
this.convo = convo this.setConvo(convo)
this.sender = sender this.sender = sender
this.recipients = recipients this.recipients = recipients
@@ -617,7 +654,7 @@ export class Convo {
this.pendingFetchConvo = (async () => { this.pendingFetchConvo = (async () => {
try { try {
const response = await networkRetry(2, () => { const response = await networkRetry(2, () => {
return this.agent.api.chat.bsky.convo.getConvo( return this.agent.chat.bsky.convo.getConvo(
{ {
convoId: this.convoId, convoId: this.convoId,
}, },
@@ -644,7 +681,7 @@ export class Convo {
try { try {
const {convo, sender, recipients} = await this.fetchConvo() const {convo, sender, recipients} = await this.fetchConvo()
// throw new Error('UNCOMMENT TO TEST REFRESH FAILURE') // throw new Error('UNCOMMENT TO TEST REFRESH FAILURE')
this.convo = convo || this.convo this.setConvo(convo)
this.sender = sender || this.sender this.sender = sender || this.sender
this.recipients = recipients || this.recipients this.recipients = recipients || this.recipients
} catch (err) { } catch (err) {
@@ -657,11 +694,7 @@ export class Convo {
} }
} }
private fetchMessageHistoryError: private fetchMessageHistoryError: {retry: () => void} | undefined
| {
retry: () => void
}
| undefined
async fetchMessageHistory() { async fetchMessageHistory() {
logger.debug('fetch message history', {}) logger.debug('fetch message history', {})
@@ -910,11 +943,11 @@ export class Convo {
id: tempId, id: tempId,
message, message,
}) })
if (this.convo?.status === 'request') { if (this.convo?.view.status === 'request') {
this.convo = { this.setConvo({
...this.convo, ...this.convo.view,
status: 'accepted', status: 'accepted',
} })
} }
this.commit() this.commit()
@@ -925,70 +958,65 @@ export class Convo {
markConvoAccepted() { markConvoAccepted() {
if (this.convo) { if (this.convo) {
this.convo = { this.setConvo({
...this.convo, ...this.convo.view,
status: 'accepted', status: 'accepted',
} })
} }
this.commit() this.commit()
} }
updateMuted(muted: boolean) { updateMuted(muted: boolean) {
if (this.convo) { if (this.convo) {
this.convo = { this.setConvo({
...this.convo, ...this.convo.view,
muted, muted,
} })
} }
this.commit() this.commit()
} }
updateGroupName(name: string) { updateGroupName(name: string) {
if ( if (this.convo?.kind !== 'group') {
this.convo && throw new Error('updateGroupName can only be called on group convo')
bsky.dangerousIsType<ChatBskyConvoDefs.GroupConvo>(
this.convo.kind,
ChatBskyConvoDefs.isGroupConvo,
)
) {
this.convo = {
...this.convo,
kind: {
...this.convo.kind,
name,
},
}
} }
this.setConvo({
...this.convo.view,
kind: {
...this.convo.details,
name,
},
})
this.commit() this.commit()
} }
updateGroupMembers(members: ChatBskyActorDefs.ProfileViewBasic[]) { updateGroupMembers(members: GroupConvoMember[]) {
if (this.convo?.kind !== 'group') {
throw new Error('updateGroupMembers can only be called on group convo')
}
if (this.convo) { if (this.convo) {
this.convo = { this.setConvo({
...this.convo, ...this.convo.view,
members, members,
} })
this.sender = members.find(m => m.did === this.senderUserDid)
this.recipients = members.filter(m => m.did !== this.senderUserDid)
} }
this.commit() this.commit()
} }
updateJoinLink(joinLink: ChatBskyGroupDefs.JoinLinkView | undefined) { updateJoinLink(joinLink: ChatBskyGroupDefs.JoinLinkView | undefined) {
if ( if (this.convo?.kind !== 'group') {
this.convo && throw new Error('updateJoinLink can only be called on group convo')
bsky.dangerousIsType<ChatBskyConvoDefs.GroupConvo>( }
this.convo.kind, if (this.convo) {
ChatBskyConvoDefs.isGroupConvo, this.setConvo({
) ...this.convo.view,
) {
this.convo = {
...this.convo,
kind: { kind: {
...this.convo.kind, ...this.convo.details,
joinLink, joinLink,
}, },
} })
} }
this.commit() this.commit()
} }
@@ -1014,7 +1042,7 @@ export class Convo {
const {id, message} = pendingMessage const {id, message} = pendingMessage
const response = await this.agent.api.chat.bsky.convo.sendMessage( const response = await this.agent.chat.bsky.convo.sendMessage(
{ {
convoId: this.convoId, convoId: this.convoId,
message, message,
@@ -1057,7 +1085,7 @@ export class Convo {
this.emitter.emit('event', { this.emitter.emit('event', {
type: 'invalidate-block-state', type: 'invalidate-block-state',
accountDids: [ accountDids: [
this.sender!.did, this.senderUserDid,
...this.recipients!.map(r => r.did), ...this.recipients!.map(r => r.did),
], ],
}) })
@@ -1109,7 +1137,7 @@ export class Convo {
) )
try { try {
const {data} = await this.agent.api.chat.bsky.convo.sendMessageBatch( const {data} = await this.agent.chat.bsky.convo.sendMessageBatch(
{ {
items: messageArray.map(({message}) => ({ items: messageArray.map(({message}) => ({
convoId: this.convoId, convoId: this.convoId,
@@ -1151,7 +1179,7 @@ export class Convo {
try { try {
await networkRetry(2, () => { await networkRetry(2, () => {
return this.agent.api.chat.bsky.convo.deleteMessageForSelf( return this.agent.chat.bsky.convo.deleteMessageForSelf(
{ {
convoId: this.convoId, convoId: this.convoId,
messageId, messageId,
@@ -1268,7 +1296,7 @@ export class Convo {
*/ */
sender: { sender: {
$type: 'chat.bsky.convo.defs#messageViewSender', $type: 'chat.bsky.convo.defs#messageViewSender',
did: this.sender!.did, did: this.senderUserDid,
}, },
}, },
nextMessage: null, nextMessage: null,
@@ -1482,46 +1510,4 @@ export class Convo {
throw error throw error
} }
} }
// Group utilities
isGroup(): boolean | undefined {
if (!this.convo) return undefined
const info = this.getGroupInfo()
return !!info
}
getGroupInfo(): ChatBskyConvoDefs.GroupConvo | undefined {
if (
this.convo &&
bsky.dangerousIsType<ChatBskyConvoDefs.GroupConvo>(
this.convo.kind,
ChatBskyConvoDefs.isGroupConvo,
)
) {
return this.convo.kind
}
return undefined
}
getPrimaryMember(): ChatBskyActorDefs.ProfileViewBasic | undefined {
if (this.isGroup()) {
return this.convo?.members.find(m => {
if (
bsky.dangerousIsType<ChatBskyActorDefs.GroupConvoMember>(
m.kind,
ChatBskyActorDefs.isGroupConvoMember,
)
) {
return m.kind.role === 'owner'
} else {
throw new Error(
'Expected a GroupConvoMember, got an unknown kind of member',
)
}
})
} else {
return this.recipients?.find(r => r.did !== this.senderUserDid)
}
}
} }
+6 -6
View File
@@ -29,6 +29,7 @@ import {
import {RQKEY_ROOT as ListConvosQueryKeyRoot} from '#/state/queries/messages/list-conversations' import {RQKEY_ROOT as ListConvosQueryKeyRoot} from '#/state/queries/messages/list-conversations'
import {RQKEY as createProfileQueryKey} from '#/state/queries/profile' import {RQKEY as createProfileQueryKey} from '#/state/queries/profile'
import {useAgent} from '#/state/session' import {useAgent} from '#/state/session'
import {type GroupConvoMember} from '#/components/dms/util'
export * from '#/state/messages/convo/util' export * from '#/state/messages/convo/util'
@@ -136,19 +137,18 @@ export function ConvoProvider({
const data = event.query.state.data as const data = event.query.state.data as
| ChatBskyConvoDefs.ConvoView | ChatBskyConvoDefs.ConvoView
| undefined | undefined
if (data && convo.convo && data.muted !== convo.convo.muted) { if (data && convo.convo && data.muted !== convo.convo.view.muted) {
convo.updateMuted(data.muted) convo.updateMuted(data.muted)
} }
if ( if (
data && data &&
convo.convo &&
ChatBskyConvoDefs.isGroupConvo(data.kind) && ChatBskyConvoDefs.isGroupConvo(data.kind) &&
ChatBskyConvoDefs.isGroupConvo(convo.convo.kind) convo.convo?.kind === 'group'
) { ) {
if (data.kind.name !== convo.convo.kind.name) { if (data.kind.name !== convo.convo.details.name) {
convo.updateGroupName(data.kind.name) convo.updateGroupName(data.kind.name)
} }
if (data.kind.joinLink !== convo.convo.kind.joinLink) { if (data.kind.joinLink !== convo.convo.details.joinLink) {
convo.updateJoinLink(data.kind.joinLink) convo.updateJoinLink(data.kind.joinLink)
} }
} }
@@ -157,7 +157,7 @@ export function ConvoProvider({
convo.convo && convo.convo &&
membersChanged(data.members, convo.convo.members) membersChanged(data.members, convo.convo.members)
) { ) {
convo.updateGroupMembers(data.members) convo.updateGroupMembers(data.members as GroupConvoMember[])
} }
} }
}) })
+7 -30
View File
@@ -6,6 +6,7 @@ import {
} from '@atproto/api' } from '@atproto/api'
import {type MessagesEventBus} from '#/state/messages/events/agent' import {type MessagesEventBus} from '#/state/messages/events/agent'
import {type ConvoWithDetails} from '#/components/dms/util'
export type ConvoParams = { export type ConvoParams = {
convoId: string convoId: string
@@ -150,14 +151,11 @@ type FetchMessageHistory = () => Promise<void>
type MarkConvoAccepted = () => void type MarkConvoAccepted = () => void
type AddReaction = (messageId: string, reaction: string) => Promise<void> type AddReaction = (messageId: string, reaction: string) => Promise<void>
type RemoveReaction = (messageId: string, reaction: string) => Promise<void> type RemoveReaction = (messageId: string, reaction: string) => Promise<void>
type IsGroup = () => boolean | undefined
type GetGroupInfo = () => ChatBskyConvoDefs.GroupConvo | undefined
type GetPrimaryMember = () => ChatBskyActorDefs.ProfileViewBasic | undefined
export type ConvoStateUninitialized = { export type ConvoStateUninitialized = {
status: ConvoStatus.Uninitialized status: ConvoStatus.Uninitialized
items: [] items: []
convo: ChatBskyConvoDefs.ConvoView | undefined convo: ConvoWithDetails | undefined
error: undefined error: undefined
sender: ChatBskyActorDefs.ProfileViewBasic | undefined sender: ChatBskyActorDefs.ProfileViewBasic | undefined
recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined
@@ -169,14 +167,11 @@ export type ConvoStateUninitialized = {
markConvoAccepted: undefined markConvoAccepted: undefined
addReaction: undefined addReaction: undefined
removeReaction: undefined removeReaction: undefined
isGroup: IsGroup
getGroupInfo: GetGroupInfo
getPrimaryMember: GetPrimaryMember
} }
export type ConvoStateInitializing = { export type ConvoStateInitializing = {
status: ConvoStatus.Initializing status: ConvoStatus.Initializing
items: [] items: []
convo: ChatBskyConvoDefs.ConvoView | undefined convo: ConvoWithDetails | undefined
error: undefined error: undefined
sender: ChatBskyActorDefs.ProfileViewBasic | undefined sender: ChatBskyActorDefs.ProfileViewBasic | undefined
recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined
@@ -188,14 +183,11 @@ export type ConvoStateInitializing = {
markConvoAccepted: undefined markConvoAccepted: undefined
addReaction: undefined addReaction: undefined
removeReaction: undefined removeReaction: undefined
isGroup: IsGroup
getGroupInfo: GetGroupInfo
getPrimaryMember: GetPrimaryMember
} }
export type ConvoStateReady = { export type ConvoStateReady = {
status: ConvoStatus.Ready status: ConvoStatus.Ready
items: ConvoItem[] items: ConvoItem[]
convo: ChatBskyConvoDefs.ConvoView convo: ConvoWithDetails
error: undefined error: undefined
sender: ChatBskyActorDefs.ProfileViewBasic sender: ChatBskyActorDefs.ProfileViewBasic
recipients: ChatBskyActorDefs.ProfileViewBasic[] recipients: ChatBskyActorDefs.ProfileViewBasic[]
@@ -207,14 +199,11 @@ export type ConvoStateReady = {
markConvoAccepted: MarkConvoAccepted markConvoAccepted: MarkConvoAccepted
addReaction: AddReaction addReaction: AddReaction
removeReaction: RemoveReaction removeReaction: RemoveReaction
isGroup: IsGroup
getGroupInfo: GetGroupInfo
getPrimaryMember: GetPrimaryMember
} }
export type ConvoStateBackgrounded = { export type ConvoStateBackgrounded = {
status: ConvoStatus.Backgrounded status: ConvoStatus.Backgrounded
items: ConvoItem[] items: ConvoItem[]
convo: ChatBskyConvoDefs.ConvoView convo: ConvoWithDetails
error: undefined error: undefined
sender: ChatBskyActorDefs.ProfileViewBasic sender: ChatBskyActorDefs.ProfileViewBasic
recipients: ChatBskyActorDefs.ProfileViewBasic[] recipients: ChatBskyActorDefs.ProfileViewBasic[]
@@ -226,14 +215,11 @@ export type ConvoStateBackgrounded = {
markConvoAccepted: MarkConvoAccepted markConvoAccepted: MarkConvoAccepted
addReaction: AddReaction addReaction: AddReaction
removeReaction: RemoveReaction removeReaction: RemoveReaction
isGroup: IsGroup
getGroupInfo: GetGroupInfo
getPrimaryMember: GetPrimaryMember
} }
export type ConvoStateSuspended = { export type ConvoStateSuspended = {
status: ConvoStatus.Suspended status: ConvoStatus.Suspended
items: ConvoItem[] items: ConvoItem[]
convo: ChatBskyConvoDefs.ConvoView convo: ConvoWithDetails
error: undefined error: undefined
sender: ChatBskyActorDefs.ProfileViewBasic sender: ChatBskyActorDefs.ProfileViewBasic
recipients: ChatBskyActorDefs.ProfileViewBasic[] recipients: ChatBskyActorDefs.ProfileViewBasic[]
@@ -245,9 +231,6 @@ export type ConvoStateSuspended = {
markConvoAccepted: MarkConvoAccepted markConvoAccepted: MarkConvoAccepted
addReaction: AddReaction addReaction: AddReaction
removeReaction: RemoveReaction removeReaction: RemoveReaction
isGroup: IsGroup
getGroupInfo: GetGroupInfo
getPrimaryMember: GetPrimaryMember
} }
export type ConvoStateError = { export type ConvoStateError = {
status: ConvoStatus.Error status: ConvoStatus.Error
@@ -264,14 +247,11 @@ export type ConvoStateError = {
markConvoAccepted: undefined markConvoAccepted: undefined
addReaction: undefined addReaction: undefined
removeReaction: undefined removeReaction: undefined
isGroup: undefined
getGroupInfo: undefined
getPrimaryMember: undefined
} }
export type ConvoStateDisabled = { export type ConvoStateDisabled = {
status: ConvoStatus.Disabled status: ConvoStatus.Disabled
items: ConvoItem[] items: ConvoItem[]
convo: ChatBskyConvoDefs.ConvoView convo: ConvoWithDetails
error: undefined error: undefined
sender: ChatBskyActorDefs.ProfileViewBasic sender: ChatBskyActorDefs.ProfileViewBasic
recipients: ChatBskyActorDefs.ProfileViewBasic[] recipients: ChatBskyActorDefs.ProfileViewBasic[]
@@ -283,9 +263,6 @@ export type ConvoStateDisabled = {
markConvoAccepted: MarkConvoAccepted markConvoAccepted: MarkConvoAccepted
addReaction: AddReaction addReaction: AddReaction
removeReaction: RemoveReaction removeReaction: RemoveReaction
isGroup: IsGroup
getGroupInfo: GetGroupInfo
getPrimaryMember: GetPrimaryMember
} }
export type ConvoState = export type ConvoState =
| ConvoStateUninitialized | ConvoStateUninitialized