Skip to content

Commit 7365dc7

Browse files
Merge pull request #813 from Adamant-im/feat/message-sending-timeouts
Feat/message sending timeouts
2 parents a377c52 + dc0a979 commit 7365dc7

File tree

9 files changed

+156
-44
lines changed

9 files changed

+156
-44
lines changed

src/components/AChat/AChatAttachment/AChatFile.vue

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ import IconFile from '@/components/icons/common/IconFile.vue'
8080
import { useStore } from 'vuex'
8181
import { AChatFileLoader } from './AChatFileLoader'
8282
import { mdiImageOff } from '@mdi/js'
83+
import { useConsiderOffline } from '@/hooks/useConsiderOffline'
8384
8485
const className = 'a-chat-file'
8586
const classes = {
@@ -120,6 +121,9 @@ const isImage = computed(() => {
120121
return ['jpg', 'jpeg', 'png'].includes(props.file.extension!)
121122
})
122123
124+
const { consideredOffline } = useConsiderOffline()
125+
126+
123127
const fileName = computed(() =>
124128
isLocalFile(props.file) ? props.file.file.name : props.file.name || 'UNNAMED'
125129
)
@@ -146,6 +150,10 @@ const fileSize = computed(() => {
146150
147151
const uploadProgress = computed(() => {
148152
if (isLocalFile(props.file)) {
153+
if (consideredOffline.value) {
154+
return 0;
155+
}
156+
149157
return store.getters['attachment/getUploadProgress'](props.file.file.cid)
150158
}
151159

src/components/Chat/Chat.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ import { useChatStateStore } from '@/stores/modal-state'
322322
import ChatPlaceholder from '@/components/Chat/ChatPlaceholder.vue'
323323
import { watchImmediate } from '@vueuse/core'
324324
import { NodeStatusResult } from '@/lib/nodes/abstract.node'
325+
import { isAllNodesOfflineError } from '@/lib/nodes/utils/errors'
325326
326327
const validationErrors = {
327328
emptyMessage: 'EMPTY_MESSAGE',
@@ -873,6 +874,14 @@ const fetchChatMessages = async () => {
873874
}
874875
} catch {
875876
if (store.getters['chat/chatOffset'](props.partnerId) !== -1) {
877+
if (areAdmNodesOnline.value) {
878+
// give health check time to be finished and then retry in case of miscoordination of nodes statuses
879+
// (when areAdmNodesOnline says there are some nodes online, but the request fails with allNodesOffline error)
880+
return setTimeout(async () => {
881+
await fetchChatMessages()
882+
}, 5000)
883+
}
884+
876885
return (allowFetchingMessages.value = true)
877886
}
878887
loading.value = false

src/components/NodesOfflineDialog.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export default {
6060
return store.state.chat.noActiveNodesDialog
6161
},
6262
set() {
63-
store.commit('chat/setNoActiveNodesDialog', false)
63+
store.commit('chat/setNoActiveNodesDialog', { value: false })
6464
}
6565
})
6666

src/hooks/useConsiderOffline.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,29 @@ export function useConsiderOffline() {
1919
...ipfsNodes.value
2020
])
2121

22+
const admAnyOnline = computed(() => admNodes.value.some((node) => node.status === 'online'))
23+
const admAllDisabled = computed(() => admNodes.value.every((node) => node.status === 'disabled'))
24+
25+
const anyNodeOnline = computed(() => allNodes.value.some((node) => node.status === 'online'))
2226
const everyNodeDisabled = computed(() =>
2327
allNodes.value.every((node) => node.status === 'disabled')
2428
)
25-
const anyNodeOnline = computed(() => allNodes.value.some((node) => node.status === 'online'))
2629

27-
const consideredOffline = computed(
28-
() =>
29-
!isOnline.value ||
30-
(!anyNodeOnline.value && !everyNodeDisabled.value) ||
31-
everyNodeDisabled.value
32-
)
30+
const consideredOffline = computed(() => {
31+
if (!isOnline.value) {
32+
return true
33+
}
34+
35+
if (admAnyOnline.value) {
36+
return false
37+
}
38+
39+
if (!admAllDisabled.value && !admAnyOnline.value) {
40+
return true
41+
}
42+
43+
return !anyNodeOnline.value || everyNodeDisabled.value
44+
})
3345

3446
return {
3547
consideredOffline

src/hooks/useResendPendingMessages.ts

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { computed, onBeforeUnmount } from 'vue'
1+
import { computed, onBeforeUnmount, watch } from 'vue'
22
import { useStore } from 'vuex'
3-
import { watchImmediate } from '@vueuse/core'
43
import { MessageType } from '@/lib/constants'
54
import { NodeStatusResult } from '@/lib/nodes/abstract.node'
65
import { FileData } from '@/lib/files'
@@ -25,33 +24,62 @@ export function useResendPendingMessages() {
2524
() => store.state.chat.pendingMessages
2625
)
2726

28-
watchImmediate([areAdmNodesOnline, areIpfsNodesOnline], ([admOnline, ipfsOnline]) => {
29-
const hasMessagesWithFiles = Object.values(pendingMessages.value).some(
30-
(message) => message.files && message.files.length > 0
31-
)
32-
33-
const isReady = hasMessagesWithFiles ? admOnline && ipfsOnline : admOnline
34-
35-
if (!isReady) return
27+
// resend basic messages
28+
function resendBasic() {
29+
Object.entries(pendingMessages.value).forEach(([id, msg]) => {
30+
if (msg.type === MessageType.BASIC_ENCRYPTED_MESSAGE) {
31+
store
32+
.dispatch('chat/resendMessage', {
33+
recipientId: msg.recipientId,
34+
messageId: id
35+
})
36+
.then((res) => {
37+
if (res.success) store.commit('chat/deletePendingMessage', id)
38+
})
39+
}
40+
})
41+
}
3642

37-
Object.entries(pendingMessages.value).forEach(([messageId, message]) => {
38-
;(message.type === MessageType.BASIC_ENCRYPTED_MESSAGE
39-
? store.dispatch('chat/resendMessage', {
40-
recipientId: message.recipientId,
41-
messageId
43+
// resend messages with attachments (requires ipfs node)
44+
function resendAttachments() {
45+
Object.entries(pendingMessages.value).forEach(([id, msg]) => {
46+
if (msg.type !== MessageType.BASIC_ENCRYPTED_MESSAGE && msg.files?.length) {
47+
store
48+
.dispatch('chat/resendAttachment', {
49+
recipientId: msg.recipientId,
50+
files: msg.files,
51+
messageId: id
4252
})
43-
: store.dispatch('chat/resendAttachment', {
44-
recipientId: message.recipientId,
45-
files: message.files,
46-
messageId
53+
.then((res) => {
54+
if (res.success) store.commit('chat/deletePendingMessage', id)
4755
})
48-
).then((res) => {
49-
if (res.success) {
50-
store.commit('chat/deletePendingMessage', messageId)
51-
}
52-
})
56+
}
5357
})
54-
})
58+
}
59+
60+
watch(
61+
areAdmNodesOnline,
62+
(isUp, wasUp) => {
63+
if (!isUp || isUp === wasUp) return
64+
65+
resendBasic()
66+
if (areIpfsNodesOnline.value) {
67+
resendAttachments()
68+
}
69+
},
70+
{ immediate: true }
71+
)
72+
73+
watch(
74+
areIpfsNodesOnline,
75+
(isUp, wasUp) => {
76+
if (!isUp || isUp === wasUp) return
77+
if (!areAdmNodesOnline.value) return
78+
79+
resendAttachments()
80+
},
81+
{ immediate: true }
82+
)
5583

5684
onBeforeUnmount(() => {
5785
Object.values(pendingMessages.value).forEach((message) => {

src/hooks/useTrackConnection.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ export function useTrackConnection() {
77
const store = useStore()
88
const { consideredOffline } = useConsiderOffline()
99

10+
let firstCallSkipped = false
11+
1012
watch(consideredOffline, (isOffline) => {
13+
if (!firstCallSkipped) {
14+
firstCallSkipped = true
15+
return
16+
}
17+
1118
const type = isOffline ? 'offline' : 'online'
1219

1320
store.dispatch('snackbar/show', {

src/lib/adamant-api/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { isStringEqualCI } from '@/lib/textHelpers'
1212
import { parseCryptoAddressesKVStxs } from '@/lib/store-crypto-address'
1313
import { DEFAULT_TIME_DELTA } from '@/lib/nodes/constants.js'
1414
import constants from '@/lib/constants/index.js'
15+
import { isAllNodesOfflineError } from '@/lib/nodes/utils/errors.js'
1516

1617
Queue.configure(Promise)
1718

@@ -111,6 +112,10 @@ export function isReady() {
111112
* @returns {Promise<string>}
112113
*/
113114
export function getPublicKey(address = '') {
115+
if (address === store.state.address && myKeypair.publicKey) {
116+
return Promise.resolve(myKeypair.publicKey.toString('hex'))
117+
}
118+
114119
// @todo remove returning cached keys and use getCachedPublicKey instead
115120
const publicKeyCached = store.getters.publicKey(address)
116121

src/lib/files/ipfs.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
import { CID } from 'multiformats/cid'
2+
import { code } from 'multiformats/codecs/raw'
3+
import { sha256 } from 'multiformats/hashes/sha2'
4+
15
/**
26
* Compute CID for a file
37
*/
48
export async function computeCID(fileOrBytes: File | Uint8Array) {
5-
const { CID } = await import('multiformats/cid')
6-
const { code } = await import('multiformats/codecs/raw')
7-
const { sha256 } = await import('multiformats/hashes/sha2')
8-
99
const bytes =
1010
fileOrBytes instanceof File ? new Uint8Array(await fileOrBytes.arrayBuffer()) : fileOrBytes
1111

src/store/modules/chat/index.js

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { replyMessageAsset, attachmentAsset } from '@/lib/adamant-api/asset'
2626
import { uploadFiles } from '../../../lib/files'
2727
import { generateAdamantChats } from './utils/generateAdamantChats'
2828
import {
29+
AllNodesDisabledError,
2930
AllNodesOfflineError,
3031
isAllNodesDisabledError,
3132
isAllNodesOfflineError
@@ -554,8 +555,8 @@ const mutations = {
554555
}
555556
},
556557

557-
setNoActiveNodesDialog(state, value) {
558-
if (state.noActiveNodesDialog === false) {
558+
setNoActiveNodesDialog(state, { value, afterSendingMessage = false }) {
559+
if (state.noActiveNodesDialog === false && !afterSendingMessage) {
559560
return // do not show dialog again
560561
}
561562

@@ -617,7 +618,7 @@ const actions = {
617618
})
618619
.catch((err) => {
619620
if (isAllNodesDisabledError(err) || getters.isNoNodesDialogAllowed(err)) {
620-
commit('setNoActiveNodesDialog', true)
621+
commit('setNoActiveNodesDialog', { value: true })
621622
setTimeout(() => dispatch('loadChats'), 5000) // retry in 5 seconds
622623
}
623624
})
@@ -674,7 +675,7 @@ const actions = {
674675
})
675676
.catch((err) => {
676677
if (isAllNodesDisabledError(err) || getters.isNoNodesDialogAllowed(err)) {
677-
commit('setNoActiveNodesDialog', true)
678+
commit('setNoActiveNodesDialog', { value: true })
678679
}
679680
throw err
680681
})
@@ -844,6 +845,15 @@ const actions = {
844845
// if the error is caused by connection we keep the message in PENDING status
845846
// and try to resend it after the connection is restored
846847

848+
dispatch(
849+
'snackbar/show',
850+
{
851+
message: i18n.global.t(`connection.offline`),
852+
timeout: 3000
853+
},
854+
{ root: true }
855+
)
856+
847857
// timeout for self deleting out of pending messages
848858
const timeout = setTimeout(() => {
849859
dispatch('rejectPendingMessage', {
@@ -866,6 +876,10 @@ const actions = {
866876
partnerId: recipientId
867877
})
868878
} else {
879+
if (isAllNodesDisabledError(error)) {
880+
commit('setNoActiveNodesDialog', { value: true, afterSendingMessage: true })
881+
}
882+
869883
if (id) {
870884
commit('updateMessage', {
871885
id,
@@ -889,7 +903,7 @@ const actions = {
889903
* @returns {Promise}
890904
*/
891905
async sendAttachment(
892-
{ commit, rootState, dispatch },
906+
{ commit, rootState, dispatch, rootGetters },
893907
{ files, message, recipientId, replyToId }
894908
) {
895909
const recipientPublicKey = await getPublicKey(recipientId)
@@ -923,6 +937,14 @@ const actions = {
923937
console.debug('Updated CIDs and Nonces', newAsset)
924938

925939
try {
940+
const areAdmNodesDisabled = rootGetters['nodes/adm'].every(
941+
(node) => node.status === 'disabled'
942+
)
943+
944+
if (areAdmNodesDisabled) {
945+
throw new AllNodesDisabledError('adm')
946+
}
947+
926948
const uploadData = await uploadFiles(files, (progress) => {
927949
for (const [cid] of cids) {
928950
commit('attachment/setUploadProgress', { cid, progress }, { root: true })
@@ -944,6 +966,10 @@ const actions = {
944966
console.debug('Updated CIDs after upload', newAsset)
945967
} catch (err) {
946968
if (!isAllNodesOfflineError(err)) {
969+
if (isAllNodesDisabledError(err)) {
970+
commit('setNoActiveNodesDialog', { value: true, afterSendingMessage: true })
971+
}
972+
947973
commit('updateMessage', {
948974
id: messageObject.id,
949975
status: TS.REJECTED,
@@ -965,7 +991,11 @@ const actions = {
965991
return queueMessage(newAsset, recipientId, MessageType.RICH_CONTENT_MESSAGE)
966992
.then((res) => {
967993
if (isAllNodesOfflineError(res)) {
968-
throw new AllNodesOfflineError('ipfs')
994+
throw new AllNodesOfflineError('adm')
995+
}
996+
997+
if (isAllNodesDisabledError(res)) {
998+
throw new AllNodesDisabledError('adm')
969999
}
9701000

9711001
if (!res.success) {
@@ -986,6 +1016,10 @@ const actions = {
9861016
.catch((err) => {
9871017
// update `message.status` to 'REJECTED' if the error is not caused by connection
9881018
if (!isAllNodesOfflineError(err)) {
1019+
if (isAllNodesDisabledError(err)) {
1020+
commit('setNoActiveNodesDialog', { value: true, afterSendingMessage: true })
1021+
}
1022+
9891023
commit('updateMessage', {
9901024
id: messageObject.id,
9911025
status: TS.REJECTED,
@@ -995,6 +1029,15 @@ const actions = {
9951029
// if the error is caused by connection we keep the message in PENDING status
9961030
// and try to resend it after the connection is restored
9971031

1032+
dispatch(
1033+
'snackbar/show',
1034+
{
1035+
message: i18n.global.t(`connection.offline`),
1036+
timeout: 3000
1037+
},
1038+
{ root: true }
1039+
)
1040+
9981041
// timeout for self deleting out of pending messages
9991042
const timeout = setTimeout(() => {
10001043
dispatch('rejectPendingMessage', {
@@ -1181,7 +1224,7 @@ const actions = {
11811224

11821225
rejectPendingMessage({ commit }, { messageId, recipientId }) {
11831226
commit('updateMessage', {
1184-
messageId,
1227+
id: messageId,
11851228
status: TS.REJECTED,
11861229
partnerId: recipientId
11871230
})

0 commit comments

Comments
 (0)