Merge pull request #236 from NebulousLabs/improve-error-reporting
improve upload and retrieve frontend errors reporting
This commit is contained in:
commit
7adb89a825
|
@ -5,6 +5,7 @@
|
|||
"author": "Nebulous",
|
||||
"dependencies": {
|
||||
"axios": "^0.19.2",
|
||||
"bytes": "^3.1.0",
|
||||
"classnames": "^2.2.6",
|
||||
"gatsby": "^2.22.11",
|
||||
"gatsby-image": "^2.4.5",
|
||||
|
@ -17,6 +18,7 @@
|
|||
"gatsby-plugin-sharp": "^2.6.9",
|
||||
"gatsby-source-filesystem": "^2.3.8",
|
||||
"gatsby-transformer-sharp": "^2.5.3",
|
||||
"http-status-codes": "^1.4.0",
|
||||
"jsonp": "^0.2.1",
|
||||
"node-sass": "^4.14.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
|
@ -30,7 +32,8 @@
|
|||
"react-mailchimp-subscribe": "^2.1.0",
|
||||
"react-reveal": "^1.2.2",
|
||||
"react-syntax-highlighter": "^12.2.1",
|
||||
"react-visibility-sensor": "^5.1.1"
|
||||
"react-visibility-sensor": "^5.1.1",
|
||||
"skynet-js": "^0.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cypress": "^4.7.0",
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React, { useState, useContext, useEffect } from "react";
|
||||
import bytes from "bytes";
|
||||
import classNames from "classnames";
|
||||
import HttpStatus from "http-status-codes";
|
||||
import path from "path-browserify";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import Reveal from "react-reveal/Reveal";
|
||||
|
@ -7,12 +9,69 @@ import { Button, UploadFile } from "../";
|
|||
import { Deco3, Deco4, Deco5, Folder, DownArrow } from "../../svg";
|
||||
import "./HomeUpload.scss";
|
||||
import AppContext from "../../AppContext";
|
||||
import axios from "axios";
|
||||
import SkynetClient, { parseSkylink } from "skynet-js";
|
||||
|
||||
const isValidSkylink = (skylink) => {
|
||||
try {
|
||||
parseSkylink(skylink); // try to parse the skylink, it will throw on error
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const getFilePath = (file) => file.webkitRelativePath || file.path || file.name;
|
||||
|
||||
const getRelativeFilePath = (file) => {
|
||||
const filePath = getFilePath(file);
|
||||
const { root, dir, base } = path.parse(filePath);
|
||||
const relative = path.normalize(dir).slice(root.length).split(path.sep).slice(1);
|
||||
|
||||
return path.join(...relative, base);
|
||||
};
|
||||
|
||||
const getRootDirectory = (file) => {
|
||||
const filePath = getFilePath(file);
|
||||
const { root, dir } = path.parse(filePath);
|
||||
|
||||
return path.normalize(dir).slice(root.length).split(path.sep)[0];
|
||||
};
|
||||
|
||||
const createUploadErrorMessage = (error) => {
|
||||
// The request was made and the server responded with a status code that falls out of the range of 2xx
|
||||
if (error.response) {
|
||||
if (error.response.data.message) {
|
||||
return `Upload failed with error: ${error.response.data.message}`;
|
||||
}
|
||||
|
||||
const statusCode = error.response.status;
|
||||
const statusText = HttpStatus.getStatusText(error.response.status);
|
||||
|
||||
return `Upload failed, our server received your request but failed with status code: ${statusCode} ${statusText}`;
|
||||
}
|
||||
|
||||
// The request was made but no response was received. The best we can do is detect whether browser is online.
|
||||
// This will be triggered mostly if the server is offline or misconfigured and doesn't respond to valid request.
|
||||
if (error.request) {
|
||||
if (!navigator.onLine) {
|
||||
return "You are offline, please connect to the internet and try again";
|
||||
}
|
||||
|
||||
// TODO: We should add a note "our team has been notified" and have some kind of notification with this error.
|
||||
return "Server failed to respond to your request, please try again later.";
|
||||
}
|
||||
|
||||
// TODO: We should add a note "our team has been notified" and have some kind of notification with this error.
|
||||
return `Critical error, please refresh the application and try again. ${error.message}`;
|
||||
};
|
||||
|
||||
export default function HomeUpload() {
|
||||
const [files, setFiles] = useState([]);
|
||||
const [skylink, setSkylink] = useState("");
|
||||
const { apiUrl } = useContext(AppContext);
|
||||
const [directoryMode, setDirectoryMode] = useState(false);
|
||||
const client = new SkynetClient(apiUrl);
|
||||
|
||||
useEffect(() => {
|
||||
if (directoryMode) {
|
||||
|
@ -22,23 +81,6 @@ export default function HomeUpload() {
|
|||
}
|
||||
}, [directoryMode]);
|
||||
|
||||
const getFilePath = (file) => file.webkitRelativePath || file.path || file.name;
|
||||
|
||||
const getRelativeFilePath = (file) => {
|
||||
const filePath = getFilePath(file);
|
||||
const { root, dir, base } = path.parse(filePath);
|
||||
const relative = path.normalize(dir).slice(root.length).split(path.sep).slice(1);
|
||||
|
||||
return path.join(...relative, base);
|
||||
};
|
||||
|
||||
const getRootDirectory = (file) => {
|
||||
const filePath = getFilePath(file);
|
||||
const { root, dir } = path.parse(filePath);
|
||||
|
||||
return path.normalize(dir).slice(root.length).split(path.sep)[0];
|
||||
};
|
||||
|
||||
const handleDrop = async (acceptedFiles) => {
|
||||
if (directoryMode && acceptedFiles.length) {
|
||||
const rootDir = getRootDirectory(acceptedFiles[0]); // get the file path from the first file
|
||||
|
@ -63,39 +105,35 @@ export default function HomeUpload() {
|
|||
});
|
||||
};
|
||||
|
||||
const upload = async (formData, directory, file) => {
|
||||
const uploadUrl = `${apiUrl}/skynet/skyfile/${directory ? `?filename=${encodeURIComponent(directory)}` : ""}`;
|
||||
const { data } = await axios.post(uploadUrl, formData, {
|
||||
onUploadProgress: ({ loaded, total }) => {
|
||||
const progress = loaded / total;
|
||||
const status = progress === 1 ? "processing" : "uploading";
|
||||
|
||||
onFileStateChange(file, { status, progress });
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
acceptedFiles.forEach(async (file) => {
|
||||
const onUploadProgress = ({ loaded, total }) => {
|
||||
const progress = loaded / total;
|
||||
const status = progress === 1 ? "processing" : "uploading";
|
||||
|
||||
onFileStateChange(file, { status, progress });
|
||||
};
|
||||
|
||||
// Reject files larger than our hard limit of 1 GB with proper message
|
||||
if (file.size > bytes("1 GB")) {
|
||||
onFileStateChange(file, { status: "error", error: "This file size exceeds the maximum allowed size of 1 GB." });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
let response;
|
||||
|
||||
if (file.directory) {
|
||||
file.files.forEach((directoryFile) => {
|
||||
const relativeFilePath = getRelativeFilePath(directoryFile);
|
||||
const directory = file.files.reduce((acc, file) => ({ ...acc, [getRelativeFilePath(file)]: file }), {});
|
||||
|
||||
formData.append("files[]", directoryFile, relativeFilePath);
|
||||
});
|
||||
response = await client.uploadDirectory(directory, encodeURIComponent(file.name), { onUploadProgress });
|
||||
} else {
|
||||
formData.append("file", file);
|
||||
response = await client.upload(file, { onUploadProgress });
|
||||
}
|
||||
|
||||
const { skylink } = await upload(formData, directoryMode && file.name, file);
|
||||
|
||||
onFileStateChange(file, { status: "complete", url: `${apiUrl}/${skylink}` });
|
||||
onFileStateChange(file, { status: "complete", url: client.getUrl(response.skylink) });
|
||||
} catch (error) {
|
||||
onFileStateChange(file, { status: "error" });
|
||||
onFileStateChange(file, { status: "error", error: createUploadErrorMessage(error) });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -105,10 +143,9 @@ export default function HomeUpload() {
|
|||
const handleSkylink = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const skylink = event.target.skylink.value.replace("sia://", "");
|
||||
|
||||
if (skylink.match(/^[a-zA-Z0-9_-]{46}$/)) {
|
||||
window.open(skylink, "_blank");
|
||||
// only try to open a valid skylink
|
||||
if (isValidSkylink(skylink)) {
|
||||
client.open(skylink);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -163,8 +200,17 @@ export default function HomeUpload() {
|
|||
<h3 id="skylink-retrieve-title">Have a Skylink?</h3>
|
||||
<p>Paste the link to retrieve your file</p>
|
||||
|
||||
<form className="home-upload-retrieve-form" onSubmit={handleSkylink}>
|
||||
<input name="skylink" type="text" placeholder="sia://" aria-labelledby="skylink-retrieve-title" />
|
||||
<form
|
||||
className={classNames("home-upload-retrieve-form", { invalid: skylink && !isValidSkylink(skylink) })}
|
||||
onSubmit={handleSkylink}
|
||||
>
|
||||
<input
|
||||
name="skylink"
|
||||
type="text"
|
||||
placeholder="sia://"
|
||||
aria-labelledby="skylink-retrieve-title"
|
||||
onChange={(event) => setSkylink(event.target.value)}
|
||||
/>
|
||||
<button type="submit" aria-label="Retrieve file">
|
||||
<DownArrow />
|
||||
</button>
|
||||
|
|
|
@ -245,4 +245,29 @@
|
|||
border-color: $green;
|
||||
}
|
||||
}
|
||||
|
||||
&.invalid {
|
||||
input {
|
||||
border-color: $red;
|
||||
}
|
||||
|
||||
input:hover,
|
||||
input:hover + button {
|
||||
border-color: $red;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
input:focus + button {
|
||||
border-color: $red;
|
||||
}
|
||||
|
||||
button {
|
||||
border-color: $red;
|
||||
|
||||
&:hover {
|
||||
background: $red;
|
||||
border-color: $red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import "./UploadFile.scss";
|
|||
import { LoadingSpinner } from "../";
|
||||
import { File, FileCheck, FileError, Copy } from "../../svg";
|
||||
|
||||
export default function UploadFile({ file, url, status, progress }) {
|
||||
export default function UploadFile({ file, url, status, progress, error }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const urlRef = useRef(null);
|
||||
|
||||
|
@ -45,7 +45,7 @@ export default function UploadFile({ file, url, status, progress }) {
|
|||
<p>
|
||||
{status === "uploading" && (progress ? `Uploading ${Math.round(progress * 100)}%` : "Uploading...")}
|
||||
{status === "processing" && "Processing..."}
|
||||
{status === "error" && <span className="red-text">Error processing file.</span>}
|
||||
{status === "error" && <span className="red-text">{error || "Upload failed."}</span>}
|
||||
{status === "complete" && (
|
||||
<a href={url} className="url green-text" target="_blank" rel="noopener noreferrer">
|
||||
{url}
|
||||
|
@ -80,4 +80,5 @@ UploadFile.propTypes = {
|
|||
status: PropTypes.string.isRequired,
|
||||
url: PropTypes.string,
|
||||
progress: PropTypes.number,
|
||||
error: PropTypes.string,
|
||||
};
|
||||
|
|
17
yarn.lock
17
yarn.lock
|
@ -2910,7 +2910,7 @@ bytes@3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
|
||||
integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
|
||||
|
||||
bytes@3.1.0:
|
||||
bytes@3.1.0, bytes@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
||||
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
|
||||
|
@ -7224,6 +7224,11 @@ http-signature@~1.2.0:
|
|||
jsprim "^1.2.2"
|
||||
sshpk "^1.7.0"
|
||||
|
||||
http-status-codes@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-1.4.0.tgz#6e4c15d16ff3a9e2df03b89f3a55e1aae05fb477"
|
||||
integrity sha512-JrT3ua+WgH8zBD3HEJYbeEgnuQaAnUeRRko/YojPAJjGmIfGD3KPU/asLdsLwKjfxOmQe5nXMQ0pt/7MyapVbQ==
|
||||
|
||||
https-browserify@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
||||
|
@ -12365,6 +12370,14 @@ sisteransi@^1.0.4:
|
|||
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
|
||||
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
|
||||
|
||||
skynet-js@^0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/skynet-js/-/skynet-js-0.0.5.tgz#f2bd8a70c50263d461486f490b3b3d0856d1e0d0"
|
||||
integrity sha512-XJ2vWCe3PUsvKhebdgM3FGZxqkAtLBz6xQ3HSbwqNWJDgHtyKWwIBb6vE2GqSC684zBT1MfxlTPgxBkbpx8hpg==
|
||||
dependencies:
|
||||
axios "^0.19.2"
|
||||
url-parse "^1.4.7"
|
||||
|
||||
slash@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
|
||||
|
@ -13821,7 +13834,7 @@ url-parse-lax@^3.0.0:
|
|||
dependencies:
|
||||
prepend-http "^2.0.0"
|
||||
|
||||
url-parse@^1.1.8, url-parse@^1.4.3:
|
||||
url-parse@^1.1.8, url-parse@^1.4.3, url-parse@^1.4.7:
|
||||
version "1.4.7"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
|
||||
integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
|
||||
|
|
Reference in New Issue