From 8665596ea95bb6c5dcbb0da50bdcf187e5d93eea Mon Sep 17 00:00:00 2001 From: evavirseda <evirseda@boxfish.studio> Date: Wed, 29 Nov 2023 15:50:16 +0100 Subject: [PATCH 1/6] feat: add getIpfsUri function --- .../shared/lib/core/nfts/utils/getIpfsUri.ts | 149 ++++++++++++++++++ packages/shared/lib/core/nfts/utils/index.ts | 5 +- 2 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 packages/shared/lib/core/nfts/utils/getIpfsUri.ts diff --git a/packages/shared/lib/core/nfts/utils/getIpfsUri.ts b/packages/shared/lib/core/nfts/utils/getIpfsUri.ts new file mode 100644 index 00000000000..b313f87a2cd --- /dev/null +++ b/packages/shared/lib/core/nfts/utils/getIpfsUri.ts @@ -0,0 +1,149 @@ +interface IIpfsLink { + Hash: string + Name: string + Size: number + Target: string + Type: number + Mode?: string + Mtime?: number + MtimeNsecs?: number +} + +interface IIPfsEntry { + readonly type: 'dir' | 'file' + readonly cid: string + readonly name: string + readonly path: string + mode?: number + mtime?: { + secs: number + nsecs?: number + } + size: number +} + +interface IIpfsObject { + Hash: string + Links: IIpfsLink[] +} + +enum typeOfLink { + Dir = 'dir', + File = 'file', +} + +const IPFS_ENDPOINT = 'https://ipfs.io' +const IPFS_PATH = '/api/v0/ls' +const IPFS_PREFIX = '/ipfs/' + +export async function getIpfsUri(link: { path?: string; hash: string }): Promise<string | undefined> { + try { + const slicedLink = link.hash.slice('https://ipfs.io/'.length) + const ipfsEntry = await ls(slicedLink) + if (ipfsEntry) { + if (ipfsEntry.type === 'dir') { + const path = `${link.path ?? ''}/${ipfsEntry.name}` + return await getIpfsUri({ hash: link.hash, path }) + } + return `${link.hash}/${encodeURIComponent(ipfsEntry.name)}` + } + } catch (error) { + console.error('error', error) + } +} + +async function ls(path: string): Promise<IIPfsEntry | undefined> { + let ipfsEntry: IIPfsEntry | undefined + + try { + const baseUrl = IPFS_ENDPOINT + const method = 'get' + const payload = undefined + let headers = {} + const timeout = undefined + + headers ??= {} + + let controller: AbortController | undefined + let timerId: NodeJS.Timeout | undefined + + if (timeout !== undefined) { + controller = new AbortController() + timerId = setTimeout(() => { + if (controller) { + controller.abort() + } + }, timeout) + } + + try { + if (path.includes('ipfs')) { + const response = await fetch(`${baseUrl}${IPFS_PATH}?arg=/${path}`, { + method, + headers, + body: payload ? JSON.stringify(payload) : undefined, + signal: controller ? controller.signal : undefined, + }) + const lsResponse = (await response.json()) as { Objects: IIpfsObject[] } + const result = lsResponse.Objects[0] + if (result) { + const links = result.Links + if (links.length > 0) { + ipfsEntry = mapLinkToIpfsEntry(links[0], path) + } + } + } + } catch (error) { + console.error('error', error) + } finally { + if (timerId) { + clearTimeout(timerId) + } + } + } catch (error) { + console.error('error', error) + } + + return ipfsEntry +} + +function mapLinkToIpfsEntry(link: IIpfsLink, path: string): IIPfsEntry { + const hash = link.Hash.startsWith(IPFS_PREFIX) ? link.Hash.slice(IPFS_PREFIX.length) : link.Hash + const entry: IIPfsEntry = { + name: link.Name, + path: path + (link.Name ? `/${link.Name}` : ''), + size: link.Size, + cid: hash, + type: typeOf(link), + } + if (link.Mode) { + entry.mode = Number.parseInt(link.Mode, 8) + } + + if (link.Mtime !== undefined && link.Mtime !== null) { + entry.mtime = { + secs: link.Mtime, + } + + if (link.MtimeNsecs !== undefined && link.MtimeNsecs !== null) { + entry.mtime.nsecs = link.MtimeNsecs + } + } + + return entry +} + +function typeOf(link: IIpfsLink): typeOfLink { + switch (link.Type) { + case 1: + case 5: { + return typeOfLink.Dir + } + case 2: { + return typeOfLink.File + } + default: { + return typeOfLink.File + } + } +} diff --git a/packages/shared/lib/core/nfts/utils/index.ts b/packages/shared/lib/core/nfts/utils/index.ts index ea8bd888dc2..250a101e79b 100644 --- a/packages/shared/lib/core/nfts/utils/index.ts +++ b/packages/shared/lib/core/nfts/utils/index.ts @@ -2,9 +2,10 @@ export * from './buildNftFromNftOutput' export * from './checkIfNftShouldBeDownloaded' export * from './composeUrlFromNftUri' export * from './convertAndFormatNftMetadata' -export * from './getSpendableStatusFromUnspentNftOutput' export * from './fetchWithTimeout' +export * from './getIpfsUri' +export * from './getParentMimeType' +export * from './getSpendableStatusFromUnspentNftOutput' export * from './isNftOwnedByAnyAccount' export * from './parseNftMetadata' export * from './rewriteIpfsUri' -export * from './getParentMimeType' From 885abaedc079630a7c6182b870555f5f8f027ce0 Mon Sep 17 00:00:00 2001 From: evavirseda <evirseda@boxfish.studio> Date: Wed, 29 Nov 2023 15:52:26 +0100 Subject: [PATCH 2/6] feat: display ipfs media --- packages/shared/components/MediaDisplay.svelte | 2 +- packages/shared/components/NftImageOrIconBox.svelte | 7 +++---- .../lib/core/nfts/actions/downloadNextNftInQueue.ts | 10 +++++++++- .../core/nfts/actions/updateNftInAllAccountNfts.ts | 12 +++++++++--- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/shared/components/MediaDisplay.svelte b/packages/shared/components/MediaDisplay.svelte index 8df73db2c8a..d08e9111b20 100644 --- a/packages/shared/components/MediaDisplay.svelte +++ b/packages/shared/components/MediaDisplay.svelte @@ -31,7 +31,7 @@ </script> <div class="h-full w-full object-cover"> - {#if htmlTag === ParentMimeType.Image} + {#if htmlTag === ParentMimeType.Image || htmlTag === ParentMimeType.Text} <img {src} {alt} loading="lazy" class="w-full h-full object-cover" /> {:else if htmlTag === ParentMimeType.Video} <video diff --git a/packages/shared/components/NftImageOrIconBox.svelte b/packages/shared/components/NftImageOrIconBox.svelte index c821731dc24..a1c9b5dd1ad 100644 --- a/packages/shared/components/NftImageOrIconBox.svelte +++ b/packages/shared/components/NftImageOrIconBox.svelte @@ -1,8 +1,7 @@ <script lang="ts"> - import { INft } from '@core/nfts' - import { NftSize } from 'shared/components/enums' + import { INft, ParentMimeType } from '@core/nfts' import { MediaPlaceholder, NftMedia } from 'shared/components' - import { ParentMimeType } from '@core/nfts' + import { NftSize } from 'shared/components/enums' export let nft: INft | null = null export let size: NftSize = NftSize.Medium @@ -18,7 +17,7 @@ class:medium={size === NftSize.Medium} class:large={size === NftSize.Large} > - {#if parentType === ParentMimeType.Image && nft} + {#if (parentType === ParentMimeType.Image && nft) || (parentType === ParentMimeType.Text && nft)} <NftMedia {nft} {useCaching}> <placeholder-wrapper slot="placeholder" diff --git a/packages/shared/lib/core/nfts/actions/downloadNextNftInQueue.ts b/packages/shared/lib/core/nfts/actions/downloadNextNftInQueue.ts index e67fa150a36..9d926972604 100644 --- a/packages/shared/lib/core/nfts/actions/downloadNextNftInQueue.ts +++ b/packages/shared/lib/core/nfts/actions/downloadNextNftInQueue.ts @@ -1,6 +1,7 @@ import { Platform } from '@core/app' import { get } from 'svelte/store' import { downloadingNftId, nftDownloadQueue, removeNftFromDownloadQueue } from '../stores' +import { getIpfsUri } from '../utils' export async function downloadNextNftInQueue(): Promise<void> { const nextDownload = get(nftDownloadQueue)?.[0] @@ -9,8 +10,15 @@ export async function downloadNextNftInQueue(): Promise<void> { } try { - const { downloadUrl, path, nft, accountIndex } = nextDownload + // eslint-disable-next-line prefer-const + let { downloadUrl, path, nft, accountIndex } = nextDownload downloadingNftId.set(nft.id) + const ipfsUri = await getIpfsUri({ hash: downloadUrl }) + + if (ipfsUri) { + downloadUrl = ipfsUri + } + await Platform.downloadNft(downloadUrl, path, nft.id, accountIndex) } catch (error) { downloadingNftId.set(undefined) diff --git a/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts b/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts index 62f63dd2936..6caaf437e99 100644 --- a/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts +++ b/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts @@ -1,14 +1,20 @@ import { allAccountNfts } from '../stores' -import { INft } from '../interfaces' +import { getIpfsUri } from '../utils' -export function updateNftInAllAccountNfts(accountIndex: number, nftId: string, partialNft: Partial<INft>): void { +export function updateNftInAllAccountNfts(accountIndex: number, nftId: string): void { allAccountNfts.update((state) => { if (!state[accountIndex]) { state[accountIndex] = [] } const nft = state[accountIndex].find((_nft) => _nft.id === nftId) if (nft) { - Object.assign(nft, { ...nft, ...partialNft }) + const downloadUrl = nft.downloadUrl + void getIpfsUri({ hash: downloadUrl }).then((ipfsUri) => { + if (ipfsUri) { + nft.downloadUrl = ipfsUri + nft.composedUrl = ipfsUri + } + }) } return state }) From 10d673511ede4e2a1bdf8ba538f9a9a76edaa472 Mon Sep 17 00:00:00 2001 From: evavirseda <evirseda@boxfish.studio> Date: Wed, 29 Nov 2023 16:08:09 +0100 Subject: [PATCH 3/6] fix types --- .../shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts b/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts index 6caaf437e99..80009e7a414 100644 --- a/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts +++ b/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts @@ -1,7 +1,8 @@ +import { INft } from '../interfaces' import { allAccountNfts } from '../stores' import { getIpfsUri } from '../utils' -export function updateNftInAllAccountNfts(accountIndex: number, nftId: string): void { +export function updateNftInAllAccountNfts(accountIndex: number, nftId: string, partialNft: Partial<INft>): void { allAccountNfts.update((state) => { if (!state[accountIndex]) { state[accountIndex] = [] @@ -15,6 +16,7 @@ export function updateNftInAllAccountNfts(accountIndex: number, nftId: string): nft.composedUrl = ipfsUri } }) + Object.assign(nft, { ...nft, ...partialNft }) } return state }) From 2edca0ef768d97cfdc3f74a2bf3d62f0e0bdc5de Mon Sep 17 00:00:00 2001 From: evavirseda <evirseda@boxfish.studio> Date: Mon, 4 Dec 2023 13:46:38 +0100 Subject: [PATCH 4/6] fix: check dowloadUrl and replace with constant --- .../core/nfts/actions/updateNftInAllAccountNfts.ts | 14 ++++++++------ packages/shared/lib/core/nfts/utils/getIpfsUri.ts | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts b/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts index 80009e7a414..07ab4890733 100644 --- a/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts +++ b/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts @@ -10,12 +10,14 @@ export function updateNftInAllAccountNfts(accountIndex: number, nftId: string, p const nft = state[accountIndex].find((_nft) => _nft.id === nftId) if (nft) { const downloadUrl = nft.downloadUrl - void getIpfsUri({ hash: downloadUrl }).then((ipfsUri) => { - if (ipfsUri) { - nft.downloadUrl = ipfsUri - nft.composedUrl = ipfsUri - } - }) + if (downloadUrl) { + void getIpfsUri({ hash: downloadUrl }).then((ipfsUri) => { + if (ipfsUri) { + nft.downloadUrl = ipfsUri + nft.composedUrl = ipfsUri + } + }) + } Object.assign(nft, { ...nft, ...partialNft }) } return state diff --git a/packages/shared/lib/core/nfts/utils/getIpfsUri.ts b/packages/shared/lib/core/nfts/utils/getIpfsUri.ts index b313f87a2cd..4ab4c091557 100644 --- a/packages/shared/lib/core/nfts/utils/getIpfsUri.ts +++ b/packages/shared/lib/core/nfts/utils/getIpfsUri.ts @@ -38,7 +38,7 @@ const IPFS_PREFIX = '/ipfs/' export async function getIpfsUri(link: { path?: string; hash: string }): Promise<string | undefined> { try { - const slicedLink = link.hash.slice('https://ipfs.io/'.length) + const slicedLink = link.hash.slice(IPFS_ENDPOINT.length) const ipfsEntry = await ls(slicedLink) if (ipfsEntry) { if (ipfsEntry.type === 'dir') { From 14db8a8aa0c527bc089c1ec7b696e178421bddba Mon Sep 17 00:00:00 2001 From: evavirseda <evirseda@boxfish.studio> Date: Tue, 12 Dec 2023 17:43:32 +0100 Subject: [PATCH 5/6] fix: check if ipfsHash exists --- .../lib/core/nfts/actions/downloadNextNftInQueue.ts | 12 +++++++----- .../core/nfts/actions/updateNftInAllAccountNfts.ts | 5 +++-- packages/shared/lib/core/nfts/utils/getIpfsHash.ts | 7 +++++++ packages/shared/lib/core/nfts/utils/index.ts | 1 + 4 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 packages/shared/lib/core/nfts/utils/getIpfsHash.ts diff --git a/packages/shared/lib/core/nfts/actions/downloadNextNftInQueue.ts b/packages/shared/lib/core/nfts/actions/downloadNextNftInQueue.ts index 9d926972604..2194d600644 100644 --- a/packages/shared/lib/core/nfts/actions/downloadNextNftInQueue.ts +++ b/packages/shared/lib/core/nfts/actions/downloadNextNftInQueue.ts @@ -1,7 +1,7 @@ import { Platform } from '@core/app' import { get } from 'svelte/store' import { downloadingNftId, nftDownloadQueue, removeNftFromDownloadQueue } from '../stores' -import { getIpfsUri } from '../utils' +import { getIpfsUri, getIPFSHash } from '../utils' export async function downloadNextNftInQueue(): Promise<void> { const nextDownload = get(nftDownloadQueue)?.[0] @@ -13,10 +13,12 @@ export async function downloadNextNftInQueue(): Promise<void> { // eslint-disable-next-line prefer-const let { downloadUrl, path, nft, accountIndex } = nextDownload downloadingNftId.set(nft.id) - const ipfsUri = await getIpfsUri({ hash: downloadUrl }) - - if (ipfsUri) { - downloadUrl = ipfsUri + const ipfsHash = getIPFSHash(downloadUrl) + if (ipfsHash) { + const ipfsUri = await getIpfsUri({ hash: downloadUrl }) + if (ipfsUri) { + downloadUrl = ipfsUri + } } await Platform.downloadNft(downloadUrl, path, nft.id, accountIndex) diff --git a/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts b/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts index 07ab4890733..264a2ca5e53 100644 --- a/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts +++ b/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts @@ -1,6 +1,6 @@ import { INft } from '../interfaces' import { allAccountNfts } from '../stores' -import { getIpfsUri } from '../utils' +import { getIpfsUri, getIPFSHash } from '../utils' export function updateNftInAllAccountNfts(accountIndex: number, nftId: string, partialNft: Partial<INft>): void { allAccountNfts.update((state) => { @@ -10,7 +10,8 @@ export function updateNftInAllAccountNfts(accountIndex: number, nftId: string, p const nft = state[accountIndex].find((_nft) => _nft.id === nftId) if (nft) { const downloadUrl = nft.downloadUrl - if (downloadUrl) { + const ipfsHash = getIPFSHash(downloadUrl) + if (ipfsHash) { void getIpfsUri({ hash: downloadUrl }).then((ipfsUri) => { if (ipfsUri) { nft.downloadUrl = ipfsUri diff --git a/packages/shared/lib/core/nfts/utils/getIpfsHash.ts b/packages/shared/lib/core/nfts/utils/getIpfsHash.ts new file mode 100644 index 00000000000..d7d832f0681 --- /dev/null +++ b/packages/shared/lib/core/nfts/utils/getIpfsHash.ts @@ -0,0 +1,7 @@ +export function getIPFSHash(url?: string): string | undefined { + const ipfsPrefix = 'ipfs' + + if (url?.includes(ipfsPrefix)) { + return url.slice(ipfsPrefix.length) + } +} diff --git a/packages/shared/lib/core/nfts/utils/index.ts b/packages/shared/lib/core/nfts/utils/index.ts index 250a101e79b..717cbfe65c5 100644 --- a/packages/shared/lib/core/nfts/utils/index.ts +++ b/packages/shared/lib/core/nfts/utils/index.ts @@ -3,6 +3,7 @@ export * from './checkIfNftShouldBeDownloaded' export * from './composeUrlFromNftUri' export * from './convertAndFormatNftMetadata' export * from './fetchWithTimeout' +export * from './getIpfsHash' export * from './getIpfsUri' export * from './getParentMimeType' export * from './getSpendableStatusFromUnspentNftOutput' From 085399e2f943177d0b540c594ec6b2339c14632e Mon Sep 17 00:00:00 2001 From: Branko Bosnic <brankobosnic1@gmail.com> Date: Wed, 13 Dec 2023 10:52:14 +0100 Subject: [PATCH 6/6] fix: ipfs hash and ipfs uri --- .../lib/core/nfts/actions/downloadNextNftInQueue.ts | 2 +- .../lib/core/nfts/actions/updateNftInAllAccountNfts.ts | 2 +- packages/shared/lib/core/nfts/utils/getIpfsHash.ts | 2 +- packages/shared/lib/core/nfts/utils/getIpfsUri.ts | 9 ++++++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/shared/lib/core/nfts/actions/downloadNextNftInQueue.ts b/packages/shared/lib/core/nfts/actions/downloadNextNftInQueue.ts index 2194d600644..33a18312c13 100644 --- a/packages/shared/lib/core/nfts/actions/downloadNextNftInQueue.ts +++ b/packages/shared/lib/core/nfts/actions/downloadNextNftInQueue.ts @@ -15,7 +15,7 @@ export async function downloadNextNftInQueue(): Promise<void> { downloadingNftId.set(nft.id) const ipfsHash = getIPFSHash(downloadUrl) if (ipfsHash) { - const ipfsUri = await getIpfsUri({ hash: downloadUrl }) + const ipfsUri = await getIpfsUri({ hash: ipfsHash }) if (ipfsUri) { downloadUrl = ipfsUri } diff --git a/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts b/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts index 264a2ca5e53..bd4214c2655 100644 --- a/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts +++ b/packages/shared/lib/core/nfts/actions/updateNftInAllAccountNfts.ts @@ -12,7 +12,7 @@ export function updateNftInAllAccountNfts(accountIndex: number, nftId: string, p const downloadUrl = nft.downloadUrl const ipfsHash = getIPFSHash(downloadUrl) if (ipfsHash) { - void getIpfsUri({ hash: downloadUrl }).then((ipfsUri) => { + void getIpfsUri({ hash: ipfsHash }).then((ipfsUri) => { if (ipfsUri) { nft.downloadUrl = ipfsUri nft.composedUrl = ipfsUri diff --git a/packages/shared/lib/core/nfts/utils/getIpfsHash.ts b/packages/shared/lib/core/nfts/utils/getIpfsHash.ts index d7d832f0681..44997e4cca8 100644 --- a/packages/shared/lib/core/nfts/utils/getIpfsHash.ts +++ b/packages/shared/lib/core/nfts/utils/getIpfsHash.ts @@ -1,5 +1,5 @@ export function getIPFSHash(url?: string): string | undefined { - const ipfsPrefix = 'ipfs' + const ipfsPrefix = 'https://ipfs.io' if (url?.includes(ipfsPrefix)) { return url.slice(ipfsPrefix.length) diff --git a/packages/shared/lib/core/nfts/utils/getIpfsUri.ts b/packages/shared/lib/core/nfts/utils/getIpfsUri.ts index 4ab4c091557..3d094ea6a87 100644 --- a/packages/shared/lib/core/nfts/utils/getIpfsUri.ts +++ b/packages/shared/lib/core/nfts/utils/getIpfsUri.ts @@ -37,19 +37,22 @@ const IPFS_PATH = '/api/v0/ls' const IPFS_PREFIX = '/ipfs/' export async function getIpfsUri(link: { path?: string; hash: string }): Promise<string | undefined> { + let ipfsLink = `${link.hash}${link.path ?? ''}` try { - const slicedLink = link.hash.slice(IPFS_ENDPOINT.length) - const ipfsEntry = await ls(slicedLink) + const ipfsEntry = await ls(ipfsLink) + if (ipfsEntry) { if (ipfsEntry.type === 'dir') { const path = `${link.path ?? ''}/${ipfsEntry.name}` return await getIpfsUri({ hash: link.hash, path }) } - return `${link.hash}/${encodeURIComponent(ipfsEntry.name)}` + ipfsLink = `${ipfsLink}/${encodeURIComponent(ipfsEntry.name)}` } } catch (error) { console.error('error', error) } + + return `${IPFS_ENDPOINT}${ipfsLink}` } async function ls(path: string): Promise<IIPfsEntry | undefined> {