PDF File Uploaded to IPFS Does Not Display - reactjs

Background
When running my app over localhost, I can choose my PDF file and submit it. I'm able to get the path of the IPFS file and display the path in the console.
Problem
When adding this line to display my file, it doesn't work and shows "No PDF file specified" instead.
<Document src={https://ipfs.infura.io/ipfs/${this.state.ipfshash}} />
<Document file={https://ipfs.infura.io/ipfs/${this.state.docupayHash}} />
What I've Tried
I've gone to the link in Google Chrome (ipfs.infura.io/ipfs/"QmUqB9dWDCeZ5nth9YKRJTQ6PcnfrGPPx1vzdyNWV6rh8s") and I can see the file there, so I know the link is correct.
Code
App.js
import React, { Component } from "react";
import { Document, Page } from 'react-pdf';
import web3 from "./web3";
import ipfs from "./ipfs";
import storehash from "./storehash";
import "./styles/App.css";
class App extends Component {
state = {
contractHash: null,
buffer: "",
ethAddress: "",
blockNumber: "",
transactionHash: ""
};
captureFile = (event) => {
event.stopPropagation()
event.preventDefault();
const file = event.target.files[0];
let reader = new window.FileReader();
reader.readAsArrayBuffer(file);
reader.onloadend = () => this.convertToBuffer(reader);
};
convertToBuffer = async (reader) => {
// Convert file to buffer so that it can be uploaded to IPFS
const buffer = await Buffer.from(reader.result);
this.setState({buffer});
};
onClick = async () => {
try {
await web3.eth.getTransactionReceipt(this.state.transactionHash, (err, txReceipt) => {
console.log(err, txReceipt);
this.setState({txReceipt});
});
} catch (error) {
console.log(error);
}
}
onSubmit = async (event) => {
event.preventDefault();
// Take the user's MetaMask address
const accounts = await web3.eth.getAccounts();
console.log("Sending from Metamask account: " + accounts[0]);
// Retrieve the contract address from storehash.js
const ethAddress= await storehash.options.address;
this.setState({ethAddress});
// Save document to IPFS, return its hash, and set it to state
await ipfs.add(this.state.buffer, (err, contractHash) => {
console.log(err, contractHash);
this.setState({ contractHash: contractHash[0].hash });
storehash.methods.setHash(this.state.contractHash).send({ from: accounts[0] }, (error, transactionHash) => {
console.log(transactionHash);
this.setState({transactionHash});
});
})
};
render() {
return (
<div className="app">
<h3> Choose file to send to IPFS </h3>
<form onSubmit={this.onSubmit}>
<input type="file" onChange={this.captureFile} />
<button type="submit">Submit</button>
</form>
<Document file={`https://ipfs.infura.io/ipfs/${this.state.contractHash}`} />
<a href={`https://ipfs.infura.io/ipfs/${this.state.contractHash}`}>Click to download the file</a>
<button onClick = {this.onClick}>Get Transaction Receipt</button>
<p>IPFS Hash: {this.state.contractHash}</p>
<p>Contract Address: {this.state.ethAddress}</p>
<p>Tx Hash: {this.state.transactionHash}</p>
</div>
);
}
}
export default App;
MyContract.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.16 <0.7.0;
contract MyContract {
string contractHash;
function setHash(string memory ipfsHash) public {
contractHash = ipfsHash;
}
function getHash() public view returns (string memory ipfsHash) {
return contractHash;
}
}
I've looked at other solutions on SO but none that I found were particularly related to my question. Thank you for your help and time!

Two things to try:
Add ?filename= parameter as a hint for both gateway and react-pdf:
<Document src={`https://ipfs.infura.io/ipfs/${this.state.ipfshash}?filename=test.pdf`} />
This will make content-type returned by the gateways more reliable and eliminate false-negatives in react-pdf.
Run your own gateway, or contact Infura and discuss raising request limits for your app.
FYI I've run below test multiple times:
$ curl -Ls 'https://dweb.link/ipfs/QmUqB9dWDCeZ5nth9YKRJTQ6PcnfrGPPx1vzdyNWV6rh8s?filename=test.pdf' > output && file output
output: PDF document, version 1.5
After a few times they stop returning PDF, instead they return HTML page with 429 Too Many Requests error:
output: HTML document, ASCII text, with CRLF line terminators
$ cat output
<html>
<head><title>429 Too Many Requests</title></head>
<body>
<center><h1>429 Too Many Requests</h1></center>
<hr><center>openresty</center>
</body>
</html>
It is very likely that react-pdf is unable to render your PDF because it gets 429 Too Many Requests error response instead of the PDF payload.

Related

How can I upload two files of the same name to AWS amplify and get unique keys for each of them?

Below I have the index page of a Next.JS app configured with an AWS S3 backend:
import styles from "../styles/Home.module.css";
import { useState, useEffect } from "react";
import { Storage } from "aws-amplify";
export default function Home() {
const [songs, setSongs] = useState(null);
useEffect(() => {
fetchSongs();
}, []);
async function fetchSongs() {
let songKeys = await Storage.list("");
console.log("before signing", songKeys);
songKeys = await Promise.all(
songKeys.map(async (k) => {
const signedUrl = await Storage.get(k.key);
return signedUrl;
})
);
console.log("after signing", songKeys);
setSongs(songKeys);
}
async function onChange(event) {
const file = event.target.files[0];
const result = await Storage.put(file.name, file, {
contentType: "audio/*",
});
console.log(result);
fetchSongs();
}
return (
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>Music Streaming Site</h1>
<input type="file" onChange={onChange} accept="audio/*" />
{songs &&
songs.map((song) => (
<div key={song}>
<audio controls>
<source src={song} type="audio/mpeg" />
</audio>
</div>
))}
</main>
</div>
);
}
Currently, when I upload two files of the same name, AWS takes the first file, and the UI updates. But when the second file of the same name is uploaded, AWS takes it, and replaces the original file with it, or in other words, modify the file since the Last Modified column of my AWS console gets a new timestamp at the time the second file was uploaded.
I would like unique keys to be generated each time I upload a file. I would also like to be able to get the file by these unique keys individually, and present them via the UI.
If there's any way for AWS to prevent two of the same file with different names from being uploaded, that would be cool too.

Error: Can't find end of central directory : is this a zip file ? in React & docxtemplater

I am facing an issue in using docxtemplater library to generate a doc file from my react application.
If anybody know how to solve this please help me.
import React from "react";
import Docxtemplater from "docxtemplater";
import PizZip from "pizzip";
import PizZipUtils from "pizzip/utils/index.js";
import { saveAs } from "file-saver";
import Sample from './sample.js';
function loadFile(url, callback) {
PizZipUtils.getBinaryContent(url, callback);
}
function App(){
//const generateDocument = () => {
// fetch("http://localhost:5000/api/doc")
// .then(res => console.log(res))
//};
const generateDocument = () => {
loadFile("./assets/docs/newTemplate.docx",function (error, content) {
if (error) {
throw error;
}
console.log(content);
const zip = new PizZip(content);
const doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
});
// render the document (replace all occurences of {first_name} by John, {last_name} by Doe, ...)
doc.render({
client_name: "John",
});
const out = doc.getZip().generate({
type: "blob",
mimeType:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
}); //Output the document using Data-URI
saveAs(out, "output.docx");
}
)};
return (
<div className="p-2">
<button onClick={generateDocument}>
Generate document
</button>
<Sample></Sample>
</div>
);
};
export default App;
My template file is in assets/docs folder.
I tried various ways of using template in same folder & changing template with new templates but nothing worked. Please help me!
It might be caused by the name of the file containing Upper case, fix that - et voilĂ !

