Skip to content

Commit

Permalink
refactor: Replace StorageNode upload with IPFS upload
Browse files Browse the repository at this point in the history
  • Loading branch information
bigint committed Mar 3, 2025
1 parent 5f715d3 commit e0cc078
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 57 deletions.
4 changes: 2 additions & 2 deletions apps/web/src/components/Composer/ChooseThumbnail.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ThumbnailsShimmer from "@components/Shared/Shimmer/ThumbnailsShimmer";
import { uploadFileToStorageNode } from "@helpers/uploadToStorageNode";
import { uploadFileToIPFS } from "@helpers/uploadToIPFS";
import { CheckCircleIcon, PhotoIcon } from "@heroicons/react/24/outline";
import { generateVideoThumbnails } from "@hey/helpers/generateVideoThumbnails";
import getFileFromDataURL from "@hey/helpers/getFileFromDataURL";
Expand Down Expand Up @@ -28,7 +28,7 @@ const ChooseThumbnail: FC = () => {

const uploadThumbnailToStorageNode = async (fileToUpload: File) => {
setVideoThumbnail({ ...videoThumbnail, uploading: true });
const result = await uploadFileToStorageNode(fileToUpload);
const result = await uploadFileToIPFS(fileToUpload);
if (!result.uri) {
toast.error("Failed to upload thumbnail");
}
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/Shared/Audio/CoverImage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import errorToast from "@helpers/errorToast";
import { uploadFileToStorageNode } from "@helpers/uploadToStorageNode";
import { uploadFileToIPFS } from "@helpers/uploadToIPFS";
import { PhotoIcon } from "@heroicons/react/24/outline";
import { ATTACHMENT } from "@hey/data/constants";
import imageKit from "@hey/helpers/imageKit";
Expand Down Expand Up @@ -36,7 +36,7 @@ const CoverImage: FC<CoverImageProps> = ({
try {
setIsSubmitting(true);
const file = event.target.files[0];
const attachment = await uploadFileToStorageNode(file);
const attachment = await uploadFileToIPFS(file);
setCover(
URL.createObjectURL(file),
attachment.uri,
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/helpers/accountPictureUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import imageCompression from "browser-image-compression";
import { uploadFileToStorageNode } from "./uploadToStorageNode";
import { uploadFileToIPFS } from "./uploadToIPFS";

/**
* Read a file as a base64 string
Expand Down Expand Up @@ -36,10 +36,10 @@ const uploadCroppedImage = async (
maxWidthOrHeight: 3000,
useWebWorker: true
});
const attachment = await uploadFileToStorageNode(cleanedFile);
const attachment = await uploadFileToIPFS(cleanedFile);
const decentralizedUrl = attachment.uri;
if (!decentralizedUrl) {
throw new Error("uploadFileToStorageNode failed");
throw new Error("uploadFileToIPFS failed");
}

return decentralizedUrl;
Expand Down
104 changes: 104 additions & 0 deletions apps/web/src/helpers/uploadToIPFS.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { S3 } from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
import {
EVER_API,
EVER_BUCKET,
EVER_REGION,
HEY_API_URL
} from "@hey/data/constants";
import axios from "axios";
import { v4 as uuid } from "uuid";

const FALLBACK_TYPE = "image/jpeg";

/**
* Returns an S3 client with temporary credentials obtained from the STS service.
*
* @returns S3 client instance.
*/
const getS3Client = async (): Promise<S3> => {
const { data } = await axios.get(`${HEY_API_URL}/sts/token`);
const client = new S3({
credentials: {
accessKeyId: data?.accessKeyId,
secretAccessKey: data?.secretAccessKey,
sessionToken: data?.sessionToken
},
endpoint: EVER_API,
maxAttempts: 10,
region: EVER_REGION
});

return client;
};

/**
* Uploads a set of files to the IPFS network via S3 and returns an array of MediaSet objects.
*
* @param data Files to upload to IPFS.
* @param onProgress Callback to be called when the upload progress changes.
* @returns Array of MediaSet objects.
*/
const uploadToIPFS = async (
data: any,
onProgress?: (percentage: number) => void
): Promise<{ mimeType: string; uri: string }[]> => {
try {
const files = Array.from(data);
const client = await getS3Client();
const currentDate = new Date()
.toLocaleDateString("en-GB")
.replace(/\//g, "-");

const attachments = await Promise.all(
files.map(async (_: any, i: number) => {
const file = data[i];
const params = {
Body: file,
Bucket: EVER_BUCKET,
ContentType: file.type,
Key: `${currentDate}/${uuid()}`
};
const task = new Upload({ client, params });
task.on("httpUploadProgress", (e) => {
const loaded = e.loaded || 0;
const total = e.total || 0;
const progress = (loaded / total) * 100;
onProgress?.(Math.round(progress));
});
await task.done();
const result = await client.headObject(params);
const metadata = result.Metadata;
const cid = metadata?.["ipfs-hash"];

return { mimeType: file.type || FALLBACK_TYPE, uri: `ipfs://${cid}` };
})
);

return attachments;
} catch {
return [];
}
};

/**
* Uploads a file to the IPFS network via S3 and returns a MediaSet object.
*
* @param file File to upload to IPFS.
* @returns MediaSet object or null if the upload fails.
*/
export const uploadFileToIPFS = async (
file: File,
onProgress?: (percentage: number) => void
): Promise<{ mimeType: string; uri: string }> => {
try {
const ipfsResponse = await uploadToIPFS([file], onProgress);
const metadata = ipfsResponse[0];

return { mimeType: file.type || FALLBACK_TYPE, uri: metadata.uri };
} catch {
return { mimeType: file.type || FALLBACK_TYPE, uri: "" };
}
};

export default uploadToIPFS;
45 changes: 0 additions & 45 deletions apps/web/src/helpers/uploadToStorageNode.ts

This file was deleted.

5 changes: 2 additions & 3 deletions apps/web/src/hooks/useUploadAttachments.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import uploadToStorageNode from "@helpers/uploadToStorageNode";
import uploadToIPFS from "@helpers/uploadToIPFS";
import type { NewAttachment } from "@hey/types/misc";
import imageCompression from "browser-image-compression";
import { useCallback } from "react";
Expand Down Expand Up @@ -92,8 +92,7 @@ const useUploadAttachments = () => {
addAttachments(previewAttachments);

try {
const attachmentsUploaded =
await uploadToStorageNode(compressedFiles);
const attachmentsUploaded = await uploadToIPFS(compressedFiles);
const attachments = attachmentsUploaded.map((uploaded, index) => ({
...previewAttachments[index],
mimeType: uploaded.mimeType,
Expand Down
13 changes: 11 additions & 2 deletions packages/helpers/sanitizeDStorageUrl.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { STORAGE_NODE_URL } from "@hey/data/constants";
import { IPFS_GATEWAY, STORAGE_NODE_URL } from "@hey/data/constants";

/**
* Returns the decentralized storage link for a given hash.
Expand All @@ -11,7 +11,16 @@ const sanitizeDStorageUrl = (hash?: string): string => {
return "";
}

return hash.replace("lens://", `${STORAGE_NODE_URL}/`);
const ipfsGateway = `${IPFS_GATEWAY}/`;

let link = hash.replace(/^Qm[1-9A-Za-z]{44}/gm, `${IPFS_GATEWAY}/${hash}`);
link = link.replace("https://ipfs.io/ipfs/", ipfsGateway);
link = link.replace("ipfs://ipfs/", ipfsGateway);
link = link.replace("ipfs://", ipfsGateway);
link = link.replace("lens://", `${STORAGE_NODE_URL}/`);
link = link.replace("ar://", "https://gateway.arweave.net/");

return link;
};

export default sanitizeDStorageUrl;

0 comments on commit e0cc078

Please sign in to comment.