[Clipclops] All my clops gone (#3850)
* Handle two common errors, provide more clarity around error states * Handle failed polling * Remove unused error type * formatzio/stable
parent
2a1dbd2756
commit
0b6ace990e
|
@ -3,7 +3,7 @@ import {View} from 'react-native'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {ConvoError, ConvoItem} from '#/state/messages/convo'
|
import {ConvoItem, ConvoItemError} from '#/state/messages/convo'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||||
import {InlineLinkText} from '#/components/Link'
|
import {InlineLinkText} from '#/components/Link'
|
||||||
|
@ -18,7 +18,13 @@ export function MessageListError({
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const message = React.useMemo(() => {
|
const message = React.useMemo(() => {
|
||||||
return {
|
return {
|
||||||
[ConvoError.HistoryFailed]: _(msg`Failed to load past messages.`),
|
[ConvoItemError.HistoryFailed]: _(msg`Failed to load past messages.`),
|
||||||
|
[ConvoItemError.ResumeFailed]: _(
|
||||||
|
msg`There was an issue connecting to the chat.`,
|
||||||
|
),
|
||||||
|
[ConvoItemError.PollFailed]: _(
|
||||||
|
msg`This chat was disconnected due to a network error.`,
|
||||||
|
),
|
||||||
}[item.code]
|
}[item.code]
|
||||||
}, [_, item.code])
|
}, [_, item.code])
|
||||||
|
|
||||||
|
|
|
@ -229,7 +229,7 @@ export function MessagesList() {
|
||||||
<ScrollProvider onScroll={onScroll} onMomentumEnd={onMomentumEnd}>
|
<ScrollProvider onScroll={onScroll} onMomentumEnd={onMomentumEnd}>
|
||||||
<List
|
<List
|
||||||
ref={flatListRef}
|
ref={flatListRef}
|
||||||
data={chat.status === ConvoStatus.Ready ? chat.items : undefined}
|
data={chat.items}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
disableVirtualization={true}
|
disableVirtualization={true}
|
||||||
|
@ -248,11 +248,7 @@ export function MessagesList() {
|
||||||
onScrollToIndexFailed={onScrollToIndexFailed}
|
onScrollToIndexFailed={onScrollToIndexFailed}
|
||||||
scrollEventThrottle={100}
|
scrollEventThrottle={100}
|
||||||
ListHeaderComponent={
|
ListHeaderComponent={
|
||||||
<MaybeLoader
|
<MaybeLoader isLoading={chat.isFetchingHistory} />
|
||||||
isLoading={
|
|
||||||
chat.status === ConvoStatus.Ready && chat.isFetchingHistory
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</ScrollProvider>
|
</ScrollProvider>
|
||||||
|
|
|
@ -14,7 +14,6 @@ import {BACK_HITSLOP} from 'lib/constants'
|
||||||
import {isWeb} from 'platform/detection'
|
import {isWeb} from 'platform/detection'
|
||||||
import {ChatProvider, useChat} from 'state/messages'
|
import {ChatProvider, useChat} from 'state/messages'
|
||||||
import {ConvoStatus} from 'state/messages/convo'
|
import {ConvoStatus} from 'state/messages/convo'
|
||||||
import {useSession} from 'state/session'
|
|
||||||
import {PreviewableUserAvatar} from 'view/com/util/UserAvatar'
|
import {PreviewableUserAvatar} from 'view/com/util/UserAvatar'
|
||||||
import {CenteredView} from 'view/com/util/Views'
|
import {CenteredView} from 'view/com/util/Views'
|
||||||
import {MessagesList} from '#/screens/Messages/Conversation/MessagesList'
|
import {MessagesList} from '#/screens/Messages/Conversation/MessagesList'
|
||||||
|
@ -43,28 +42,27 @@ export function MessagesConversationScreen({route}: Props) {
|
||||||
|
|
||||||
function Inner() {
|
function Inner() {
|
||||||
const chat = useChat()
|
const chat = useChat()
|
||||||
const {currentAccount} = useSession()
|
|
||||||
const myDid = currentAccount?.did
|
|
||||||
|
|
||||||
const otherProfile = React.useMemo(() => {
|
if (
|
||||||
if (chat.status !== ConvoStatus.Ready) return
|
chat.status === ConvoStatus.Uninitialized ||
|
||||||
return chat.convo.members.find(m => m.did !== myDid)
|
chat.status === ConvoStatus.Initializing
|
||||||
}, [chat, myDid])
|
) {
|
||||||
|
return <ListMaybePlaceholder isLoading />
|
||||||
// TODO whenever we have error messages, we should use them in here -hailey
|
|
||||||
if (chat.status !== ConvoStatus.Ready || !otherProfile) {
|
|
||||||
return (
|
|
||||||
<ListMaybePlaceholder
|
|
||||||
isLoading={true}
|
|
||||||
isError={chat.status === ConvoStatus.Error}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (chat.status === ConvoStatus.Error) {
|
||||||
|
// TODO error
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Any other chat states (atm) are "ready" states
|
||||||
|
*/
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardProvider>
|
<KeyboardProvider>
|
||||||
<CenteredView style={{flex: 1}} sideBorders>
|
<CenteredView style={{flex: 1}} sideBorders>
|
||||||
<Header profile={otherProfile} />
|
<Header profile={chat.recipients[0]} />
|
||||||
<MessagesList />
|
<MessagesList />
|
||||||
</CenteredView>
|
</CenteredView>
|
||||||
</KeyboardProvider>
|
</KeyboardProvider>
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
import {describe, it} from '@jest/globals'
|
import {describe, it} from '@jest/globals'
|
||||||
|
|
||||||
describe(`#/state/messages/convo`, () => {
|
describe(`#/state/messages/convo`, () => {
|
||||||
describe(`status states`, () => {
|
describe(`init`, () => {
|
||||||
|
it.todo(`fails if sender and recipients aren't found`)
|
||||||
it.todo(`cannot re-initialize from a non-unintialized state`)
|
it.todo(`cannot re-initialize from a non-unintialized state`)
|
||||||
it.todo(`can re-initialize from a failed state`)
|
it.todo(`can re-initialize from a failed state`)
|
||||||
|
})
|
||||||
|
|
||||||
describe(`destroy`, () => {
|
describe(`resume`, () => {
|
||||||
it.todo(`cannot be interacted with when destroyed`)
|
it.todo(`restores previous state if resume fails`)
|
||||||
it.todo(`polling is stopped when destroyed`)
|
})
|
||||||
it.todo(`events are cleaned up when destroyed`)
|
|
||||||
})
|
describe(`suspend`, () => {
|
||||||
|
it.todo(`cannot be interacted with when suspended`)
|
||||||
|
it.todo(`polling is stopped when suspended`)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe(`read states`, () => {
|
describe(`read states`, () => {
|
||||||
|
|
|
@ -25,8 +25,14 @@ export enum ConvoStatus {
|
||||||
Suspended = 'suspended',
|
Suspended = 'suspended',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ConvoError {
|
export enum ConvoItemError {
|
||||||
HistoryFailed = 'historyFailed',
|
HistoryFailed = 'historyFailed',
|
||||||
|
ResumeFailed = 'resumeFailed',
|
||||||
|
PollFailed = 'pollFailed',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ConvoError {
|
||||||
|
InitFailed = 'initFailed',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ConvoItem =
|
export type ConvoItem =
|
||||||
|
@ -56,14 +62,9 @@ export type ConvoItem =
|
||||||
| {
|
| {
|
||||||
type: 'error-recoverable'
|
type: 'error-recoverable'
|
||||||
key: string
|
key: string
|
||||||
code: ConvoError
|
code: ConvoItemError
|
||||||
retry: () => void
|
retry: () => void
|
||||||
}
|
}
|
||||||
| {
|
|
||||||
type: 'error-fatal'
|
|
||||||
code: ConvoError
|
|
||||||
key: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ConvoState =
|
export type ConvoState =
|
||||||
| {
|
| {
|
||||||
|
@ -71,6 +72,8 @@ export type ConvoState =
|
||||||
items: []
|
items: []
|
||||||
convo: undefined
|
convo: undefined
|
||||||
error: undefined
|
error: undefined
|
||||||
|
sender: undefined
|
||||||
|
recipients: undefined
|
||||||
isFetchingHistory: false
|
isFetchingHistory: false
|
||||||
deleteMessage: undefined
|
deleteMessage: undefined
|
||||||
sendMessage: undefined
|
sendMessage: undefined
|
||||||
|
@ -81,6 +84,8 @@ export type ConvoState =
|
||||||
items: []
|
items: []
|
||||||
convo: undefined
|
convo: undefined
|
||||||
error: undefined
|
error: undefined
|
||||||
|
sender: undefined
|
||||||
|
recipients: undefined
|
||||||
isFetchingHistory: boolean
|
isFetchingHistory: boolean
|
||||||
deleteMessage: undefined
|
deleteMessage: undefined
|
||||||
sendMessage: undefined
|
sendMessage: undefined
|
||||||
|
@ -91,6 +96,8 @@ export type ConvoState =
|
||||||
items: ConvoItem[]
|
items: ConvoItem[]
|
||||||
convo: ChatBskyConvoDefs.ConvoView
|
convo: ChatBskyConvoDefs.ConvoView
|
||||||
error: undefined
|
error: undefined
|
||||||
|
sender: AppBskyActorDefs.ProfileViewBasic
|
||||||
|
recipients: AppBskyActorDefs.ProfileViewBasic[]
|
||||||
isFetchingHistory: boolean
|
isFetchingHistory: boolean
|
||||||
deleteMessage: (messageId: string) => Promise<void>
|
deleteMessage: (messageId: string) => Promise<void>
|
||||||
sendMessage: (
|
sendMessage: (
|
||||||
|
@ -103,6 +110,8 @@ export type ConvoState =
|
||||||
items: ConvoItem[]
|
items: ConvoItem[]
|
||||||
convo: ChatBskyConvoDefs.ConvoView
|
convo: ChatBskyConvoDefs.ConvoView
|
||||||
error: undefined
|
error: undefined
|
||||||
|
sender: AppBskyActorDefs.ProfileViewBasic
|
||||||
|
recipients: AppBskyActorDefs.ProfileViewBasic[]
|
||||||
isFetchingHistory: boolean
|
isFetchingHistory: boolean
|
||||||
deleteMessage: (messageId: string) => Promise<void>
|
deleteMessage: (messageId: string) => Promise<void>
|
||||||
sendMessage: (
|
sendMessage: (
|
||||||
|
@ -115,6 +124,8 @@ export type ConvoState =
|
||||||
items: ConvoItem[]
|
items: ConvoItem[]
|
||||||
convo: ChatBskyConvoDefs.ConvoView
|
convo: ChatBskyConvoDefs.ConvoView
|
||||||
error: undefined
|
error: undefined
|
||||||
|
sender: AppBskyActorDefs.ProfileViewBasic
|
||||||
|
recipients: AppBskyActorDefs.ProfileViewBasic[]
|
||||||
isFetchingHistory: boolean
|
isFetchingHistory: boolean
|
||||||
deleteMessage: (messageId: string) => Promise<void>
|
deleteMessage: (messageId: string) => Promise<void>
|
||||||
sendMessage: (
|
sendMessage: (
|
||||||
|
@ -127,6 +138,8 @@ export type ConvoState =
|
||||||
items: ConvoItem[]
|
items: ConvoItem[]
|
||||||
convo: ChatBskyConvoDefs.ConvoView
|
convo: ChatBskyConvoDefs.ConvoView
|
||||||
error: undefined
|
error: undefined
|
||||||
|
sender: AppBskyActorDefs.ProfileViewBasic
|
||||||
|
recipients: AppBskyActorDefs.ProfileViewBasic[]
|
||||||
isFetchingHistory: boolean
|
isFetchingHistory: boolean
|
||||||
deleteMessage: (messageId: string) => Promise<void>
|
deleteMessage: (messageId: string) => Promise<void>
|
||||||
sendMessage: (
|
sendMessage: (
|
||||||
|
@ -139,6 +152,8 @@ export type ConvoState =
|
||||||
items: []
|
items: []
|
||||||
convo: undefined
|
convo: undefined
|
||||||
error: any
|
error: any
|
||||||
|
sender: undefined
|
||||||
|
recipients: undefined
|
||||||
isFetchingHistory: false
|
isFetchingHistory: false
|
||||||
deleteMessage: undefined
|
deleteMessage: undefined
|
||||||
sendMessage: undefined
|
sendMessage: undefined
|
||||||
|
@ -165,10 +180,17 @@ export class Convo {
|
||||||
|
|
||||||
private pollInterval = ACTIVE_POLL_INTERVAL
|
private pollInterval = ACTIVE_POLL_INTERVAL
|
||||||
private status: ConvoStatus = ConvoStatus.Uninitialized
|
private status: ConvoStatus = ConvoStatus.Uninitialized
|
||||||
private error: any
|
private error:
|
||||||
|
| {
|
||||||
|
code: ConvoError
|
||||||
|
exception?: Error
|
||||||
|
retry: () => void
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
private historyCursor: string | undefined | null = undefined
|
private historyCursor: string | undefined | null = undefined
|
||||||
private isFetchingHistory = false
|
private isFetchingHistory = false
|
||||||
private eventsCursor: string | undefined = undefined
|
private eventsCursor: string | undefined = undefined
|
||||||
|
private pollingFailure = false
|
||||||
|
|
||||||
private pastMessages: Map<
|
private pastMessages: Map<
|
||||||
string,
|
string,
|
||||||
|
@ -192,6 +214,7 @@ export class Convo {
|
||||||
convoId: string
|
convoId: string
|
||||||
convo: ChatBskyConvoDefs.ConvoView | undefined
|
convo: ChatBskyConvoDefs.ConvoView | undefined
|
||||||
sender: AppBskyActorDefs.ProfileViewBasic | undefined
|
sender: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||||
|
recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined = undefined
|
||||||
snapshot: ConvoState | undefined
|
snapshot: ConvoState | undefined
|
||||||
|
|
||||||
constructor(params: ConvoParams) {
|
constructor(params: ConvoParams) {
|
||||||
|
@ -226,7 +249,7 @@ export class Convo {
|
||||||
|
|
||||||
getSnapshot(): ConvoState {
|
getSnapshot(): ConvoState {
|
||||||
if (!this.snapshot) this.snapshot = this.generateSnapshot()
|
if (!this.snapshot) this.snapshot = this.generateSnapshot()
|
||||||
logger.debug('Convo: snapshotted', {}, logger.DebugContext.convo)
|
// logger.debug('Convo: snapshotted', {}, logger.DebugContext.convo)
|
||||||
return this.snapshot
|
return this.snapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,6 +261,8 @@ export class Convo {
|
||||||
items: [],
|
items: [],
|
||||||
convo: undefined,
|
convo: undefined,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
sender: undefined,
|
||||||
|
recipients: undefined,
|
||||||
isFetchingHistory: this.isFetchingHistory,
|
isFetchingHistory: this.isFetchingHistory,
|
||||||
deleteMessage: undefined,
|
deleteMessage: undefined,
|
||||||
sendMessage: undefined,
|
sendMessage: undefined,
|
||||||
|
@ -253,6 +278,8 @@ export class Convo {
|
||||||
items: this.getItems(),
|
items: this.getItems(),
|
||||||
convo: this.convo!,
|
convo: this.convo!,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
sender: this.sender!,
|
||||||
|
recipients: this.recipients!,
|
||||||
isFetchingHistory: this.isFetchingHistory,
|
isFetchingHistory: this.isFetchingHistory,
|
||||||
deleteMessage: this.deleteMessage,
|
deleteMessage: this.deleteMessage,
|
||||||
sendMessage: this.sendMessage,
|
sendMessage: this.sendMessage,
|
||||||
|
@ -265,6 +292,8 @@ export class Convo {
|
||||||
items: [],
|
items: [],
|
||||||
convo: undefined,
|
convo: undefined,
|
||||||
error: this.error,
|
error: this.error,
|
||||||
|
sender: undefined,
|
||||||
|
recipients: undefined,
|
||||||
isFetchingHistory: false,
|
isFetchingHistory: false,
|
||||||
deleteMessage: undefined,
|
deleteMessage: undefined,
|
||||||
sendMessage: undefined,
|
sendMessage: undefined,
|
||||||
|
@ -277,6 +306,8 @@ export class Convo {
|
||||||
items: [],
|
items: [],
|
||||||
convo: undefined,
|
convo: undefined,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
sender: undefined,
|
||||||
|
recipients: undefined,
|
||||||
isFetchingHistory: false,
|
isFetchingHistory: false,
|
||||||
deleteMessage: undefined,
|
deleteMessage: undefined,
|
||||||
sendMessage: undefined,
|
sendMessage: undefined,
|
||||||
|
@ -289,7 +320,10 @@ export class Convo {
|
||||||
async init() {
|
async init() {
|
||||||
logger.debug('Convo: init', {}, logger.DebugContext.convo)
|
logger.debug('Convo: init', {}, logger.DebugContext.convo)
|
||||||
|
|
||||||
if (this.status === ConvoStatus.Uninitialized) {
|
if (
|
||||||
|
this.status === ConvoStatus.Uninitialized ||
|
||||||
|
this.status === ConvoStatus.Error
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
this.status = ConvoStatus.Initializing
|
this.status = ConvoStatus.Initializing
|
||||||
this.commit()
|
this.commit()
|
||||||
|
@ -301,8 +335,16 @@ export class Convo {
|
||||||
await this.fetchMessageHistory()
|
await this.fetchMessageHistory()
|
||||||
|
|
||||||
this.pollEvents()
|
this.pollEvents()
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
this.error = e
|
logger.error('Convo: failed to init')
|
||||||
|
this.error = {
|
||||||
|
exception: e,
|
||||||
|
code: ConvoError.InitFailed,
|
||||||
|
retry: () => {
|
||||||
|
this.error = undefined
|
||||||
|
this.init()
|
||||||
|
},
|
||||||
|
}
|
||||||
this.status = ConvoStatus.Error
|
this.status = ConvoStatus.Error
|
||||||
this.commit()
|
this.commit()
|
||||||
}
|
}
|
||||||
|
@ -318,6 +360,8 @@ export class Convo {
|
||||||
this.status === ConvoStatus.Suspended ||
|
this.status === ConvoStatus.Suspended ||
|
||||||
this.status === ConvoStatus.Backgrounded
|
this.status === ConvoStatus.Backgrounded
|
||||||
) {
|
) {
|
||||||
|
const fromStatus = this.status
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.status = ConvoStatus.Resuming
|
this.status = ConvoStatus.Resuming
|
||||||
this.commit()
|
this.commit()
|
||||||
|
@ -326,14 +370,24 @@ export class Convo {
|
||||||
this.status = ConvoStatus.Ready
|
this.status = ConvoStatus.Ready
|
||||||
this.commit()
|
this.commit()
|
||||||
|
|
||||||
await this.fetchMessageHistory()
|
// throw new Error('UNCOMMENT TO TEST RESUME FAILURE')
|
||||||
|
|
||||||
this.pollInterval = ACTIVE_POLL_INTERVAL
|
this.pollInterval = ACTIVE_POLL_INTERVAL
|
||||||
this.pollEvents()
|
this.pollEvents()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO handle errors in one place
|
logger.error('Convo: failed to resume')
|
||||||
this.error = e
|
|
||||||
this.status = ConvoStatus.Error
|
this.footerItems.set(ConvoItemError.ResumeFailed, {
|
||||||
|
type: 'error-recoverable',
|
||||||
|
key: ConvoItemError.ResumeFailed,
|
||||||
|
code: ConvoItemError.ResumeFailed,
|
||||||
|
retry: () => {
|
||||||
|
this.footerItems.delete(ConvoItemError.ResumeFailed)
|
||||||
|
this.resume()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
this.status = fromStatus
|
||||||
this.commit()
|
this.commit()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -367,6 +421,19 @@ export class Convo {
|
||||||
)
|
)
|
||||||
this.convo = response.data.convo
|
this.convo = response.data.convo
|
||||||
this.sender = this.convo.members.find(m => m.did === this.__tempFromUserDid)
|
this.sender = this.convo.members.find(m => m.did === this.__tempFromUserDid)
|
||||||
|
this.recipients = this.convo.members.filter(
|
||||||
|
m => m.did !== this.__tempFromUserDid,
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prevent invalid states
|
||||||
|
*/
|
||||||
|
if (!this.sender) {
|
||||||
|
throw new Error('Convo: could not find sender in convo')
|
||||||
|
}
|
||||||
|
if (!this.recipients) {
|
||||||
|
throw new Error('Convo: could not find recipients in convo')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchMessageHistory() {
|
async fetchMessageHistory() {
|
||||||
|
@ -386,7 +453,7 @@ export class Convo {
|
||||||
* If we've rendered a retry state for history fetching, exit. Upon retry,
|
* If we've rendered a retry state for history fetching, exit. Upon retry,
|
||||||
* this will be removed and we'll try again.
|
* this will be removed and we'll try again.
|
||||||
*/
|
*/
|
||||||
if (this.headerItems.has(ConvoError.HistoryFailed)) return
|
if (this.headerItems.has(ConvoItemError.HistoryFailed)) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.isFetchingHistory = true
|
this.isFetchingHistory = true
|
||||||
|
@ -435,12 +502,12 @@ export class Convo {
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.error('Convo: failed to fetch message history')
|
logger.error('Convo: failed to fetch message history')
|
||||||
|
|
||||||
this.headerItems.set(ConvoError.HistoryFailed, {
|
this.headerItems.set(ConvoItemError.HistoryFailed, {
|
||||||
type: 'error-recoverable',
|
type: 'error-recoverable',
|
||||||
key: ConvoError.HistoryFailed,
|
key: ConvoItemError.HistoryFailed,
|
||||||
code: ConvoError.HistoryFailed,
|
code: ConvoItemError.HistoryFailed,
|
||||||
retry: () => {
|
retry: () => {
|
||||||
this.headerItems.delete(ConvoError.HistoryFailed)
|
this.headerItems.delete(ConvoItemError.HistoryFailed)
|
||||||
this.fetchMessageHistory()
|
this.fetchMessageHistory()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -457,6 +524,11 @@ export class Convo {
|
||||||
) {
|
) {
|
||||||
if (this.pendingEventIngestion) return
|
if (this.pendingEventIngestion) return
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Represents a failed state, which is retryable.
|
||||||
|
*/
|
||||||
|
if (this.pollingFailure) return
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
this.pendingEventIngestion = this.ingestLatestEvents()
|
this.pendingEventIngestion = this.ingestLatestEvents()
|
||||||
await this.pendingEventIngestion
|
await this.pendingEventIngestion
|
||||||
|
@ -467,76 +539,94 @@ export class Convo {
|
||||||
}
|
}
|
||||||
|
|
||||||
async ingestLatestEvents() {
|
async ingestLatestEvents() {
|
||||||
const response = await this.agent.api.chat.bsky.convo.getLog(
|
try {
|
||||||
{
|
// throw new Error('UNCOMMENT TO TEST POLL FAILURE')
|
||||||
cursor: this.eventsCursor,
|
const response = await this.agent.api.chat.bsky.convo.getLog(
|
||||||
},
|
{
|
||||||
{
|
cursor: this.eventsCursor,
|
||||||
headers: {
|
|
||||||
Authorization: this.__tempFromUserDid,
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
)
|
headers: {
|
||||||
const {logs} = response.data
|
Authorization: this.__tempFromUserDid,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
const {logs} = response.data
|
||||||
|
|
||||||
let needsCommit = false
|
let needsCommit = false
|
||||||
|
|
||||||
for (const log of logs) {
|
for (const log of logs) {
|
||||||
/*
|
|
||||||
* If there's a rev, we should handle it. If there's not a rev, we don't
|
|
||||||
* know what it is.
|
|
||||||
*/
|
|
||||||
if (typeof log.rev === 'string') {
|
|
||||||
/*
|
/*
|
||||||
* We only care about new events
|
* If there's a rev, we should handle it. If there's not a rev, we don't
|
||||||
|
* know what it is.
|
||||||
*/
|
*/
|
||||||
if (log.rev > (this.eventsCursor = this.eventsCursor || log.rev)) {
|
if (typeof log.rev === 'string') {
|
||||||
/*
|
/*
|
||||||
* Update rev regardless of if it's a log type we care about or not
|
* We only care about new events
|
||||||
*/
|
*/
|
||||||
this.eventsCursor = log.rev
|
if (log.rev > (this.eventsCursor = this.eventsCursor || log.rev)) {
|
||||||
|
|
||||||
/*
|
|
||||||
* This is VERY important. We don't want to insert any messages from
|
|
||||||
* your other chats.
|
|
||||||
*/
|
|
||||||
if (log.convoId !== this.convoId) continue
|
|
||||||
|
|
||||||
if (
|
|
||||||
ChatBskyConvoDefs.isLogCreateMessage(log) &&
|
|
||||||
ChatBskyConvoDefs.isMessageView(log.message)
|
|
||||||
) {
|
|
||||||
if (this.newMessages.has(log.message.id)) {
|
|
||||||
// Trust the log as the source of truth on ordering
|
|
||||||
this.newMessages.delete(log.message.id)
|
|
||||||
}
|
|
||||||
this.newMessages.set(log.message.id, log.message)
|
|
||||||
needsCommit = true
|
|
||||||
} else if (
|
|
||||||
ChatBskyConvoDefs.isLogDeleteMessage(log) &&
|
|
||||||
ChatBskyConvoDefs.isDeletedMessageView(log.message)
|
|
||||||
) {
|
|
||||||
/*
|
/*
|
||||||
* Update if we have this in state. If we don't, don't worry about it.
|
* Update rev regardless of if it's a log type we care about or not
|
||||||
*/
|
*/
|
||||||
if (this.pastMessages.has(log.message.id)) {
|
this.eventsCursor = log.rev
|
||||||
/*
|
|
||||||
* For now, we remove deleted messages from the thread, if we receive one.
|
/*
|
||||||
*
|
* This is VERY important. We don't want to insert any messages from
|
||||||
* To support them, it'd look something like this:
|
* your other chats.
|
||||||
* this.pastMessages.set(log.message.id, log.message)
|
*/
|
||||||
*/
|
if (log.convoId !== this.convoId) continue
|
||||||
this.pastMessages.delete(log.message.id)
|
|
||||||
this.newMessages.delete(log.message.id)
|
if (
|
||||||
this.deletedMessages.delete(log.message.id)
|
ChatBskyConvoDefs.isLogCreateMessage(log) &&
|
||||||
|
ChatBskyConvoDefs.isMessageView(log.message)
|
||||||
|
) {
|
||||||
|
if (this.newMessages.has(log.message.id)) {
|
||||||
|
// Trust the log as the source of truth on ordering
|
||||||
|
this.newMessages.delete(log.message.id)
|
||||||
|
}
|
||||||
|
this.newMessages.set(log.message.id, log.message)
|
||||||
needsCommit = true
|
needsCommit = true
|
||||||
|
} else if (
|
||||||
|
ChatBskyConvoDefs.isLogDeleteMessage(log) &&
|
||||||
|
ChatBskyConvoDefs.isDeletedMessageView(log.message)
|
||||||
|
) {
|
||||||
|
/*
|
||||||
|
* Update if we have this in state. If we don't, don't worry about it.
|
||||||
|
*/
|
||||||
|
if (this.pastMessages.has(log.message.id)) {
|
||||||
|
/*
|
||||||
|
* For now, we remove deleted messages from the thread, if we receive one.
|
||||||
|
*
|
||||||
|
* To support them, it'd look something like this:
|
||||||
|
* this.pastMessages.set(log.message.id, log.message)
|
||||||
|
*/
|
||||||
|
this.pastMessages.delete(log.message.id)
|
||||||
|
this.newMessages.delete(log.message.id)
|
||||||
|
this.deletedMessages.delete(log.message.id)
|
||||||
|
needsCommit = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (needsCommit) {
|
if (needsCommit) {
|
||||||
|
this.commit()
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error('Convo: failed to poll events')
|
||||||
|
this.pollingFailure = true
|
||||||
|
this.footerItems.set(ConvoItemError.PollFailed, {
|
||||||
|
type: 'error-recoverable',
|
||||||
|
key: ConvoItemError.PollFailed,
|
||||||
|
code: ConvoItemError.PollFailed,
|
||||||
|
retry: () => {
|
||||||
|
this.footerItems.delete(ConvoItemError.PollFailed)
|
||||||
|
this.pollingFailure = false
|
||||||
|
this.commit()
|
||||||
|
this.pollEvents()
|
||||||
|
},
|
||||||
|
})
|
||||||
this.commit()
|
this.commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue