Reducer-esque
This commit is contained in:
@@ -18,10 +18,10 @@ export function MessageListError({
|
|||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const message = React.useMemo(() => {
|
const message = React.useMemo(() => {
|
||||||
return {
|
return {
|
||||||
[ConvoItemError.HistoryFailed]: _(msg`Failed to load past messages.`),
|
[ConvoItemError.Network]: _(
|
||||||
[ConvoItemError.ResumeFailed]: _(
|
|
||||||
msg`There was an issue connecting to the chat.`,
|
msg`There was an issue connecting to the chat.`,
|
||||||
),
|
),
|
||||||
|
[ConvoItemError.HistoryFailed]: _(msg`Failed to load past messages.`),
|
||||||
[ConvoItemError.PollFailed]: _(
|
[ConvoItemError.PollFailed]: _(
|
||||||
msg`This chat was disconnected due to a network error.`,
|
msg`This chat was disconnected due to a network error.`,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ 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'
|
||||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||||
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import {ConvoMenu} from '#/components/dms/ConvoMenu'
|
import {ConvoMenu} from '#/components/dms/ConvoMenu'
|
||||||
import {ListMaybePlaceholder} from '#/components/Lists'
|
import {ListMaybePlaceholder} from '#/components/Lists'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
@@ -51,8 +52,21 @@ function Inner() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (chat.status === ConvoStatus.Error) {
|
if (chat.status === ConvoStatus.Error) {
|
||||||
// TODO error
|
// TODO
|
||||||
return null
|
return (
|
||||||
|
<View>
|
||||||
|
<CenteredView style={{flex: 1}} sideBorders>
|
||||||
|
<Text>Something went wrong</Text>
|
||||||
|
<Button
|
||||||
|
label="Retry"
|
||||||
|
onPress={() => {
|
||||||
|
chat.error.retry()
|
||||||
|
}}>
|
||||||
|
<ButtonText>Retry</ButtonText>
|
||||||
|
</Button>
|
||||||
|
</CenteredView>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
+279
-111
@@ -26,14 +26,50 @@ export enum ConvoStatus {
|
|||||||
|
|
||||||
export enum ConvoItemError {
|
export enum ConvoItemError {
|
||||||
HistoryFailed = 'historyFailed',
|
HistoryFailed = 'historyFailed',
|
||||||
ResumeFailed = 'resumeFailed',
|
|
||||||
PollFailed = 'pollFailed',
|
PollFailed = 'pollFailed',
|
||||||
|
Network = 'network',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ConvoError {
|
export enum ConvoErrorCode {
|
||||||
InitFailed = 'initFailed',
|
InitFailed = 'initFailed',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ConvoError = {
|
||||||
|
code: ConvoErrorCode
|
||||||
|
exception?: Error
|
||||||
|
retry: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ConvoDispatchEvent {
|
||||||
|
Init = 'init',
|
||||||
|
Ready = 'ready',
|
||||||
|
Resume = 'resume',
|
||||||
|
Background = 'background',
|
||||||
|
Suspend = 'suspend',
|
||||||
|
Error = 'error',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConvoDispatch =
|
||||||
|
| {
|
||||||
|
event: ConvoDispatchEvent.Init
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
event: ConvoDispatchEvent.Ready
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
event: ConvoDispatchEvent.Resume
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
event: ConvoDispatchEvent.Background
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
event: ConvoDispatchEvent.Suspend
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
event: ConvoDispatchEvent.Error
|
||||||
|
payload: ConvoError
|
||||||
|
}
|
||||||
|
|
||||||
export type ConvoItem =
|
export type ConvoItem =
|
||||||
| {
|
| {
|
||||||
type: 'message' | 'pending-message'
|
type: 'message' | 'pending-message'
|
||||||
@@ -168,11 +204,11 @@ export class Convo {
|
|||||||
private agent: BskyAgent
|
private agent: BskyAgent
|
||||||
private __tempFromUserDid: string
|
private __tempFromUserDid: string
|
||||||
|
|
||||||
private pollInterval = ACTIVE_POLL_INTERVAL
|
|
||||||
private status: ConvoStatus = ConvoStatus.Uninitialized
|
private status: ConvoStatus = ConvoStatus.Uninitialized
|
||||||
|
private pollInterval = ACTIVE_POLL_INTERVAL
|
||||||
private error:
|
private error:
|
||||||
| {
|
| {
|
||||||
code: ConvoError
|
code: ConvoErrorCode
|
||||||
exception?: Error
|
exception?: Error
|
||||||
retry: () => void
|
retry: () => void
|
||||||
}
|
}
|
||||||
@@ -315,113 +351,228 @@ export class Convo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
dispatch(action: ConvoDispatch) {
|
||||||
if (
|
const prevStatus = this.status
|
||||||
this.status === ConvoStatus.Uninitialized ||
|
|
||||||
this.status === ConvoStatus.Error
|
|
||||||
) {
|
|
||||||
logger.debug('Convo: init', {id: this.id}, logger.DebugContext.convo)
|
|
||||||
|
|
||||||
this.status = ConvoStatus.Initializing
|
switch (this.status) {
|
||||||
this.commit()
|
case ConvoStatus.Uninitialized: {
|
||||||
|
switch (action.event) {
|
||||||
this.refreshConvo()
|
case ConvoDispatchEvent.Init: {
|
||||||
.then(async () => {
|
this.status = ConvoStatus.Initializing
|
||||||
if (this.status === ConvoStatus.Initializing) {
|
this.setup()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ConvoStatus.Initializing: {
|
||||||
|
switch (action.event) {
|
||||||
|
case ConvoDispatchEvent.Ready: {
|
||||||
this.status = ConvoStatus.Ready
|
this.status = ConvoStatus.Ready
|
||||||
this.commit()
|
this.pollInterval = ACTIVE_POLL_INTERVAL
|
||||||
|
|
||||||
this.fetchMessageHistory()
|
this.fetchMessageHistory()
|
||||||
this.pollEvents()
|
this.initiatePoll()
|
||||||
} else {
|
break
|
||||||
logger.debug(
|
|
||||||
`Convo: init was canceled`,
|
|
||||||
{},
|
|
||||||
logger.DebugContext.convo,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
case ConvoDispatchEvent.Background: {
|
||||||
.catch(e => {
|
this.status = ConvoStatus.Backgrounded
|
||||||
logger.error('Convo: failed to init')
|
this.pollInterval = BACKGROUND_POLL_INTERVAL
|
||||||
this.error = {
|
this.fetchMessageHistory()
|
||||||
exception: e,
|
this.initiatePoll()
|
||||||
code: ConvoError.InitFailed,
|
break
|
||||||
retry: () => {
|
|
||||||
this.error = undefined
|
|
||||||
this.init()
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
this.status = ConvoStatus.Error
|
case ConvoDispatchEvent.Suspend: {
|
||||||
this.commit()
|
this.status = ConvoStatus.Suspended
|
||||||
})
|
break
|
||||||
} else if (this.status === ConvoStatus.Suspended) {
|
}
|
||||||
this.resume()
|
case ConvoDispatchEvent.Error: {
|
||||||
} else {
|
this.status = ConvoStatus.Error
|
||||||
logger.warn(`Convo: cannot init from ${this.status}`)
|
this.error = action.payload
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ConvoStatus.Ready: {
|
||||||
|
switch (action.event) {
|
||||||
|
case ConvoDispatchEvent.Resume: {
|
||||||
|
this.refreshConvo()
|
||||||
|
this.initiatePoll()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ConvoDispatchEvent.Background: {
|
||||||
|
this.status = ConvoStatus.Backgrounded
|
||||||
|
this.pollInterval = BACKGROUND_POLL_INTERVAL
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ConvoDispatchEvent.Suspend: {
|
||||||
|
this.status = ConvoStatus.Suspended
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ConvoDispatchEvent.Error: {
|
||||||
|
this.status = ConvoStatus.Error
|
||||||
|
this.error = action.payload
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ConvoStatus.Backgrounded: {
|
||||||
|
switch (action.event) {
|
||||||
|
case ConvoDispatchEvent.Resume: {
|
||||||
|
this.status = ConvoStatus.Ready
|
||||||
|
this.pollInterval = ACTIVE_POLL_INTERVAL
|
||||||
|
this.refreshConvo()
|
||||||
|
this.initiatePoll()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ConvoDispatchEvent.Suspend: {
|
||||||
|
this.status = ConvoStatus.Suspended
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ConvoDispatchEvent.Error: {
|
||||||
|
this.status = ConvoStatus.Error
|
||||||
|
this.error = action.payload
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ConvoStatus.Suspended: {
|
||||||
|
switch (action.event) {
|
||||||
|
case ConvoDispatchEvent.Init: {
|
||||||
|
this.status = ConvoStatus.Ready
|
||||||
|
this.pollInterval = ACTIVE_POLL_INTERVAL
|
||||||
|
this.refreshConvo()
|
||||||
|
this.initiatePoll()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ConvoDispatchEvent.Resume: {
|
||||||
|
this.status = ConvoStatus.Ready
|
||||||
|
this.pollInterval = ACTIVE_POLL_INTERVAL
|
||||||
|
this.refreshConvo()
|
||||||
|
this.initiatePoll()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ConvoDispatchEvent.Error: {
|
||||||
|
this.status = ConvoStatus.Error
|
||||||
|
this.error = action.payload
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ConvoStatus.Error: {
|
||||||
|
switch (action.event) {
|
||||||
|
case ConvoDispatchEvent.Init: {
|
||||||
|
this.reset()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ConvoDispatchEvent.Resume: {
|
||||||
|
this.reset()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ConvoDispatchEvent.Suspend: {
|
||||||
|
this.status = ConvoStatus.Suspended
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ConvoDispatchEvent.Error: {
|
||||||
|
this.status = ConvoStatus.Error
|
||||||
|
this.error = action.payload
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`Convo: dispatch '${action.event}'`,
|
||||||
|
{
|
||||||
|
id: this.id,
|
||||||
|
prev: prevStatus,
|
||||||
|
next: this.status,
|
||||||
|
},
|
||||||
|
logger.DebugContext.convo,
|
||||||
|
)
|
||||||
|
|
||||||
|
this.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private reset() {
|
||||||
|
this.convo = undefined
|
||||||
|
this.sender = undefined
|
||||||
|
this.recipients = undefined
|
||||||
|
this.snapshot = undefined
|
||||||
|
|
||||||
|
this.status = ConvoStatus.Uninitialized
|
||||||
|
this.error = undefined
|
||||||
|
this.historyCursor = undefined
|
||||||
|
this.eventsCursor = undefined
|
||||||
|
|
||||||
|
this.pastMessages = new Map()
|
||||||
|
this.newMessages = new Map()
|
||||||
|
this.pendingMessages = new Map()
|
||||||
|
this.deletedMessages = new Set()
|
||||||
|
this.footerItems = new Map()
|
||||||
|
this.headerItems = new Map()
|
||||||
|
|
||||||
|
this.dispatch({event: ConvoDispatchEvent.Init})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async setup() {
|
||||||
|
try {
|
||||||
|
await this.fetchConvo()
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some validation prior to `Ready` status
|
||||||
|
*/
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
|
||||||
|
// await new Promise(y => setTimeout(y, 2000))
|
||||||
|
// throw new Error('UNCOMMENT TO TEST INIT FAILURE')
|
||||||
|
this.dispatch({event: ConvoDispatchEvent.Ready})
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error('Convo: setup() failed')
|
||||||
|
|
||||||
|
this.dispatch({
|
||||||
|
event: ConvoDispatchEvent.Error,
|
||||||
|
payload: {
|
||||||
|
exception: e,
|
||||||
|
code: ConvoErrorCode.InitFailed,
|
||||||
|
retry: () => {
|
||||||
|
this.reset()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO resume failure
|
init() {
|
||||||
|
this.dispatch({event: ConvoDispatchEvent.Init})
|
||||||
|
}
|
||||||
|
|
||||||
resume() {
|
resume() {
|
||||||
if (
|
this.dispatch({event: ConvoDispatchEvent.Resume})
|
||||||
this.status === ConvoStatus.Suspended ||
|
|
||||||
this.status === ConvoStatus.Backgrounded
|
|
||||||
) {
|
|
||||||
logger.debug('Convo: resume', {id: this.id}, logger.DebugContext.convo)
|
|
||||||
|
|
||||||
this.status = ConvoStatus.Ready
|
|
||||||
this.cancelNextPoll()
|
|
||||||
this.pollInterval = ACTIVE_POLL_INTERVAL
|
|
||||||
this.pollEvents()
|
|
||||||
this.commit()
|
|
||||||
|
|
||||||
// intentionally un-awaited
|
|
||||||
this.refreshConvo().then(() => {
|
|
||||||
this.commit()
|
|
||||||
})
|
|
||||||
} else if (this.status === ConvoStatus.Error) {
|
|
||||||
this.init()
|
|
||||||
} else {
|
|
||||||
logger.debug(
|
|
||||||
`Convo: resume called from ${this.status}`,
|
|
||||||
{},
|
|
||||||
logger.DebugContext.convo,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
background() {
|
background() {
|
||||||
if (
|
this.dispatch({event: ConvoDispatchEvent.Background})
|
||||||
this.status === ConvoStatus.Initializing ||
|
|
||||||
this.status === ConvoStatus.Ready ||
|
|
||||||
this.status === ConvoStatus.Error
|
|
||||||
) {
|
|
||||||
logger.debug(
|
|
||||||
'Convo: backgrounded',
|
|
||||||
{id: this.id},
|
|
||||||
logger.DebugContext.convo,
|
|
||||||
)
|
|
||||||
this.status = ConvoStatus.Backgrounded
|
|
||||||
this.pollInterval = BACKGROUND_POLL_INTERVAL
|
|
||||||
this.commit()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend() {
|
suspend() {
|
||||||
if (
|
this.dispatch({event: ConvoDispatchEvent.Suspend})
|
||||||
this.status === ConvoStatus.Initializing ||
|
DEBUG_ACTIVE_CHAT = undefined
|
||||||
this.status === ConvoStatus.Ready ||
|
|
||||||
this.status === ConvoStatus.Error ||
|
|
||||||
this.status === ConvoStatus.Backgrounded
|
|
||||||
) {
|
|
||||||
logger.debug('Convo: suspended', {id: this.id}, logger.DebugContext.convo)
|
|
||||||
DEBUG_ACTIVE_CHAT = undefined
|
|
||||||
this.status = ConvoStatus.Suspended
|
|
||||||
this.commit()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshConvo() {
|
async fetchConvo() {
|
||||||
const response = await this.agent.api.chat.bsky.convo.getConvo(
|
const response = await this.agent.api.chat.bsky.convo.getConvo(
|
||||||
{
|
{
|
||||||
convoId: this.convoId,
|
convoId: this.convoId,
|
||||||
@@ -438,14 +589,26 @@ export class Convo {
|
|||||||
m => m.did !== this.__tempFromUserDid,
|
m => m.did !== this.__tempFromUserDid,
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
this.commit()
|
||||||
* Prevent invalid states
|
}
|
||||||
*/
|
|
||||||
if (!this.sender) {
|
async refreshConvo() {
|
||||||
throw new Error('Convo: could not find sender in convo')
|
try {
|
||||||
}
|
await this.fetchConvo()
|
||||||
if (!this.recipients) {
|
// throw new Error('UNCOMMENT TO TEST REFRESH FAILURE')
|
||||||
throw new Error('Convo: could not find recipients in convo')
|
} catch (e: any) {
|
||||||
|
logger.error(`Convo: failed to refresh convo`)
|
||||||
|
|
||||||
|
this.footerItems.set(ConvoItemError.Network, {
|
||||||
|
type: 'error-recoverable',
|
||||||
|
key: ConvoItemError.Network,
|
||||||
|
code: ConvoItemError.Network,
|
||||||
|
retry: () => {
|
||||||
|
this.footerItems.delete(ConvoItemError.Network)
|
||||||
|
this.resume()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
this.commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,6 +693,15 @@ export class Convo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private initiatePoll() {
|
||||||
|
this.cancelNextPoll()
|
||||||
|
this.pollEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
private cancelNextPoll() {
|
||||||
|
if (this.nextPoll) clearTimeout(this.nextPoll)
|
||||||
|
}
|
||||||
|
|
||||||
private async pollEvents() {
|
private async pollEvents() {
|
||||||
if (this.pendingPoll) return
|
if (this.pendingPoll) return
|
||||||
|
|
||||||
@@ -537,11 +709,11 @@ export class Convo {
|
|||||||
this.status === ConvoStatus.Ready ||
|
this.status === ConvoStatus.Ready ||
|
||||||
this.status === ConvoStatus.Backgrounded
|
this.status === ConvoStatus.Backgrounded
|
||||||
) {
|
) {
|
||||||
// logger.debug(
|
logger.debug(
|
||||||
// 'Convo: poll events',
|
'Convo: poll events',
|
||||||
// {id: this.id},
|
{id: this.id},
|
||||||
// logger.DebugContext.convo,
|
logger.DebugContext.convo,
|
||||||
// )
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.pendingPoll = this.ingestLatestEvents()
|
this.pendingPoll = this.ingestLatestEvents()
|
||||||
@@ -574,10 +746,6 @@ export class Convo {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
private cancelNextPoll() {
|
|
||||||
if (this.nextPoll) clearTimeout(this.nextPoll)
|
|
||||||
}
|
|
||||||
|
|
||||||
async ingestLatestEvents() {
|
async ingestLatestEvents() {
|
||||||
// throw new Error('UNCOMMENT TO TEST POLL FAILURE')
|
// throw new Error('UNCOMMENT TO TEST POLL FAILURE')
|
||||||
const response = await this.agent.api.chat.bsky.convo.getLog(
|
const response = await this.agent.api.chat.bsky.convo.getLog(
|
||||||
|
|||||||
Reference in New Issue
Block a user