diff --git a/package-lock.json b/package-lock.json index 2034476..cbc4ec7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "classnames": "^2.3.2", "comlink": "^4.4.1", "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.13", "fast-deep-equal": "^3.1.3", "fuse.js": "^6.6.2", "level": "^8.0.0", @@ -5970,6 +5971,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -18195,6 +18201,11 @@ "is-data-view": "^1.0.1" } }, + "dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + }, "debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", diff --git a/package.json b/package.json index c2bb4a6..a9b7833 100755 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "classnames": "^2.3.2", "comlink": "^4.4.1", "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.13", "fast-deep-equal": "^3.1.3", "fuse.js": "^6.6.2", "level": "^8.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 36b5574..62af3bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ dependencies: copy-to-clipboard: specifier: ^3.3.3 version: 3.3.3 + dayjs: + specifier: ^1.11.13 + version: 1.11.13 fast-deep-equal: specifier: ^3.1.3 version: 3.1.3 @@ -3878,6 +3881,10 @@ packages: is-data-view: 1.0.1 dev: true + /dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + dev: false + /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: diff --git a/src/assets/img/dot-menu.svg b/src/assets/img/dot-menu.svg new file mode 100644 index 0000000..ca8778e --- /dev/null +++ b/src/assets/img/dot-menu.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/assets/img/notarize.png b/src/assets/img/notarize.png new file mode 100644 index 0000000..eae8f5b Binary files /dev/null and b/src/assets/img/notarize.png differ diff --git a/src/entries/Background/db.ts b/src/entries/Background/db.ts index 217287b..8c753a8 100644 --- a/src/entries/Background/db.ts +++ b/src/entries/Background/db.ts @@ -1,6 +1,6 @@ import { Level } from 'level'; -import type { RequestHistory } from './rpc'; import { PluginConfig, PluginMetadata, sha256, urlify } from '../../utils/misc'; +import { RequestHistory, RequestProgress } from './rpc'; import mutex from './mutex'; import { minimatch } from 'minimatch'; const charwise = require('charwise'); @@ -112,6 +112,24 @@ export async function setNotaryRequestError( return newReq; } +export async function setNotaryRequestProgress( + id: string, + progress: RequestProgress, +): Promise { + const existing = await historyDb.get(id); + + if (!existing) return null; + + const newReq: RequestHistory = { + ...existing, + progress, + }; + + await historyDb.put(id, newReq); + + return newReq; +} + export async function setNotaryRequestVerification( id: string, verification: { sent: string; recv: string }, diff --git a/src/entries/Background/rpc.ts b/src/entries/Background/rpc.ts index 4c4c816..a8b8e74 100644 --- a/src/entries/Background/rpc.ts +++ b/src/entries/Background/rpc.ts @@ -28,6 +28,7 @@ import { setDefaultPluginsInstalled, setLocalStorage, setSessionStorage, + setNotaryRequestProgress, } from './db'; import { addOnePlugin, removeOnePlugin } from '../../reducers/plugins'; import { @@ -76,6 +77,7 @@ export enum BackgroundActiontype { prove_request_start = 'prove_request_start', process_prove_request = 'process_prove_request', finish_prove_request = 'finish_prove_request', + update_request_progress = 'update_request_progress', verify_prove_request = 'verify_prove_request', verify_proof = 'verify_proof', delete_prove_request = 'delete_prove_request', @@ -158,6 +160,32 @@ export type RequestLog = { responseHeaders?: browser.WebRequest.HttpHeaders; }; +export enum RequestProgress { + CreatingProver, + GettingSession, + SettingUpProver, + SendingRequest, + ReadingTranscript, + FinalizingOutputs, +} + +export function progressText(progress: RequestProgress): string { + switch (progress) { + case RequestProgress.CreatingProver: + return 'Creating prover...'; + case RequestProgress.GettingSession: + return 'Getting session url from notary...'; + case RequestProgress.SettingUpProver: + return 'Setting up prover mpc backend...'; + case RequestProgress.SendingRequest: + return 'Sending request...'; + case RequestProgress.ReadingTranscript: + return 'Reading request transcript...'; + case RequestProgress.FinalizingOutputs: + return 'Finalizing notarization outputs...'; + } +} + export type RequestHistory = { id: string; url: string; @@ -169,6 +197,7 @@ export type RequestHistory = { notaryUrl: string; websocketProxyUrl: string; status: '' | 'pending' | 'success' | 'error'; + progress?: RequestProgress; error?: any; proof?: { session: any; substrings: any }; requestBody?: any; @@ -197,6 +226,8 @@ export const initRPC = () => { return handleGetProveRequests(request, sendResponse); case BackgroundActiontype.finish_prove_request: return handleFinishProveRequest(request, sendResponse); + case BackgroundActiontype.update_request_progress: + return handleUpdateRequestProgress(request, sendResponse); case BackgroundActiontype.delete_prove_request: return removeNotaryRequest(request.data); case BackgroundActiontype.retry_prove_request: @@ -393,6 +424,19 @@ async function handleFinishProveRequest( return sendResponse(); } +async function handleUpdateRequestProgress( + request: BackgroundAction, + sendResponse: (data?: any) => void, +) { + const { id, progress } = request.data; + + const newReq = await setNotaryRequestProgress(id, progress); + if (!newReq) return; + await pushToRedux(addRequestHistory(await getNotaryRequest(id))); + + return sendResponse(); +} + async function handleRetryProveReqest( request: BackgroundAction, sendResponse: (data?: any) => void, diff --git a/src/entries/Offscreen/rpc.ts b/src/entries/Offscreen/rpc.ts index 36ddb80..1dfa173 100644 --- a/src/entries/Offscreen/rpc.ts +++ b/src/entries/Offscreen/rpc.ts @@ -1,5 +1,9 @@ import browser from 'webextension-polyfill'; -import { BackgroundActiontype } from '../Background/rpc'; +import { + BackgroundActiontype, + progressText, + RequestProgress, +} from '../Background/rpc'; import { Method } from 'tlsn-wasm'; import { NotaryServer, @@ -8,7 +12,7 @@ import { Transcript, Verifier as TVerifier, } from 'tlsn-js'; -import { urlify } from '../../utils/misc'; +import { devlog, urlify } from '../../utils/misc'; import * as Comlink from 'comlink'; import { PresentationJSON as PresentationJSONa7 } from 'tlsn-js/build/types'; import { subtractRanges } from './utils'; @@ -52,13 +56,13 @@ export const onNotarizationRequest = async (request: any) => { proof, }, }); - } catch (error) { + } catch (error: any) { console.error(error); browser.runtime.sendMessage({ type: BackgroundActiontype.finish_prove_request, data: { id, - error, + error: error?.message || 'Unknown error', }, }); @@ -66,7 +70,7 @@ export const onNotarizationRequest = async (request: any) => { type: OffscreenActionTypes.notarization_response, data: { id, - error, + error: error?.message || 'Unknown error', }, }); } @@ -80,6 +84,7 @@ export const onCreateProverRequest = async (request: any) => { provers[id] = prover; + updateRequestProgress(id, RequestProgress.ReadingTranscript); browser.runtime.sendMessage({ type: OffscreenActionTypes.create_prover_response, data: { @@ -87,13 +92,13 @@ export const onCreateProverRequest = async (request: any) => { transcript: await prover.transcript(), }, }); - } catch (error) { + } catch (error: any) { console.error(error); browser.runtime.sendMessage({ type: OffscreenActionTypes.create_prover_response, data: { id, - error, + error: error?.message || 'Unknown error', }, }); } @@ -106,6 +111,7 @@ export const onCreatePresentationRequest = async (request: any) => { try { if (!prover) throw new Error(`Cannot find prover ${id}.`); + updateRequestProgress(id, RequestProgress.FinalizingOutputs); const notarizationOutputs = await prover.notarize(commit); const presentation = (await new Presentation({ @@ -126,13 +132,13 @@ export const onCreatePresentationRequest = async (request: any) => { }); delete provers[id]; - } catch (error) { + } catch (error: any) { console.error(error); browser.runtime.sendMessage({ type: BackgroundActiontype.finish_prove_request, data: { id, - error, + error: error?.message || 'Unknown error', }, }); } @@ -151,13 +157,13 @@ export const onProcessProveRequest = async (request: any) => { proof: proof, }, }); - } catch (error) { + } catch (error: any) { console.error(error); browser.runtime.sendMessage({ type: BackgroundActiontype.finish_prove_request, data: { id, - error, + error: error?.message || 'Unknown error', }, }); } @@ -367,6 +373,8 @@ async function createProof(options: { const hostname = urlify(url)?.hostname || ''; const notary = NotaryServer.from(notaryUrl); + + updateRequestProgress(id, RequestProgress.CreatingProver); const prover: TProver = await new Prover({ id, serverDns: hostname, @@ -374,8 +382,13 @@ async function createProof(options: { maxRecvData, }); - await prover.setup(await notary.sessionUrl(maxSentData, maxRecvData)); + updateRequestProgress(id, RequestProgress.GettingSession); + const sessionUrl = await notary.sessionUrl(maxSentData, maxRecvData); + updateRequestProgress(id, RequestProgress.SettingUpProver); + await prover.setup(sessionUrl); + + updateRequestProgress(id, RequestProgress.SendingRequest); await prover.sendRequest(websocketProxyUrl + `?token=${hostname}`, { url, method, @@ -383,6 +396,7 @@ async function createProof(options: { body, }); + updateRequestProgress(id, RequestProgress.ReadingTranscript); const transcript = await prover.transcript(); const commit = { @@ -396,6 +410,7 @@ async function createProof(options: { ), }; + updateRequestProgress(id, RequestProgress.FinalizingOutputs); const notarizationOutputs = await prover.notarize(commit); const presentation = (await new Presentation({ @@ -436,6 +451,8 @@ async function createProver(options: { const hostname = urlify(url)?.hostname || ''; const notary = NotaryServer.from(notaryUrl); + + updateRequestProgress(id, RequestProgress.CreatingProver); const prover: TProver = await new Prover({ id, serverDns: hostname, @@ -443,8 +460,13 @@ async function createProver(options: { maxRecvData, }); - await prover.setup(await notary.sessionUrl(maxSentData, maxRecvData)); + updateRequestProgress(id, RequestProgress.GettingSession); + const sessionUrl = await notary.sessionUrl(maxSentData, maxRecvData); + updateRequestProgress(id, RequestProgress.SettingUpProver); + await prover.setup(sessionUrl); + + updateRequestProgress(id, RequestProgress.SendingRequest); await prover.sendRequest(websocketProxyUrl + `?token=${hostname}`, { url, method, @@ -482,3 +504,14 @@ async function verifyProof( return result; } + +function updateRequestProgress(id: string, progress: RequestProgress) { + devlog(`Request ${id}: ${progressText(progress)}`); + browser.runtime.sendMessage({ + type: BackgroundActiontype.update_request_progress, + data: { + id, + progress: progress, + }, + }); +} diff --git a/src/entries/Popup/index.tsx b/src/entries/Popup/index.tsx index e9bb3ff..d10ea61 100644 --- a/src/entries/Popup/index.tsx +++ b/src/entries/Popup/index.tsx @@ -4,6 +4,11 @@ import { HashRouter } from 'react-router-dom'; import Popup from './Popup'; import './index.scss'; import { Provider } from 'react-redux'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; +import localizedFormat from 'dayjs/plugin/localizedFormat'; +dayjs.extend(relativeTime); +dayjs.extend(localizedFormat); import store from '../../utils/store'; const container = document.getElementById('app-container'); diff --git a/src/entries/SidePanel/SidePanel.tsx b/src/entries/SidePanel/SidePanel.tsx index 9c16b59..ebba5dc 100644 --- a/src/entries/SidePanel/SidePanel.tsx +++ b/src/entries/SidePanel/SidePanel.tsx @@ -13,7 +13,7 @@ import logo from '../../assets/img/icon-128.png'; import classNames from 'classnames'; import Icon from '../../components/Icon'; import { useRequestHistory } from '../../reducers/history'; -import { BackgroundActiontype } from '../Background/rpc'; +import { BackgroundActiontype, progressText } from '../Background/rpc'; import { getPluginByHash, getPluginConfigByHash } from '../Background/db'; import { SidePanelActionTypes } from './types'; import { fetchP2PState, useClientId } from '../../reducers/p2p'; @@ -279,7 +279,6 @@ function StepContent( let btnContent = null; - console.log('prover', prover, p2p); if (prover && p2p) { btnContent = ( ); } else { diff --git a/src/pages/History/index.tsx b/src/pages/History/index.tsx index c5a24d8..5e6e0d7 100644 --- a/src/pages/History/index.tsx +++ b/src/pages/History/index.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, useState, useCallback, useEffect } from 'react'; +import React, { ReactElement, useState, useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { useNavigate } from 'react-router'; import { @@ -7,24 +7,24 @@ import { deleteRequestHistory, } from '../../reducers/history'; import Icon from '../../components/Icon'; +import NotarizeIcon from '../../assets/img/notarize.png'; import { getNotaryApi, getProxyApi } from '../../utils/storage'; -import { urlify, download, upload } from '../../utils/misc'; -import { BackgroundActiontype } from '../../entries/Background/rpc'; +import { urlify } from '../../utils/misc'; +import { + BackgroundActiontype, + progressText, +} from '../../entries/Background/rpc'; import Modal, { ModalContent } from '../../components/Modal/Modal'; import classNames from 'classnames'; -import copy from 'copy-to-clipboard'; -import { EXPLORER_API } from '../../utils/constants'; -import { - getNotaryRequest, - setNotaryRequestCid, -} from '../../entries/Background/db'; +import dayjs from 'dayjs'; +import RequestMenu from './request-menu'; const charwise = require('charwise'); export default function History(): ReactElement { const history = useHistoryOrder(); return ( -
+
{history .map((id) => { return ; @@ -43,42 +43,11 @@ export function OneRequestHistory(props: { const dispatch = useDispatch(); const request = useRequestHistory(props.requestId); const [showingError, showError] = useState(false); - const [uploadError, setUploadError] = useState(''); - const [showingShareConfirmation, setShowingShareConfirmation] = - useState(false); - const [cid, setCid] = useState<{ [key: string]: string }>({}); - const [uploading, setUploading] = useState(false); + const [showingMenu, showMenu] = useState(false); const navigate = useNavigate(); const { status } = request || {}; const requestUrl = urlify(request?.url || ''); - useEffect(() => { - const fetchData = async () => { - try { - const request = await getNotaryRequest(props.requestId); - if (request && request.cid) { - setCid({ [props.requestId]: request.cid }); - } - } catch (e) { - console.error('Error fetching data', e); - } - }; - fetchData(); - }, []); - - const onRetry = useCallback(async () => { - const notaryUrl = await getNotaryApi(); - const websocketProxyUrl = await getProxyApi(); - chrome.runtime.sendMessage({ - type: BackgroundActiontype.retry_prove_request, - data: { - id: props.requestId, - notaryUrl, - websocketProxyUrl, - }, - }); - }, [props.requestId]); - const onView = useCallback(() => { chrome.runtime.sendMessage({ type: BackgroundActiontype.verify_prove_request, @@ -87,151 +56,95 @@ export function OneRequestHistory(props: { navigate('/verify/' + request?.id); }, [request]); - const onDelete = useCallback(async () => { - dispatch(deleteRequestHistory(props.requestId)); - }, [props.requestId]); - const onShowError = useCallback(async () => { showError(true); }, [request?.error, showError]); const closeAllModal = useCallback(() => { - setShowingShareConfirmation(false); showError(false); - }, [setShowingShareConfirmation, showError]); + }, [showError]); - const handleUpload = useCallback(async () => { - setUploading(true); - try { - const data = await upload( - `${request?.id}.json`, - JSON.stringify(request?.proof), - ); - setCid((prevCid) => ({ ...prevCid, [props.requestId]: data })); - await setNotaryRequestCid(props.requestId, data); - } catch (e: any) { - setUploadError(e.message); - } finally { - setUploading(false); - } - }, [props.requestId, request, cid]); + const day = dayjs(charwise.decode(props.requestId, 'hex')); return (
{ + if (status === 'success') onView(); + if (status === 'error') onShowError(); + }} > - -
-
-
- {request?.method} -
-
- {requestUrl?.pathname} -
+
+ +
+
+
+ Notarize request + + {requestUrl?.hostname} +
-
-
Time:
-
- {new Date(charwise.decode(props.requestId, 'hex')).toISOString()} -
-
-
-
Host:
-
{requestUrl?.host}
-
-
-
Notary API:
-
{request?.notaryUrl}
-
-
-
TLS Proxy API:
-
- {request?.websocketProxyUrl} -
+
+ {status === 'success' && 'Success'} + {status === 'error' && 'Error'} + {status === 'pending' && ( +
+ + + {request?.progress + ? `(${( + ((request.progress + 1) / 6.06) * + 100 + ).toFixed()}%) ${progressText(request.progress)}` + : 'Pending...'} + +
+ )}
-
- {status === 'success' && ( - <> -
); - function RetryButton(p: { hidden?: boolean }): ReactElement { - if (p.hidden) return <>; - return ( - - ); - } - - function ErrorButton(p: { hidden?: boolean }): ReactElement { - if (p.hidden) return <>; - return ( - - ); - } - function ErrorModal(): ReactElement { const msg = typeof request?.error === 'string' && request?.error; return !showingError ? ( @@ -253,99 +166,4 @@ export function OneRequestHistory(props: { ); } - - function ShareConfirmationModal(): ReactElement { - return !showingShareConfirmation ? ( - <> - ) : ( - - - {!cid[props.requestId] ? ( -

- {uploadError || - 'This will make your proof publicly accessible by anyone with the CID'} -

- ) : ( - e.target.select()} - /> - )} -
-
- {!cid[props.requestId] ? ( - <> - {!uploadError && ( - - )} - - - ) : ( - <> - - - - )} -
-
- ); - } -} - -function ActionButton(props: { - onClick: () => void; - fa: string; - ctaText: string; - className?: string; - hidden?: boolean; -}): ReactElement { - if (props.hidden) return <>; - - return ( - - ); } diff --git a/src/pages/History/request-menu.tsx b/src/pages/History/request-menu.tsx new file mode 100644 index 0000000..82645d4 --- /dev/null +++ b/src/pages/History/request-menu.tsx @@ -0,0 +1,256 @@ +import React, { + MouseEventHandler, + ReactElement, + ReactNode, + useCallback, + useEffect, + useState, +} from 'react'; +import classNames from 'classnames'; +import Icon from '../../components/Icon'; +import { + addRequestCid, + deleteRequestHistory, + useRequestHistory, +} from '../../reducers/history'; +import { download, upload } from '../../utils/misc'; +import Modal, { ModalContent } from '../../components/Modal/Modal'; +import { EXPLORER_API } from '../../utils/constants'; +import copy from 'copy-to-clipboard'; +import { setNotaryRequestCid } from '../../entries/Background/db'; +import { useDispatch } from 'react-redux'; +import { getNotaryApi, getProxyApi } from '../../utils/storage'; +import { BackgroundActiontype } from '../../entries/Background/rpc'; + +export default function RequestMenu({ + requestId, + showMenu, +}: { + showMenu: (opened: boolean) => void; + requestId: string; +}): ReactElement { + const dispatch = useDispatch(); + const request = useRequestHistory(requestId); + const [showingShareConfirmation, setShowingShareConfirmation] = + useState(false); + + const onRetry = useCallback(async () => { + const notaryUrl = await getNotaryApi(); + const websocketProxyUrl = await getProxyApi(); + chrome.runtime.sendMessage({ + type: BackgroundActiontype.retry_prove_request, + data: { + id: requestId, + notaryUrl, + websocketProxyUrl, + }, + }); + }, [requestId]); + + const onDelete = useCallback(async () => { + dispatch(deleteRequestHistory(requestId)); + }, [requestId]); + + if (!request) return <>; + + const { status } = request; + + return ( + <> + {showingShareConfirmation && ( + + )} +
{ + e.stopPropagation(); + showMenu(false); + }} + /> +
+
+ {status === 'success' && ( + <> + { + e.stopPropagation(); + showMenu(false); + download(`${request.id}.json`, JSON.stringify(request.proof)); + }} + > + Download + + { + e.stopPropagation(); + setShowingShareConfirmation(true); + }} + > + Share + + + )} + {status === 'error' && ( + { + e.stopPropagation(); + onRetry(); + showMenu(false); + }} + > + Retry + + )} + { + e.stopPropagation(); + onDelete(); + showMenu(false); + }} + > + Delete + +
+
+ + ); +} + +function RequestMenuRow(props: { + fa: string; + children?: ReactNode; + onClick?: MouseEventHandler; + className?: string; +}): ReactElement { + return ( +
+ + {props.children} +
+ ); +} + +function ShareConfirmationModal({ + setShowingShareConfirmation, + requestId, + showMenu, +}: { + showMenu: (opened: boolean) => void; + setShowingShareConfirmation: (showing: boolean) => void; + requestId: string; +}): ReactElement { + const dispatch = useDispatch(); + const request = useRequestHistory(requestId); + const [uploadError, setUploadError] = useState(''); + const [uploading, setUploading] = useState(false); + + const handleUpload = useCallback(async () => { + setUploading(true); + try { + const data = await upload( + `${request?.id}.json`, + JSON.stringify(request?.proof), + ); + await setNotaryRequestCid(requestId, data); + dispatch(addRequestCid(requestId, data)); + } catch (e: any) { + setUploadError(e.message); + } finally { + setUploading(false); + } + }, [requestId, request, request?.cid]); + + const onClose = useCallback(() => { + setShowingShareConfirmation(false); + showMenu(false); + }, [showMenu]); + + return !request ? ( + <> + ) : ( + { + e.stopPropagation(); + onClose(); + }} + > + + {!request.cid ? ( +

+ {uploadError || + 'This will make your proof publicly accessible by anyone with the CID'} +

+ ) : ( + e.target.select()} + /> + )} +
+
+ {!request.cid ? ( + <> + {!uploadError && ( + + )} + + + ) : ( + <> + + + + )} +
+
+ ); +} diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx index 6db11c2..64aaf5d 100644 --- a/src/pages/Home/index.tsx +++ b/src/pages/Home/index.tsx @@ -77,9 +77,13 @@ export default function Home(props: { scrollTop={scrollTop} />
setTab('network')} diff --git a/src/pages/ProofViewer/index.tsx b/src/pages/ProofViewer/index.tsx index 6265578..95fde2b 100644 --- a/src/pages/ProofViewer/index.tsx +++ b/src/pages/ProofViewer/index.tsx @@ -3,24 +3,37 @@ import React, { ReactElement, useState, MouseEventHandler, + useCallback, } from 'react'; import { useParams, useNavigate } from 'react-router'; import c from 'classnames'; -import { useRequestHistory } from '../../reducers/history'; +import { + deleteRequestHistory, + useRequestHistory, +} from '../../reducers/history'; import Icon from '../../components/Icon'; import { download } from '../../utils/misc'; import classNames from 'classnames'; +import { useDispatch } from 'react-redux'; export default function ProofViewer(props?: { className?: string; recv?: string; sent?: string; }): ReactElement { + const dispatch = useDispatch(); const { requestId } = useParams<{ requestId: string }>(); const request = useRequestHistory(requestId); const navigate = useNavigate(); const [tab, setTab] = useState('sent'); + const onDelete = useCallback(async () => { + if (requestId) { + dispatch(deleteRequestHistory(requestId)); + navigate(-1); + } + }, [requestId]); + return (
setTab('recv')} active={tab === 'recv'}> Recv -
+
{!props?.recv && (
diff --git a/src/reducers/history.tsx b/src/reducers/history.tsx index 9001ea9..e1bb1dc 100644 --- a/src/reducers/history.tsx +++ b/src/reducers/history.tsx @@ -10,6 +10,7 @@ enum ActionType { '/history/addRequest' = '/history/addRequest', '/history/setRequests' = '/history/setRequests', '/history/deleteRequest' = '/history/deleteRequest', + '/history/addRequestCid' = '/history/addRequestCid', } type Action = { @@ -45,6 +46,13 @@ export const setRequests = (requests: RequestHistory[]) => { }; }; +export const addRequestCid = (requestId: string, cid: string) => { + return { + type: ActionType['/history/addRequestCid'], + payload: { requestId, cid }, + }; +}; + export const deleteRequestHistory = (id: string) => { chrome.runtime.sendMessage({ type: BackgroundActiontype.delete_prove_request, @@ -103,6 +111,20 @@ export default function history( order: newOrder, }; } + case ActionType['/history/addRequestCid']: { + const { requestId, cid } = action.payload; + if (!state.map[requestId]) return state; + return { + ...state, + map: { + ...state.map, + [requestId]: { + ...state.map[requestId], + cid, + }, + }, + }; + } default: return state; }