Display audio file in frontend through axios get request. Nodejs Reactjs Mongodb , multer , Gridfs

I used mongodb , gridfs ,multer to store the audio file.
And need to show the audio file in reactjs frontend through axios.
app.get('/songs/getsong/:filename', (req, res) => {
console.log(req.params.filename);
const file = gfs
.find({
filename: req.params.filename
})
.toArray((err, files) => {
if (!files || files.length === 0) {
return res.status(404).json({
err: "no files exist"
});
}
gfs.openDownloadStreamByName(req.params.filename).pipe(res);
console.log(files)
return res.status(200);
});
});
Display audio file in frontend
import React, { useEffect ,useState} from 'react'
import Header from '../header/Header';
import "./home.css"
import axios from 'axios';
export default function Home(){
const [audiofile , setAudioFile] = useState()
axios.get('http://localhost:8000/songs/getsong/379500b35576a4c49792718aebfc27ef.mp3')
.then((res) => {
console.log(res);
setAudioFile(res)
}).catch((error) => {
console.log(error)
});
return (
<div>
<Header />
<form action="http://localhost:8000/songs/upload" method="post" enctype="multipart/form-data">
<h1>File Upload</h1>
<input id="file" type="file" accept="audio/*" name='file' />
<input type="submit" />
</form>
<audio
controls
src={audiofile}>
Your browser does not support the
<code>audio</code> element.
</audio>
</div>
)
}
this is the file request
{
_id: 60774f8560de1713d4b06234,
length: 11756413,
chunkSize: 261120,
uploadDate: 2021-04-14T20:25:25.300Z,
filename: '379500b35576a4c49792718aebfc27ef.mp3',
md5: '36d8d712eb886859b07952e49d4de6a6',
contentType: 'audio/mpeg'
}
In postman it showed a audio file and it works fine. I need to show it
on frontend.
Check Postman response ss here
Thank you.
Locally, you can use the HTML5 Audio tag to play the audio, feed src with URL of the audio and , that's it.
its probably not playing because you are setting audio file with res instead of res.data, then confirm the format of the response. its usually in binary format so you need to convert before playing, you can blob() and URL.createObjectURL()

