From 67ece21a995b14829877b52690f4074f6d68fd74 Mon Sep 17 00:00:00 2001 From: luc Date: Wed, 3 May 2023 11:07:30 -0300 Subject: [PATCH 01/10] initial draft for the inclusion proof page --- pages/inclusion-proofs.tsx | 296 +++++++++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 pages/inclusion-proofs.tsx diff --git a/pages/inclusion-proofs.tsx b/pages/inclusion-proofs.tsx new file mode 100644 index 0000000..f685d7a --- /dev/null +++ b/pages/inclusion-proofs.tsx @@ -0,0 +1,296 @@ +import S from '@pages/index.module.scss'; + +import * as R from '@common/requests'; +import * as U from '@common/utilities'; +import * as React from 'react'; + +import * as C from '@common/constants'; +import Button from '@components/Button'; +import Input from '@components/Input'; +import LoaderSpinner from '@components/LoaderSpinner'; +import Navigation from '@components/Navigation'; +import Page from '@components/Page'; +import RetrievalCommands from '@components/RetrievalCommands'; +import SingleColumnLayout from '@components/SingleColumnLayout'; +import StatRow from '@components/StatRow'; +import { CodeBlock, H1, H2, H3, P } from '@components/Typography'; +export async function getServerSideProps(context) { + const viewer = await U.getViewerFromHeader(context.req.headers); + + return { + props: { viewer, api: process.env.NEXT_PUBLIC_ESTUARY_API, hostname: `https://${context.req.headers.host}` }, + }; +} + +function useWindowSize() { + const [size, setSize] = React.useState([0, 0]); + if (!process.browser) { + return size; + } + + React.useLayoutEffect(() => { + function updateSize() { + setSize([window.innerWidth, window.innerHeight]); + } + window.addEventListener('resize', updateSize); + updateSize(); + return () => window.removeEventListener('resize', updateSize); + }, []); + return size; +} + +const getURIWithParam = (baseUrl, params) => { + const Url = new URL(baseUrl); + const urlParams = new URLSearchParams(Url.search); + for (const key in params) { + if (params[key] !== undefined) { + urlParams.set(key, params[key]); + } + } + Url.search = urlParams.toString(); + return Url.toString(); +}; + +const onCheckCID = async (state, setState, host) => { + setState({ ...state, working: true, data: null }); + await U.delay(2000); + const response = await R.get(`/public/by-cid/${state.cid}`, host); + + if (response.error) { + return setState({ ...state, working: false, data: { error: response.error } }); + } + + if (history.pushState) { + let searchParams = new URLSearchParams(window.location.search); + searchParams.set('cid', state.cid); + let newurl = window.location.protocol + '//' + window.location.host + window.location.pathname + '?' + searchParams.toString(); + window.history.pushState({ path: newurl }, '', newurl); + } + + setTimeout(() => { + setState({ ...state, data: response && response.length ? response[0] : null, working: false }); + }); +}; + +function VerifyCIDPage(props: any) { + const [width, height] = useWindowSize(); + const [state, setState] = React.useState({ + cid: '', + data: null, + working: true, + }); + + React.useEffect(() => { + const load = async () => { + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); + + if (!params) { + return setState({ ...state, working: false }); + } + + let cid = params.cid ? params.cid : ''; + if (U.isEmpty(cid)) { + return setState({ ...state, working: false }); + } + + setState({ ...state, cid }); + }; + + load(); + }, [width]); + + React.useEffect(() => { + const timeoutId = setTimeout(() => { + if (U.isEmpty(state.cid)) { + return; + } + + onCheckCID(state, setState, props.api); + }, 1000); + + return () => clearTimeout(timeoutId); + }, [state.cid]); + + const description = 'Verify that your CID is stored in a storage deal on the Filecoin Network.'; + + let status = U.isEmpty(state.cid) ? 3 : 4; + if (state.data && state.data.content) { + status = 2; + } + + if (state.data && state.data.error) { + status = 5; + } + + if (state.working) { + status = 1; + } + + let statusElement = null; + if (status === 1) { + statusElement = ( +
+

Searching...

+ +
+ ); + } + + if (status === 2) { + statusElement = ( +
+

✅ This CID is verified on Estuary

+

Here is more information about this CID that is pinned by Estuary.

+
+ ); + } + + if (status === 3) { + statusElement = ( +
+

Enter a CID

+

If this Estuary Node pinned your CID and stored the data on Filecoin, you will be able to find it here.

+
+ ); + } + + if (status === 4) { + statusElement = ( +
+

This CID is not found

+

It might be pinned by a IPFS Node, you can use the dweb.link URL to check

+
+ ); + } + + if (status === 5) { + statusElement = ( +
+

Request Error

+

There was an error verifying this CID

+ + {state.data.error.code}: {state.data.error.details} + +
+ ); + } + + const cid = state.data && state.data.content ? state.data.content.cid : state.cid; + const estuaryRetrievalUrl = U.formatEstuaryRetrievalUrl(cid); + const dwebRetrievalUrl = U.formatDwebRetrievalUrl(cid); + + console.log(state); + + return ( + + + + +

Verify your Inclusion Proof

+

{description}

+ +
+ { + const nextState = { ...state, [e.target.name]: e.target.value }; + setState(nextState); + }} + value={state.cid} + readOnly={state.working} + name="cid" + onSubmit={() => onCheckCID(state, setState, props.api)} + /> + + {state.data && state.data.content ? ( + + {statusElement} + {state.data.content.cid} + + + {estuaryRetrievalUrl} + + + + + {dwebRetrievalUrl} + + + {state.data.content.id} + + {state.data.content.size} bytes ⇄ {U.bytesToSize(state.data.content.size)} + + + ) : U.isEmpty(state.cid) ? ( + statusElement + ) : ( + + {statusElement} + + + + {estuaryRetrievalUrl} + + + + + {dwebRetrievalUrl} + + + + )} + + {state.data && state.data && state.data.deals && state.data.deals.length > 0 ? ( + +
+

✅ This CID is sealed on Filecoin

+

+ Here are all of the providers that have guaranteed this data is accessible on Filecoin. The integrity of the underlying data is guaranteed by cryptographic proofs + for verifiability. +

+
+ {state.data.deals.map((d) => { + return ( +
+ + {d.miner}{' '} + + (view provider) + + + {U.toDate(d.sealedAt)} + + {d.dealId}{' '} + + (view receipt) + + + + + +
+ ); + })} +
+ ) : ( + +
+

This data is not on Filecoin

+

Check back later to see if we have successfully made deals for this data.

+
+
+ )} +
+
+
+ ); +} + +export default VerifyCIDPage; From f7b973d746234ae5e0d986b7a2a410ec78b436fd Mon Sep 17 00:00:00 2001 From: luc Date: Wed, 3 May 2023 11:07:55 -0300 Subject: [PATCH 02/10] added navigation to inclusion proof page --- components/Navigation.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/Navigation.tsx b/components/Navigation.tsx index c367277..4588da8 100644 --- a/components/Navigation.tsx +++ b/components/Navigation.tsx @@ -42,6 +42,10 @@ const Navigation = (props: any) => { Verify + + Proofs + + Documentation From 3cf6a5444d898d6c13cdaa4bf17df9412e6de007 Mon Sep 17 00:00:00 2001 From: luc Date: Wed, 17 May 2023 11:25:16 -0300 Subject: [PATCH 03/10] added fetch user threshold data --- pages/home.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pages/home.tsx b/pages/home.tsx index 904f9ed..16a3a8f 100644 --- a/pages/home.tsx +++ b/pages/home.tsx @@ -56,6 +56,7 @@ function HomePage(props: any) { const [state, setState] = React.useState({ files: null, stats: null, + threshold: null, offset: 0, limit: INCREMENT, }); @@ -64,9 +65,10 @@ function HomePage(props: any) { const run = async () => { const files = await R.get(`/content/contents?offset=${state.offset}&limit=${state.limit}`, props.api); const stats = await R.get('/user/stats', props.api); + const threshold = await R.get('/user/utilization', props.api); if (files && !files.error) { - setState({ ...state, files, stats }); + setState({ ...state, files, stats, threshold }); } }; From 0925fd6363f9fb9e2ea276a4671ca198ab4c3a4b Mon Sep 17 00:00:00 2001 From: luc Date: Wed, 17 May 2023 11:26:39 -0300 Subject: [PATCH 04/10] added Alert Panels for soft / hard limit reached --- pages/home.tsx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pages/home.tsx b/pages/home.tsx index 16a3a8f..f1edf54 100644 --- a/pages/home.tsx +++ b/pages/home.tsx @@ -15,6 +15,7 @@ import PageHeader from '@components/PageHeader'; import { H2, P } from '@components/Typography'; import FilesTable from '@root/components/FilesTable'; +import AlertPanel from '@components/AlertPanel'; const INCREMENT = 1000; @@ -109,6 +110,25 @@ function HomePage(props: any) { )} + { state.stats && state.threshold ? ( +
+ { state.stats.totalSize >= state.threshold.soft_limit && state.stats.totalSize < state.threshold.hard_limit ? ( + + You are currently at { U.formatNumber((state.stats.totalSize / state.threshold.hard_limit) * 100) }% of + storage utilization. Once your hard limit threshold is reached + ({ U.bytesToSize(state.threshold.hard_limit) }), you will no longer be able to upload files. Please get + in touch with the Estuary Team if you require additional storage. + + ) : null } + { state.stats.totalSize >= state.threshold.hard_limit ? ( + + You have reached your hard limit threshold { U.bytesToSize(state.threshold.hard_limit) }. Please get in touch + with the Estuary Team if you require additional storage. + + ) : null } +
+ ) : null } + {state.stats ? (
From 6d985f62da9e8f63978e99c7467c9bac21b204eb Mon Sep 17 00:00:00 2001 From: luc Date: Wed, 17 May 2023 11:44:33 -0300 Subject: [PATCH 05/10] added fetch users threshold function --- common/utilities.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/common/utilities.ts b/common/utilities.ts index 8138404..85d0504 100644 --- a/common/utilities.ts +++ b/common/utilities.ts @@ -172,6 +172,32 @@ export const getAuthAddress = async (headers) => { } } +export const getUserStorageThreshold = async (headers) => { + try { + const token = Cookies.get(headers, C.auth); + const url = `${C.api.host}/user/utilization`; + const response = await fetch(url, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + const json = await response.json(); + + if (!json) { + return null; + } + + if (json.error) { + return null; + } + + return json; + } catch (e) { + return null; + } +} + export const getViewerFromHeader = async (headers) => { try { const token = Cookies.get(headers, C.auth); From d2ec2a010c60f98310180ab15c9b9ec5a26c7a2c Mon Sep 17 00:00:00 2001 From: luc Date: Wed, 17 May 2023 11:45:13 -0300 Subject: [PATCH 06/10] setting page now displays users soft / hard threshold limits --- pages/settings.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pages/settings.tsx b/pages/settings.tsx index 9390e27..3bfc8f7 100644 --- a/pages/settings.tsx +++ b/pages/settings.tsx @@ -20,6 +20,7 @@ import Modal from '@components/Modal'; export async function getServerSideProps(context) { const viewer = await U.getViewerFromHeader(context.req.headers); + const threshold = await U.getUserStorageThreshold(context.req.headers) const wallet = await U.getAuthAddress(context.req.headers); const host = context.req.headers.host; const protocol = host.split(':')[0] === 'localhost' ? 'http' : 'https'; @@ -34,6 +35,7 @@ export async function getServerSideProps(context) { } viewer.wallet = wallet; + viewer.threshold = threshold; return { props: { host, protocol, viewer, api: process.env.NEXT_PUBLIC_ESTUARY_API, hostname: `https://${context.req.headers.host}` }, @@ -298,6 +300,14 @@ function SettingsPage(props: any) { + +

User soft limit threshold (%)

+ + +

User hard limit threshold

+ + From 18e24dbb1f90d61a28a2aba8d9a8fbc577c417c4 Mon Sep 17 00:00:00 2001 From: luc Date: Wed, 17 May 2023 11:53:30 -0300 Subject: [PATCH 07/10] Revert "added navigation to inclusion proof page" This reverts commit f7b973d746234ae5e0d986b7a2a410ec78b436fd. --- components/Navigation.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/components/Navigation.tsx b/components/Navigation.tsx index 4588da8..c367277 100644 --- a/components/Navigation.tsx +++ b/components/Navigation.tsx @@ -42,10 +42,6 @@ const Navigation = (props: any) => { Verify - - Proofs - - Documentation From 270bfcee6f4ac71d63967bd1c75e576a21f0c22e Mon Sep 17 00:00:00 2001 From: luc Date: Wed, 17 May 2023 11:56:09 -0300 Subject: [PATCH 08/10] Revert "initial draft for the inclusion proof page" This reverts commit 67ece21a995b14829877b52690f4074f6d68fd74. --- pages/inclusion-proofs.tsx | 296 ------------------------------------- 1 file changed, 296 deletions(-) delete mode 100644 pages/inclusion-proofs.tsx diff --git a/pages/inclusion-proofs.tsx b/pages/inclusion-proofs.tsx deleted file mode 100644 index f685d7a..0000000 --- a/pages/inclusion-proofs.tsx +++ /dev/null @@ -1,296 +0,0 @@ -import S from '@pages/index.module.scss'; - -import * as R from '@common/requests'; -import * as U from '@common/utilities'; -import * as React from 'react'; - -import * as C from '@common/constants'; -import Button from '@components/Button'; -import Input from '@components/Input'; -import LoaderSpinner from '@components/LoaderSpinner'; -import Navigation from '@components/Navigation'; -import Page from '@components/Page'; -import RetrievalCommands from '@components/RetrievalCommands'; -import SingleColumnLayout from '@components/SingleColumnLayout'; -import StatRow from '@components/StatRow'; -import { CodeBlock, H1, H2, H3, P } from '@components/Typography'; -export async function getServerSideProps(context) { - const viewer = await U.getViewerFromHeader(context.req.headers); - - return { - props: { viewer, api: process.env.NEXT_PUBLIC_ESTUARY_API, hostname: `https://${context.req.headers.host}` }, - }; -} - -function useWindowSize() { - const [size, setSize] = React.useState([0, 0]); - if (!process.browser) { - return size; - } - - React.useLayoutEffect(() => { - function updateSize() { - setSize([window.innerWidth, window.innerHeight]); - } - window.addEventListener('resize', updateSize); - updateSize(); - return () => window.removeEventListener('resize', updateSize); - }, []); - return size; -} - -const getURIWithParam = (baseUrl, params) => { - const Url = new URL(baseUrl); - const urlParams = new URLSearchParams(Url.search); - for (const key in params) { - if (params[key] !== undefined) { - urlParams.set(key, params[key]); - } - } - Url.search = urlParams.toString(); - return Url.toString(); -}; - -const onCheckCID = async (state, setState, host) => { - setState({ ...state, working: true, data: null }); - await U.delay(2000); - const response = await R.get(`/public/by-cid/${state.cid}`, host); - - if (response.error) { - return setState({ ...state, working: false, data: { error: response.error } }); - } - - if (history.pushState) { - let searchParams = new URLSearchParams(window.location.search); - searchParams.set('cid', state.cid); - let newurl = window.location.protocol + '//' + window.location.host + window.location.pathname + '?' + searchParams.toString(); - window.history.pushState({ path: newurl }, '', newurl); - } - - setTimeout(() => { - setState({ ...state, data: response && response.length ? response[0] : null, working: false }); - }); -}; - -function VerifyCIDPage(props: any) { - const [width, height] = useWindowSize(); - const [state, setState] = React.useState({ - cid: '', - data: null, - working: true, - }); - - React.useEffect(() => { - const load = async () => { - const urlSearchParams = new URLSearchParams(window.location.search); - const params = Object.fromEntries(urlSearchParams.entries()); - - if (!params) { - return setState({ ...state, working: false }); - } - - let cid = params.cid ? params.cid : ''; - if (U.isEmpty(cid)) { - return setState({ ...state, working: false }); - } - - setState({ ...state, cid }); - }; - - load(); - }, [width]); - - React.useEffect(() => { - const timeoutId = setTimeout(() => { - if (U.isEmpty(state.cid)) { - return; - } - - onCheckCID(state, setState, props.api); - }, 1000); - - return () => clearTimeout(timeoutId); - }, [state.cid]); - - const description = 'Verify that your CID is stored in a storage deal on the Filecoin Network.'; - - let status = U.isEmpty(state.cid) ? 3 : 4; - if (state.data && state.data.content) { - status = 2; - } - - if (state.data && state.data.error) { - status = 5; - } - - if (state.working) { - status = 1; - } - - let statusElement = null; - if (status === 1) { - statusElement = ( -
-

Searching...

- -
- ); - } - - if (status === 2) { - statusElement = ( -
-

✅ This CID is verified on Estuary

-

Here is more information about this CID that is pinned by Estuary.

-
- ); - } - - if (status === 3) { - statusElement = ( -
-

Enter a CID

-

If this Estuary Node pinned your CID and stored the data on Filecoin, you will be able to find it here.

-
- ); - } - - if (status === 4) { - statusElement = ( -
-

This CID is not found

-

It might be pinned by a IPFS Node, you can use the dweb.link URL to check

-
- ); - } - - if (status === 5) { - statusElement = ( -
-

Request Error

-

There was an error verifying this CID

- - {state.data.error.code}: {state.data.error.details} - -
- ); - } - - const cid = state.data && state.data.content ? state.data.content.cid : state.cid; - const estuaryRetrievalUrl = U.formatEstuaryRetrievalUrl(cid); - const dwebRetrievalUrl = U.formatDwebRetrievalUrl(cid); - - console.log(state); - - return ( - - - - -

Verify your Inclusion Proof

-

{description}

- -
- { - const nextState = { ...state, [e.target.name]: e.target.value }; - setState(nextState); - }} - value={state.cid} - readOnly={state.working} - name="cid" - onSubmit={() => onCheckCID(state, setState, props.api)} - /> - - {state.data && state.data.content ? ( - - {statusElement} - {state.data.content.cid} - - - {estuaryRetrievalUrl} - - - - - {dwebRetrievalUrl} - - - {state.data.content.id} - - {state.data.content.size} bytes ⇄ {U.bytesToSize(state.data.content.size)} - - - ) : U.isEmpty(state.cid) ? ( - statusElement - ) : ( - - {statusElement} - - - - {estuaryRetrievalUrl} - - - - - {dwebRetrievalUrl} - - - - )} - - {state.data && state.data && state.data.deals && state.data.deals.length > 0 ? ( - -
-

✅ This CID is sealed on Filecoin

-

- Here are all of the providers that have guaranteed this data is accessible on Filecoin. The integrity of the underlying data is guaranteed by cryptographic proofs - for verifiability. -

-
- {state.data.deals.map((d) => { - return ( -
- - {d.miner}{' '} - - (view provider) - - - {U.toDate(d.sealedAt)} - - {d.dealId}{' '} - - (view receipt) - - - - - -
- ); - })} -
- ) : ( - -
-

This data is not on Filecoin

-

Check back later to see if we have successfully made deals for this data.

-
-
- )} -
-
-
- ); -} - -export default VerifyCIDPage; From e37e8eec043f3de58bb07d7db67d802e31857aba Mon Sep 17 00:00:00 2001 From: luc Date: Tue, 23 May 2023 10:04:12 -0300 Subject: [PATCH 09/10] appended _bytes to soft / hard limit fields for usability / clarification --- pages/home.tsx | 10 +++++----- pages/settings.tsx | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pages/home.tsx b/pages/home.tsx index f1edf54..1b17190 100644 --- a/pages/home.tsx +++ b/pages/home.tsx @@ -112,17 +112,17 @@ function HomePage(props: any) { { state.stats && state.threshold ? (
- { state.stats.totalSize >= state.threshold.soft_limit && state.stats.totalSize < state.threshold.hard_limit ? ( + { state.stats.totalSize >= state.threshold.soft_limit_bytes && state.stats.totalSize < state.threshold.hard_limit_bytes ? ( - You are currently at { U.formatNumber((state.stats.totalSize / state.threshold.hard_limit) * 100) }% of + You are currently at { U.formatNumber((state.stats.totalSize / state.threshold.hard_limit_bytes) * 100) }% of storage utilization. Once your hard limit threshold is reached - ({ U.bytesToSize(state.threshold.hard_limit) }), you will no longer be able to upload files. Please get + ({ U.bytesToSize(state.threshold.hard_limit_bytes) }), you will no longer be able to upload files. Please get in touch with the Estuary Team if you require additional storage. ) : null } - { state.stats.totalSize >= state.threshold.hard_limit ? ( + { state.stats.totalSize >= state.threshold.hard_limit_bytes ? ( - You have reached your hard limit threshold { U.bytesToSize(state.threshold.hard_limit) }. Please get in touch + You have reached your hard limit threshold { U.bytesToSize(state.threshold.hard_limit_bytes) }. Please get in touch with the Estuary Team if you require additional storage. ) : null } diff --git a/pages/settings.tsx b/pages/settings.tsx index 3bfc8f7..4291af6 100644 --- a/pages/settings.tsx +++ b/pages/settings.tsx @@ -303,10 +303,10 @@ function SettingsPage(props: any) {

User soft limit threshold (%)

+ value={ U.formatNumber((viewer.threshold.soft_limit_bytes / viewer.threshold.hard_limit_bytes) * 100) } />

User hard limit threshold

- + From 30f83455e6f027f4ee9ad8dabbee907107aa81b0 Mon Sep 17 00:00:00 2001 From: luc Date: Tue, 23 May 2023 10:09:20 -0300 Subject: [PATCH 10/10] added our slack channel in max threshold reached alert for clarity on where to find us --- pages/home.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pages/home.tsx b/pages/home.tsx index 1b17190..b287b15 100644 --- a/pages/home.tsx +++ b/pages/home.tsx @@ -123,7 +123,8 @@ function HomePage(props: any) { { state.stats.totalSize >= state.threshold.hard_limit_bytes ? ( You have reached your hard limit threshold { U.bytesToSize(state.threshold.hard_limit_bytes) }. Please get in touch - with the Estuary Team if you require additional storage. + with the Estuary Team if you require additional storage. You can find us in the Filecoin slack under the + #ecosystem-dev channel. ) : null }