Handle block state when sending messages
This commit is contained in:
@@ -202,7 +202,7 @@ let MessageItemMetadata = ({
|
||||
)}
|
||||
</TimeElapsed>
|
||||
|
||||
{item.type === 'pending-message' && item.retry && (
|
||||
{item.type === 'pending-message' && item.failed && (
|
||||
<>
|
||||
{' '}
|
||||
·{' '}
|
||||
@@ -214,15 +214,20 @@ let MessageItemMetadata = ({
|
||||
},
|
||||
]}>
|
||||
{_(msg`Failed to send`)}
|
||||
</Text>{' '}
|
||||
·{' '}
|
||||
<InlineLinkText
|
||||
label={_(msg`Click to retry failed message`)}
|
||||
to="#"
|
||||
onPress={handleRetry}
|
||||
style={[a.text_xs]}>
|
||||
{_(msg`Retry`)}
|
||||
</InlineLinkText>
|
||||
</Text>
|
||||
{item.retry && (
|
||||
<>
|
||||
{' '}
|
||||
·{' '}
|
||||
<InlineLinkText
|
||||
label={_(msg`Click to retry failed message`)}
|
||||
to="#"
|
||||
onPress={handleRetry}
|
||||
style={[a.text_xs]}>
|
||||
{_(msg`Retry`)}
|
||||
</InlineLinkText>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
|
||||
@@ -10,22 +10,21 @@ import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Refresh} from '#/
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function MessageListError({
|
||||
item,
|
||||
}: {
|
||||
item: ConvoItem & {type: 'error-recoverable'}
|
||||
}) {
|
||||
export function MessageListError({item}: {item: ConvoItem & {type: 'error'}}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const message = React.useMemo(() => {
|
||||
return {
|
||||
[ConvoItemError.Network]: _(
|
||||
msg`There was an issue connecting to the chat.`,
|
||||
[ConvoItemError.Unknown]: _(
|
||||
msg`An unknown error occurred. If the issue persists, contact support.`,
|
||||
),
|
||||
[ConvoItemError.FirehoseFailed]: _(
|
||||
msg`This chat was disconnected due to a network error.`,
|
||||
),
|
||||
[ConvoItemError.HistoryFailed]: _(msg`Failed to load past messages.`),
|
||||
[ConvoItemError.UserBlocked]: _(
|
||||
msg`The other user in this chat has blocked you.`,
|
||||
),
|
||||
}[item.code]
|
||||
}, [_, item.code])
|
||||
|
||||
@@ -49,24 +48,26 @@ export function MessageListError({
|
||||
fill={t.palette.negative_400}
|
||||
style={[{top: 3}]}
|
||||
/>
|
||||
<View style={[a.flex_1, {maxWidth: 200}]}>
|
||||
<View style={[a.flex_1, {maxWidth: 240}]}>
|
||||
<Text style={[a.leading_snug]}>{message}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
label={_(msg`Press to retry`)}
|
||||
size="small"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
onPress={e => {
|
||||
e.preventDefault()
|
||||
item.retry()
|
||||
return false
|
||||
}}>
|
||||
<ButtonText>{_(msg`Retry`)}</ButtonText>
|
||||
<ButtonIcon icon={Refresh} position="right" />
|
||||
</Button>
|
||||
{item.retry && (
|
||||
<Button
|
||||
label={_(msg`Press to retry`)}
|
||||
size="small"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
onPress={e => {
|
||||
e.preventDefault()
|
||||
item.retry?.()
|
||||
return false
|
||||
}}>
|
||||
<ButtonText>{_(msg`Retry`)}</ButtonText>
|
||||
<ButtonIcon icon={Refresh} position="right" />
|
||||
</Button>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ function renderItem({item}: {item: ConvoItem}) {
|
||||
return <MessageItem item={item} />
|
||||
} else if (item.type === 'deleted-message') {
|
||||
return <Text>Deleted message</Text>
|
||||
} else if (item.type === 'error-recoverable') {
|
||||
} else if (item.type === 'error') {
|
||||
return <MessageListError item={item} />
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
ChatBskyConvoGetLog,
|
||||
ChatBskyConvoSendMessage,
|
||||
} from '@atproto/api'
|
||||
import {XRPCError} from '@atproto/xrpc'
|
||||
import EventEmitter from 'eventemitter3'
|
||||
import {nanoid} from 'nanoid/non-secure'
|
||||
|
||||
import {networkRetry} from '#/lib/async/retry'
|
||||
@@ -14,11 +16,13 @@ import {
|
||||
ACTIVE_POLL_INTERVAL,
|
||||
BACKGROUND_POLL_INTERVAL,
|
||||
INACTIVE_TIMEOUT,
|
||||
NETWORK_FAILURE_STATUSES,
|
||||
} from '#/state/messages/convo/const'
|
||||
import {
|
||||
ConvoDispatch,
|
||||
ConvoDispatchEvent,
|
||||
ConvoErrorCode,
|
||||
ConvoEvent,
|
||||
ConvoItem,
|
||||
ConvoItemError,
|
||||
ConvoParams,
|
||||
@@ -82,6 +86,8 @@ export class Convo {
|
||||
|
||||
private lastActiveTimestamp: number | undefined
|
||||
|
||||
private emitter = new EventEmitter<{event: [ConvoEvent]}>()
|
||||
|
||||
convoId: string
|
||||
convo: ChatBskyConvoDefs.ConvoView | undefined
|
||||
sender: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||
@@ -587,7 +593,7 @@ export class Convo {
|
||||
logger.error('Convo: failed to fetch message history')
|
||||
|
||||
this.headerItems.set(ConvoItemError.HistoryFailed, {
|
||||
type: 'error-recoverable',
|
||||
type: 'error',
|
||||
key: ConvoItemError.HistoryFailed,
|
||||
code: ConvoItemError.HistoryFailed,
|
||||
retry: () => {
|
||||
@@ -635,7 +641,7 @@ export class Convo {
|
||||
|
||||
onFirehoseError(error?: MessagesEventBusError) {
|
||||
this.footerItems.set(ConvoItemError.FirehoseFailed, {
|
||||
type: 'error-recoverable',
|
||||
type: 'error',
|
||||
key: ConvoItemError.FirehoseFailed,
|
||||
code: ConvoItemError.FirehoseFailed,
|
||||
retry: () => {
|
||||
@@ -724,7 +730,7 @@ export class Convo {
|
||||
}
|
||||
}
|
||||
|
||||
private pendingFailed = false
|
||||
private pendingMessageFailure: 'recoverable' | 'unrecoverable' | null = null
|
||||
|
||||
async sendMessage(message: ChatBskyConvoSendMessage.InputSchema['message']) {
|
||||
// Ignore empty messages for now since they have no other purpose atm
|
||||
@@ -740,7 +746,7 @@ export class Convo {
|
||||
})
|
||||
this.commit()
|
||||
|
||||
if (!this.isProcessingPendingMessages && !this.pendingFailed) {
|
||||
if (!this.isProcessingPendingMessages && !this.pendingMessageFailure) {
|
||||
this.processPendingMessages()
|
||||
}
|
||||
}
|
||||
@@ -765,7 +771,6 @@ export class Convo {
|
||||
try {
|
||||
this.isProcessingPendingMessages = true
|
||||
|
||||
// throw new Error('UNCOMMENT TO TEST RETRY')
|
||||
const {id, message} = pendingMessage
|
||||
|
||||
const response = await networkRetry(2, () => {
|
||||
@@ -794,13 +799,47 @@ export class Convo {
|
||||
this.commit()
|
||||
} catch (e: any) {
|
||||
logger.error(e, {context: `Convo: failed to send message`})
|
||||
this.pendingFailed = true
|
||||
this.commit()
|
||||
this.handleSendMessageFailure(e)
|
||||
} finally {
|
||||
this.isProcessingPendingMessages = false
|
||||
}
|
||||
}
|
||||
|
||||
private handleSendMessageFailure(e: any) {
|
||||
if (e instanceof XRPCError) {
|
||||
if (NETWORK_FAILURE_STATUSES.includes(e.status)) {
|
||||
this.pendingMessageFailure = 'recoverable'
|
||||
} else {
|
||||
switch (e.message) {
|
||||
case 'block between recipient and sender':
|
||||
this.pendingMessageFailure = 'unrecoverable'
|
||||
this.emitter.emit('event', {type: 'sync-convo-state'})
|
||||
break
|
||||
default:
|
||||
logger.warn(
|
||||
`Convo handleSendMessageFailure could not handle error`,
|
||||
{
|
||||
status: e.status,
|
||||
message: e.message,
|
||||
},
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn(`Convo handleSendMessageFailure received unknown error`, {
|
||||
message: e.message,
|
||||
})
|
||||
this.footerItems.set(ConvoItemError.Unknown, {
|
||||
type: 'error',
|
||||
key: ConvoItemError.Unknown,
|
||||
code: ConvoItemError.Unknown,
|
||||
})
|
||||
}
|
||||
|
||||
this.commit()
|
||||
}
|
||||
|
||||
async batchRetryPendingMessages() {
|
||||
logger.debug(
|
||||
`Convo: retrying ${this.pendingMessages.size} pending messages`,
|
||||
@@ -848,8 +887,7 @@ export class Convo {
|
||||
)
|
||||
} catch (e: any) {
|
||||
logger.error(e, {context: `Convo: failed to batch retry messages`})
|
||||
this.pendingFailed = true
|
||||
this.commit()
|
||||
this.handleSendMessageFailure(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -877,6 +915,14 @@ export class Convo {
|
||||
}
|
||||
}
|
||||
|
||||
on(handler: (event: ConvoEvent) => void) {
|
||||
this.emitter.on('event', handler)
|
||||
|
||||
return () => {
|
||||
this.emitter.off('event', handler)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Items in reverse order, since FlatList inverts
|
||||
*/
|
||||
@@ -940,13 +986,15 @@ export class Convo {
|
||||
sender: this.sender!,
|
||||
},
|
||||
nextMessage: null,
|
||||
retry: this.pendingFailed
|
||||
? () => {
|
||||
this.pendingFailed = false
|
||||
this.commit()
|
||||
this.batchRetryPendingMessages()
|
||||
}
|
||||
: undefined,
|
||||
failed: this.pendingMessageFailure !== null,
|
||||
retry:
|
||||
this.pendingMessageFailure === 'recoverable'
|
||||
? () => {
|
||||
this.pendingMessageFailure = null
|
||||
this.commit()
|
||||
this.batchRetryPendingMessages()
|
||||
}
|
||||
: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
export const ACTIVE_POLL_INTERVAL = 1e3
|
||||
export const BACKGROUND_POLL_INTERVAL = 5e3
|
||||
export const INACTIVE_TIMEOUT = 60e3 * 5
|
||||
|
||||
export const NETWORK_FAILURE_STATUSES = [
|
||||
1, 408, 425, 429, 500, 502, 503, 504, 522, 524,
|
||||
]
|
||||
|
||||
@@ -78,6 +78,16 @@ export function ConvoProvider({
|
||||
}, [convo, convoId, markAsRead]),
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
return convo.on(event => {
|
||||
switch (event.type) {
|
||||
case 'sync-convo-state': {
|
||||
console.log('SYNC')
|
||||
}
|
||||
}
|
||||
})
|
||||
}, [convo])
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleAppStateChange = (nextAppState: string) => {
|
||||
if (isScreenFocused) {
|
||||
|
||||
@@ -26,7 +26,7 @@ export enum ConvoItemError {
|
||||
/**
|
||||
* Generic error
|
||||
*/
|
||||
Network = 'network',
|
||||
Unknown = 'unknown',
|
||||
/**
|
||||
* Error connecting to event firehose
|
||||
*/
|
||||
@@ -35,6 +35,10 @@ export enum ConvoItemError {
|
||||
* Error fetching past messages
|
||||
*/
|
||||
HistoryFailed = 'historyFailed',
|
||||
/**
|
||||
* Recipient is blocking the user
|
||||
*/
|
||||
UserBlocked = 'userBlocked',
|
||||
}
|
||||
|
||||
export enum ConvoErrorCode {
|
||||
@@ -95,6 +99,7 @@ export type ConvoItem =
|
||||
| ChatBskyConvoDefs.MessageView
|
||||
| ChatBskyConvoDefs.DeletedMessageView
|
||||
| null
|
||||
failed: boolean
|
||||
/**
|
||||
* Retry sending the message. If present, the message is in a failed state.
|
||||
*/
|
||||
@@ -110,10 +115,13 @@ export type ConvoItem =
|
||||
| null
|
||||
}
|
||||
| {
|
||||
type: 'error-recoverable'
|
||||
type: 'error'
|
||||
key: string
|
||||
code: ConvoItemError
|
||||
retry: () => void
|
||||
/**
|
||||
* If present, error is recoverable.
|
||||
*/
|
||||
retry?: () => void
|
||||
}
|
||||
|
||||
type DeleteMessage = (messageId: string) => Promise<void>
|
||||
@@ -201,3 +209,7 @@ export type ConvoState =
|
||||
| ConvoStateBackgrounded
|
||||
| ConvoStateSuspended
|
||||
| ConvoStateError
|
||||
|
||||
export type ConvoEvent = {
|
||||
type: 'sync-convo-state'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user