What is the Syntax for displaying a State img Url in React?

I think I have a simple question to which I haven't found an answer which would work for me.
The main idea is: Once the User uploads the image to aws s3 it would get displayed (the reading from DB still in progress, but it would work as below +/-).
Why isn't the below img not displaying in React, if I set it manually to it it works. :
handleFiles = async (e) => {
const uploadedImage = await uploadFile(e.target.files[0]);
this.setState({imgLink:uploadedImage})
};
state = {
imgLink: "../../../images/person-01.jpg",
};
render() {
.....
<div className="bg-transfer">
<img src={this.state.imgLink} alt="" />
</div>
.....
<div className="single-file-input">
<input
type="file"
id="user_image"
name="user_image"
onChange={this.handleFiles}
/>
Upload file:
import S3FileUpload from "react-s3";
const ID = "";
const SECRET = "";
//If bucket not set, getting 400 error.
const BUCKET_NAME = "";
//If Region not set then this isn't working, getting 400 error
const REGION_NAME = "";
const config = {
bucketName: BUCKET_NAME,
/*dirName: 'media', /* optional */
region: REGION_NAME,
accessKeyId: ID,
secretAccessKey: SECRET,
/*s3Url: 'https:/your-custom-s3-url.com/', /* optional */
};
export const uploadFile = async (file) => {
try {
const response = await S3FileUpload.uploadFile(file, config)
return response.location;
} catch (error) {
console.log(error);
}
};
I tried to use imgLink: "\"../../../images/person-01.jpg\"",, as I thought I am missing links, but it didn't work either. Since I will be reading the path from MongoDB I can't simply import the image, but would require this to be dynamically changed.
What syntax should be used here?
uploadedImaged is probably not a valid url link, unless the function uploadFile returns the url generated for the file. You should be update the state with the url for the file generated in s3.
I am not quite sure why I can't use the state for the img URL still, but turns out that if you take the URL from MongoDb directly and place in the HTML, it is displayed. Which is what I wanted at first.
This time the middle developing step was not necessary.

Passing correct properties to popup notification

I want to show a notification with the upload status. I took over a project in React & ASP.NET and I am relatively new to this. The question is quite simple, yet I am struggling to solve it: How do I display a popup notification showing which files have been successfully been uploaded and which not?
import * as React from "react";
import { connect } from "react-redux";
import { Form, Select, Button, Upload, message, notification} from 'antd';
import * as Actions from "../actions";
const FormItem = Form.Item;
class UploadFileForm extends React.Component<any, any> {
constructor(props: any) {
super(props);
}
handleSubmit = (e) => {
message.config({ top: 0 });
message.loading('Importing in progress...', 3);
e.preventDefault();
this.props.uploadFile(this.props.form.getFieldsValue());
notification["info"]({
message: 'Files successfully uploaded',
description: '', // <-- this line has to be modified
duration: 10
});
}
render() {
const { getFieldDecorator } = this.props.form;
return (
<Form onSubmit={this.handleSubmit}>
<FormItem label="File" >
{getFieldDecorator('upload', {
valuePropName: 'fileList',
getValueFromEvent: (e) => e.fileList.slice(-1)
})(
<Upload name="importFile" action={' '} multiple={false}>
<Button> Upload </Button>
</Upload>
)}
</FormItem>
<Button type="primary" htmlType="submit">Import</Button>
</Form>
);
}
}
export default Form.create()(UploadFileForm);
More specifically: How do I have to modify the line description: '', to show me a list of all uploaded files and their status as pure text, e.g. File(s) '1.txt', '2.txt', and '3.txt' have been successfully uploaded. File(s) '4.txt' failed.?
The project documentation says that we are using Redux-Saga, but I am not so maybe that makes the story easier.
I guess your this.props.uploadFile method is a promise so considering that you should show notification once that promise is resolved
this.props.uploadFile(this.props.form.getFieldsValue()).then(result => {
// since your client doesn't know which ones are success/failed, server should return
// this information when request is finished
const { successUploads, failUploads } = result;
notification["info"]({
message: 'Files successfully uploaded',
description: `File(s) ${successUploads.join(', ')} have been successfully uploaded. File(s) ${failUploads.join(', ')} failed.`
duration: 10
});
});
If you can't control whats returned from the server then you'd need to track uploads on client side, but that would mean having multiple uploads (requests) to the server and your upload method would look something like this:
async function uploadFiles(files) {
// I've called your server upload uploadService.send(), but replace this with your method
const results = await Promise.all(
files.map(file => uploadService.send(file))
.map(p => p.catch(e => e)
);
let successUploads = [];
let failUploads = [];
results.forEach((result, idx) => {
const file = files[idx];
if (result instanceof Error) {
failUploads.push(file);
} else {
successUploads.push(file);
}
});
return {
successUploads,
failUploads
}
}
Then you could call uploadFiles same way as shown in first snippet.

Resources