Skip to content

v4.10.6 #838

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ android {
applicationId "im.adamant.adamantmessengerpwa"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 4105
versionName "4.10.5"
versionCode 4106
versionName "4.10.6"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "adamant-im",
"version": "4.10.5",
"version": "4.10.6",
"type": "module",
"author": "ADAMANT Foundation <devs@adamant.im>",
"license": "GPLv3",
Expand Down
8 changes: 0 additions & 8 deletions src/components/AChat/AChatAttachment/AChatFile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ import IconFile from '@/components/icons/common/IconFile.vue'
import { useStore } from 'vuex'
import { AChatFileLoader } from './AChatFileLoader'
import { mdiImageOff } from '@mdi/js'
import { useConsiderOffline } from '@/hooks/useConsiderOffline'

const className = 'a-chat-file'
const classes = {
Expand Down Expand Up @@ -121,9 +120,6 @@ const isImage = computed(() => {
return ['jpg', 'jpeg', 'png'].includes(props.file.extension!)
})

const { consideredOffline } = useConsiderOffline()


const fileName = computed(() =>
isLocalFile(props.file) ? props.file.file.name : props.file.name || 'UNNAMED'
)
Expand All @@ -150,10 +146,6 @@ const fileSize = computed(() => {

const uploadProgress = computed(() => {
if (isLocalFile(props.file)) {
if (consideredOffline.value) {
return 0;
}

return store.getters['attachment/getUploadProgress'](props.file.file.cid)
}

Expand Down
43 changes: 33 additions & 10 deletions src/hooks/useConsiderOffline.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { computed } from 'vue'
import { useStore } from 'vuex'
import { computed, watch } from 'vue'
import { GetterTree, useStore } from 'vuex'
import { NodeStatusResult } from '@/lib/nodes/abstract.node'
import { RootState } from '@/store/types'

export function useConsiderOffline() {
const store = useStore()
export function useConsiderOffline(options?: { getters: GetterTree<any, RootState> }) {
const getters = options?.getters ?? useStore().getters

const isOnline = computed(() => store.getters['isOnline'])
const isOnline = computed(() => getters['isOnline'])

const admNodes = computed<NodeStatusResult[]>(() => store.getters['nodes/adm'])
const coinNodes = computed<NodeStatusResult[]>(() => store.getters['nodes/coins'])
const servicesNodes = computed<NodeStatusResult[]>(() => store.getters['services/services'])
const ipfsNodes = computed<NodeStatusResult[]>(() => store.getters['nodes/ipfs'])
const admNodes = computed<NodeStatusResult[]>(() => getters['nodes/adm'])
const coinNodes = computed<NodeStatusResult[]>(() => getters['nodes/coins'])
const servicesNodes = computed<NodeStatusResult[]>(() => getters['services/services'])
const ipfsNodes = computed<NodeStatusResult[]>(() => getters['nodes/ipfs'])

const allNodes = computed<NodeStatusResult[]>(() => [
...admNodes.value,
Expand Down Expand Up @@ -43,7 +44,29 @@ export function useConsiderOffline() {
return !anyNodeOnline.value || everyNodeDisabled.value
})

const offlineHandlers = new Set<() => void>()

const subscribeOffline = (callback: () => void) => {
offlineHandlers.add(callback)

return () => {
offlineHandlers.delete(callback)
}
}

const unsubscribeOffline = (callback: () => void) => {
offlineHandlers.delete(callback)
}

watch(consideredOffline, (isOffline) => {
if (isOffline) {
offlineHandlers.forEach((cb) => cb())
}
})

return {
consideredOffline
consideredOffline,
subscribeOffline,
unsubscribeOffline
}
}
2 changes: 2 additions & 0 deletions src/hooks/useResendPendingMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type PendingMessage = {
timeout: ReturnType<typeof setTimeout>
type: number
files?: FileData[]
cids?: [string, string | undefined][]
}

export function useResendPendingMessages() {
Expand Down Expand Up @@ -48,6 +49,7 @@ export function useResendPendingMessages() {
.dispatch('chat/resendAttachment', {
recipientId: msg.recipientId,
files: msg.files,
cids: msg.cids,
messageId: id
})
.then((res) => {
Expand Down
10 changes: 4 additions & 6 deletions src/lib/adamant-api/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,23 +201,21 @@ export interface AttachmentAsset {
* AIP-18: https://github.com/Adamant-im/AIPs/pull/54/files
* @param {Array<FileData>} files
* @param {string} [comment] Optional comment associated with the transaction
* @param {Array<[string]>} [cids] List of files IDs after uploading to IPFS. ID of original file and ID of preview go one by one.
* @param {Array<[string, string][]>} [cids] List of files IDs after uploading to IPFS. First element is the ID of original file, second is ID of preview.
*/
export function attachmentAsset(
files: FileData[],
comment?: string,
cids?: string[]
cids?: [string, string][]
): AttachmentAsset {
return {
files: files.map(({ file, width, height, cid, preview, encoded }, index) => {
const cidsChunk = index * 2

const name = extractFileName(file.name)
const extension = extractFileExtension(file.name)!
const resolution: FileAsset['resolution'] = width && height ? [width, height] : undefined

const fileCid = (cids && cids[cidsChunk]) || cid
const previewCid = (cids && cids[cidsChunk + 1]) || preview?.cid
const fileCid = cids?.[index]?.[0] || cid
const previewCid = cids?.[index]?.[1] || preview?.cid

return {
mimeType: file.type,
Expand Down
33 changes: 18 additions & 15 deletions src/lib/files/upload.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
import { FileData } from './types'
import ipfs from '@/lib/nodes/ipfs'

export async function uploadFiles(
files: FileData[],
onUploadProgress?: (progress: number) => void
export async function uploadFile(
file: FileData,
onUploadProgress?: (progress: number) => void,
signal?: AbortSignal
) {
const formData = new FormData()

for (const file of files) {
const blob = new Blob([file.encoded.binary], { type: 'application/octet-stream' })
formData.append('files', blob, file.file.name)
const blob = new Blob([file.encoded.binary], { type: 'application/octet-stream' })
formData.append('files', blob, file.file.name)

if (file.preview) {
const blob = new Blob([file.preview.encoded.binary], { type: 'application/octet-stream' })
formData.append('files', blob, 'preview-' + file.file.name)
}
if (file.preview) {
const blob = new Blob([file.preview.encoded.binary], { type: 'application/octet-stream' })
formData.append('files', blob, 'preview-' + file.file.name)
}

onUploadProgress?.(0) // set initial progress to 0
const response = await ipfs.upload(formData, (progress) => {
const percentCompleted = Math.round((progress.loaded * 100) / (progress.total || 0))

onUploadProgress?.(percentCompleted)
})
const response = await ipfs.upload(
formData,
(progress) => {
const percentCompleted = Math.round((progress.loaded * 100) / (progress.total || 0))

onUploadProgress?.(percentCompleted)
},
signal
)

return response
}
Expand Down
9 changes: 7 additions & 2 deletions src/lib/nodes/ipfs/IpfsClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,20 @@ export class IpfsClient extends Client<IpfsNode> {
})
}

async upload(payload: FormData, onUploadProgress?: (progressEvent: AxiosProgressEvent) => void) {
async upload(
payload: FormData,
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void,
signal?: AbortSignal
) {
return this.request({
method: 'post',
url: '/api/file/upload',
payload,
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress
onUploadProgress,
signal
})
}

Expand Down
13 changes: 8 additions & 5 deletions src/lib/nodes/ipfs/IpfsNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type RequestConfig<P extends Payload> = {
payload?: P
onUploadProgress?: (progress: AxiosProgressEvent) => void
responseType?: ResponseType
signal?: AbortSignal
}

/**
Expand All @@ -50,7 +51,7 @@ export class IpfsNode extends Node<AxiosInstance> {
* accepts `ApiNode` as a first argument and returns an object.
*/
request<P extends Payload = Payload, R = any>(cfg: RequestConfig<P>): Promise<R> {
const { url, headers, method = 'get', payload, onUploadProgress } = cfg
const { url, headers, method = 'get', payload, signal, onUploadProgress } = cfg

const config: AxiosRequestConfig = {
url,
Expand All @@ -60,6 +61,7 @@ export class IpfsNode extends Node<AxiosInstance> {
[method === 'get' ? 'params' : 'data']:
typeof payload === 'function' ? payload(this) : payload,
responseType: cfg.responseType,
signal,
onUploadProgress
}

Expand Down Expand Up @@ -117,7 +119,7 @@ export class IpfsNode extends Node<AxiosInstance> {
protected async checkHealth() {
const time = Date.now()
const { timestamp } = await this.fetchNodeInfo()
this.height = timestamp;
this.height = timestamp

return {
height: this.height,
Expand All @@ -127,9 +129,10 @@ export class IpfsNode extends Node<AxiosInstance> {

formatHeight(height: number): string {
return super.formatHeight(
Number(Math.ceil(height / 1000)
.toString()
.substring(2)
Number(
Math.ceil(height / 1000)
.toString()
.substring(2)
)
)
}
Expand Down
Loading
Loading