Skip to content

Commit

Permalink
feat(IPFS): upload file functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
bludnic committed Sep 17, 2024
1 parent 0eea02a commit 32a0bc3
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 36 deletions.
9 changes: 8 additions & 1 deletion src/lib/adamant-api/asset.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { FileData } from '@/components/UploadFile.vue'

/**
* AIP 12: Non-ADM crypto transfer messages
* @see https://aips.adamant.im/AIPS/aip-12
Expand Down Expand Up @@ -158,4 +160,9 @@ export interface AttachmentAsset {
comment?: string
}

export function attachmentAsset(files: File[], comment?: string): AttachmentAsset
export function attachmentAsset(
files: FileData[],
nonces?: [string, string],
ids?: [string, string],
comment?: string
): AttachmentAsset
43 changes: 26 additions & 17 deletions src/lib/adamant-api/asset.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,35 @@ export function reactionAsset(reactToId, reactMessage) {

/**
* AIP-18: https://github.com/Adamant-im/AIPs/pull/54/files
* @param {Array<File>} files
* @param {string} comment
* @param {Array<FileData>} files
* @param {Array<[string, string]>} [nonces] First element is the nonce of original file, second is nonce of preview
* @param {Array<[string, string]>} [ids] List of files IDs after uploading to IPFS. First element is the ID of original file, second is ID of preview.
* @param {string} [comment]
*/
export function attachmentAsset(files, comment) {
export function attachmentAsset(files, nonces, ids, comment) {
return {
files: files.map((file) => ({
mimeType: file.type,
name: file.name,
extension: file.name.split('.').pop(),
// resolution?: [number, number] // @todo
// duration?: number // @todo
id: '',
size: file.size,
nonce: '',
preview: {
id: '',
nonce: '',
extension: ''
files: files.map(({ file, width, height }, index) => {
const [name, extension] = file.name.split('.')
const resolution = width && height ? [width, height] : undefined
const [nonce, previewNonce] = nonces?.[index] || []
const [id, previewId] = ids?.[index] || []

return {
mimeType: file.type,
name,
extension,
resolution,
duration: undefined, // @todo check if is a video or audio file
size: file.size,
id,
nonce,
preview: {
id: previewId,
nonce: previewNonce,
extension
}
}
})),
}),
comment,
storage: { id: 'ipfs' }
}
Expand Down
11 changes: 7 additions & 4 deletions src/lib/adamant-api/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,13 @@ export type SendMessageParams = {
amount?: number
}
export function sendMessage(params: SendMessageParams): Promise<CreateNewChatMessageResponseDto>
export function encodeFile(
file: Uint8Array,
params: SendMessageParams
): Promise<{ binary: Uint8Array; nonce: string }>

export type EncodedFile = {
binary: Uint8Array
nonce: string
}

export function encodeFile(file: Uint8Array, params: SendMessageParams): Promise<EncodedFile>

export function sendSpecialMessage(
to: string,
Expand Down
11 changes: 8 additions & 3 deletions src/lib/chat/helpers/createAttachment.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { FileData } from '@/components/UploadFile.vue'
import utils from '@/lib/adamant'
import { EncodedFile } from '@/lib/adamant-api'
import { NormalizedChatMessageTransaction } from '@/lib/chat/helpers/normalizeMessage'
import { TransactionStatus as TS, TransactionStatusType } from '@/lib/constants'
import { attachmentAsset } from '@/lib/adamant-api/asset'
Expand All @@ -8,6 +9,9 @@ type Params = {
recipientId: string
senderId: string
files: FileData[]
nonces?: [string, string]
ids?: [string, string]
encodedFiles?: EncodedFile[]
message?: string
replyToId?: string
status?: TransactionStatusType
Expand All @@ -19,13 +23,14 @@ export function createAttachment({
recipientId,
senderId,
files,
nonces,
ids,
message,
replyToId,
status = TS.PENDING
}: Params) {
const timestamp = Date.now()
const id = utils.epochTime().toString()
const filesList = files.map((file) => file.file)

const transaction: NormalizedChatMessageTransaction = {
id,
Expand All @@ -37,8 +42,8 @@ export function createAttachment({
timestamp: Date.now(),
type: 'attachment',
asset: replyToId
? { replyto_id: replyToId, reply_message: attachmentAsset(filesList) }
: attachmentAsset(filesList),
? { replyto_id: replyToId, reply_message: attachmentAsset(files, nonces, ids, message) }
: attachmentAsset(files, nonces, ids, message),
/**
* When sending a message, we need to store the files locally.
*/
Expand Down
27 changes: 27 additions & 0 deletions src/lib/file.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { FileData } from '@/components/UploadFile.vue'
import { EncodedFile } from '@/lib/adamant-api'
import ipfs from '@/lib/nodes/ipfs'

export function readFileAsDataURL(file: File): Promise<{ raw: Uint8Array; dataURL: string }> {
return new Promise((resolve) => {
const reader = new FileReader()
Expand Down Expand Up @@ -29,3 +33,26 @@ export function readFileAsBuffer(file: File): Promise<Uint8Array> {
reader.readAsArrayBuffer(file)
})
}

export async function uploadFiles(files: FileData[], encodedFiles: EncodedFile[]) {
const formData = new FormData()

for (const [index, file] of Object.entries(files)) {
const encodedFile = encodedFiles[+index]?.binary
if (!encodedFile) {
throw new Error(`Missing binary data for ${file.file.name}`)
}

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

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

console.log(`Progress ${percentCompleted}%`)
})
console.log(`Uploaded CIDs`, response)

return response
}
54 changes: 43 additions & 11 deletions src/store/modules/chat/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import {
createMessage,
createTransaction,
createReaction,
normalizeMessage
normalizeMessage,
createAttachment
} from '@/lib/chat/helpers'
import { i18n } from '@/i18n'
import { isNumeric } from '@/lib/numericHelpers'
import { Cryptos, TransactionStatus as TS, MessageType } from '@/lib/constants'
import { isStringEqualCI } from '@/lib/textHelpers'
import { replyMessageAsset } from '@/lib/adamant-api/asset'
import { createAttachment } from '../../../lib/chat/helpers'
import { replyMessageAsset, attachmentAsset } from '@/lib/adamant-api/asset'
import { encodeFile } from '../../../lib/adamant-api'
import { readFileAsBuffer, uploadFiles } from '../../../lib/file'

import { generateAdamantChats } from './utils/generateAdamantChats'
import { isAllNodesDisabledError, isAllNodesOfflineError } from '@/lib/nodes/utils/errors'
Expand Down Expand Up @@ -460,7 +462,7 @@ const mutations = {
* @param {string} realId Real id (from server)
* @param {string} status Message status
*/
updateMessage(state, { partnerId, id, realId, status }) {
updateMessage(state, { partnerId, id, realId, status, asset }) {
const chat = state.chats[partnerId]

if (chat) {
Expand All @@ -473,6 +475,9 @@ const mutations = {
if (status) {
message.status = status
}
if (asset) {
message.asset = asset
}
}
}
},
Expand Down Expand Up @@ -766,27 +771,54 @@ const actions = {
* @returns {Promise}
*/
async sendAttachment({ commit, rootState }, { files, message, recipientId, replyToId }) {
const messageObject = createAttachment({
let messageObject = createAttachment({
message,
recipientId,
senderId: rootState.address,
files,
replyToId
})
console.log('Pushed message', messageObject)

commit('pushMessage', {
message: messageObject,
userId: rootState.address
})

const type = replyToId ? MessageType.RICH_CONTENT_MESSAGE : MessageType.BASIC_ENCRYPTED_MESSAGE
const encodedFiles = await Promise.all(
files.map((file) =>
readFileAsBuffer(file.file).then((buffer) => encodeFile(buffer, { to: recipientId }))
)
)

// const promises = files.map(file => readFile(file))
// const result = await Promise.all(promises);
// console.log('result', result)
// Updating nonces
const nonces = encodedFiles.map((file) => [file.nonce, file.nonce]) // @todo preview nonce
let newAsset = replyToId
? { replyto_id: replyToId, reply_message: attachmentAsset(files, nonces, undefined, message) }
: attachmentAsset(files, nonces, undefined, message)
commit('updateMessage', {
id: messageObject.id,
partnerId: recipientId,
asset: newAsset
})
console.log('Updated Nonces', newAsset)

throw new Error('TODO')
return queueMessage(messageObject.asset, recipientId, type)
const uploadData = await uploadFiles(files, encodedFiles)
console.log('Files uploaded', uploadData)

// Update CIDs
const cids = uploadData.cids.map((cid) => [cid, cid]) // @todo preview cid
newAsset = replyToId
? { replyto_id: replyToId, reply_message: attachmentAsset(files, nonces, cids, message) }
: attachmentAsset(files, nonces, cids, message)
commit('updateMessage', {
id: messageObject.id,
partnerId: recipientId,
asset: newAsset
})
console.log('Updated CIDs', newAsset)

return queueMessage(newAsset, recipientId, MessageType.RICH_CONTENT_MESSAGE)
.then((res) => {
if (!res.success) {
throw new Error(i18n.global.t('chats.message_rejected'))
Expand Down

0 comments on commit 32a0bc3

Please sign in to comment.