From 051e897a2b9a2dc60efe7b032b5496cdbcfd6374 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Fri, 3 May 2024 09:39:40 -0500 Subject: [PATCH] [Clipclops] Moar error (#3837) * Add history error * Log error * Add period --- .../Conversation/MessageListError.tsx | 54 +++++++++ .../Messages/Conversation/MessagesList.tsx | 3 + src/state/messages/convo.ts | 109 ++++++++++++------ 3 files changed, 132 insertions(+), 34 deletions(-) create mode 100644 src/screens/Messages/Conversation/MessageListError.tsx diff --git a/src/screens/Messages/Conversation/MessageListError.tsx b/src/screens/Messages/Conversation/MessageListError.tsx new file mode 100644 index 0000000000..82ca48e8b4 --- /dev/null +++ b/src/screens/Messages/Conversation/MessageListError.tsx @@ -0,0 +1,54 @@ +import React from 'react' +import {View} from 'react-native' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {ConvoError, ConvoItem} from '#/state/messages/convo' +import {atoms as a, useTheme} from '#/alf' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' +import {InlineLinkText} from '#/components/Link' +import {Text} from '#/components/Typography' + +export function MessageListError({ + item, +}: { + item: ConvoItem & {type: 'error-recoverable'} +}) { + const t = useTheme() + const {_} = useLingui() + const message = React.useMemo(() => { + return { + [ConvoError.HistoryFailed]: _(msg`Failed to load past messages.`), + }[item.code] + }, [_, item.code]) + + return ( + + + + + {message}{' '} + { + e.preventDefault() + item.retry() + return false + }}> + {_(msg`Retry.`)} + + + + + ) +} diff --git a/src/screens/Messages/Conversation/MessagesList.tsx b/src/screens/Messages/Conversation/MessagesList.tsx index 3990a1deaa..86a10d8c4f 100644 --- a/src/screens/Messages/Conversation/MessagesList.tsx +++ b/src/screens/Messages/Conversation/MessagesList.tsx @@ -17,6 +17,7 @@ import {useChat} from '#/state/messages' import {ConvoItem, ConvoStatus} from '#/state/messages/convo' import {useSetMinimalShellMode} from '#/state/shell' import {MessageInput} from '#/screens/Messages/Conversation/MessageInput' +import {MessageListError} from '#/screens/Messages/Conversation/MessageListError' import {atoms as a} from '#/alf' import {Button, ButtonText} from '#/components/Button' import {MessageItem} from '#/components/dms/MessageItem' @@ -63,6 +64,8 @@ function renderItem({item}: {item: ConvoItem}) { return Deleted message } else if (item.type === 'pending-retry') { return + } else if (item.type === 'error-recoverable') { + return } return null diff --git a/src/state/messages/convo.ts b/src/state/messages/convo.ts index f687008e5e..fe2095c462 100644 --- a/src/state/messages/convo.ts +++ b/src/state/messages/convo.ts @@ -25,6 +25,10 @@ export enum ConvoStatus { Suspended = 'suspended', } +export enum ConvoError { + HistoryFailed = 'historyFailed', +} + export type ConvoItem = | { type: 'message' | 'pending-message' @@ -49,6 +53,17 @@ export type ConvoItem = key: string retry: () => void } + | { + type: 'error-recoverable' + key: string + code: ConvoError + retry: () => void + } + | { + type: 'error-fatal' + code: ConvoError + key: string + } export type ConvoState = | { @@ -169,6 +184,7 @@ export class Convo { > = new Map() private deletedMessages: Set = new Set() private footerItems: Map = new Map() + private headerItems: Map = new Map() private pendingEventIngestion: Promise | undefined private isProcessingPendingMessages = false @@ -366,51 +382,72 @@ export class Convo { */ if (this.isFetchingHistory) return - this.isFetchingHistory = true - this.commit() - /* - * Delay if paginating while scrolled to prevent momentum scrolling from - * jerking the list around, plus makes it feel a little more human. + * If we've rendered a retry state for history fetching, exit. Upon retry, + * this will be removed and we'll try again. */ - if (this.pastMessages.size > 0) { - await new Promise(y => setTimeout(y, 500)) - } + if (this.headerItems.has(ConvoError.HistoryFailed)) return - const response = await this.agent.api.chat.bsky.convo.getMessages( - { - cursor: this.historyCursor, - convoId: this.convoId, - limit: isNative ? 25 : 50, - }, - { - headers: { - Authorization: this.__tempFromUserDid, - }, - }, - ) - const {cursor, messages} = response.data + try { + this.isFetchingHistory = true + this.commit() - this.historyCursor = cursor || null + /* + * Delay if paginating while scrolled to prevent momentum scrolling from + * jerking the list around, plus makes it feel a little more human. + */ + if (this.pastMessages.size > 0) { + await new Promise(y => setTimeout(y, 500)) + // throw new Error('UNCOMMENT TO TEST RETRY') + } + + const response = await this.agent.api.chat.bsky.convo.getMessages( + { + cursor: this.historyCursor, + convoId: this.convoId, + limit: isNative ? 25 : 50, + }, + { + headers: { + Authorization: this.__tempFromUserDid, + }, + }, + ) + const {cursor, messages} = response.data - for (const message of messages) { - if ( - ChatBskyConvoDefs.isMessageView(message) || - ChatBskyConvoDefs.isDeletedMessageView(message) - ) { - this.pastMessages.set(message.id, message) + this.historyCursor = cursor ?? null - // set to latest rev + for (const message of messages) { if ( - message.rev > (this.eventsCursor = this.eventsCursor || message.rev) + ChatBskyConvoDefs.isMessageView(message) || + ChatBskyConvoDefs.isDeletedMessageView(message) ) { - this.eventsCursor = message.rev + this.pastMessages.set(message.id, message) + + // set to latest rev + if ( + message.rev > (this.eventsCursor = this.eventsCursor || message.rev) + ) { + this.eventsCursor = message.rev + } } } + } catch (e: any) { + logger.error('Convo: failed to fetch message history') + + this.headerItems.set(ConvoError.HistoryFailed, { + type: 'error-recoverable', + key: ConvoError.HistoryFailed, + code: ConvoError.HistoryFailed, + retry: () => { + this.headerItems.delete(ConvoError.HistoryFailed) + this.fetchMessageHistory() + }, + }) + } finally { + this.isFetchingHistory = false + this.commit() } - - this.isFetchingHistory = false - this.commit() } private async pollEvents() { @@ -730,6 +767,10 @@ export class Convo { } }) + this.headerItems.forEach(item => { + items.push(item) + }) + return items .filter(item => { if (isConvoItemMessage(item)) {