mirror of
https://github.com/tlsnotary/tlsn-extension.git
synced 2026-01-08 22:58:04 -05:00
feat: add progress for notarization request and resurface error message (#119)
#119
This commit is contained in:
11
package-lock.json
generated
11
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
7
pnpm-lock.yaml
generated
7
pnpm-lock.yaml
generated
@@ -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:
|
||||
|
||||
2
src/assets/img/dot-menu.svg
Normal file
2
src/assets/img/dot-menu.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" id="Glyph" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M16,13c-1.654,0-3,1.346-3,3s1.346,3,3,3s3-1.346,3-3S17.654,13,16,13z" id="XMLID_287_"/><path d="M6,13c-1.654,0-3,1.346-3,3s1.346,3,3,3s3-1.346,3-3S7.654,13,6,13z" id="XMLID_289_"/><path d="M26,13c-1.654,0-3,1.346-3,3s1.346,3,3,3s3-1.346,3-3S27.654,13,26,13z" id="XMLID_291_"/></svg>
|
||||
|
After Width: | Height: | Size: 621 B |
BIN
src/assets/img/notarize.png
Normal file
BIN
src/assets/img/notarize.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -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<RequestHistory | null> {
|
||||
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 },
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 = (
|
||||
<button
|
||||
@@ -318,7 +317,14 @@ function StepContent(
|
||||
btnContent = (
|
||||
<button className="button mt-2 w-fit flex flex-row flex-nowrap items-center gap-2 cursor-default">
|
||||
<Icon className="animate-spin" fa="fa-solid fa-spinner" size={1} />
|
||||
<span className="text-sm">{cta}</span>
|
||||
<span className="text-sm">
|
||||
{notaryRequest?.progress
|
||||
? `(${(
|
||||
((notaryRequest.progress + 1) / 6.06) *
|
||||
100
|
||||
).toFixed()}%) ${progressText(notaryRequest.progress)}`
|
||||
: 'Pending...'}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -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 (
|
||||
<div className="flex flex-col flex-nowrap overflow-y-auto">
|
||||
<div className="flex flex-col flex-nowrap overflow-y-auto pb-36">
|
||||
{history
|
||||
.map((id) => {
|
||||
return <OneRequestHistory key={id} requestId={id} />;
|
||||
@@ -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<any, string>({
|
||||
type: BackgroundActiontype.retry_prove_request,
|
||||
data: {
|
||||
id: props.requestId,
|
||||
notaryUrl,
|
||||
websocketProxyUrl,
|
||||
},
|
||||
});
|
||||
}, [props.requestId]);
|
||||
|
||||
const onView = useCallback(() => {
|
||||
chrome.runtime.sendMessage<any, string>({
|
||||
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 (
|
||||
<div
|
||||
className={classNames(
|
||||
'flex flex-row flex-nowrap border rounded-md p-2 gap-1 hover:bg-slate-50 cursor-pointer',
|
||||
'flex flex-row items-center flex-nowrap border rounded-md px-2.5 py-3 gap-0.5 hover:bg-slate-50 cursor-pointer relative',
|
||||
{
|
||||
'!cursor-default !bg-slate-200': status === 'pending',
|
||||
},
|
||||
props.className,
|
||||
)}
|
||||
onClick={() => {
|
||||
if (status === 'success') onView();
|
||||
if (status === 'error') onShowError();
|
||||
}}
|
||||
>
|
||||
<ShareConfirmationModal />
|
||||
<ErrorModal />
|
||||
<div className="flex flex-col flex-nowrap flex-grow flex-shrink w-0">
|
||||
<div className="flex flex-row items-center text-xs">
|
||||
<div className="bg-slate-200 text-slate-400 px-1 py-0.5 rounded-sm">
|
||||
{request?.method}
|
||||
</div>
|
||||
<div className="text-black font-bold px-2 py-1 rounded-md overflow-hidden text-ellipsis">
|
||||
{requestUrl?.pathname}
|
||||
</div>
|
||||
<div className="w-12 h-12 rounded-full flex flex-row items-center justify-center bg-slate-300">
|
||||
<img
|
||||
className="relative w-7 h-7 top-[-1px] opacity-60"
|
||||
src={NotarizeIcon}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col flex-nowrap flex-grow flex-shrink w-0 gap-1">
|
||||
<div className="flex flex-row text-black text-sm font-semibold px-2 rounded-md overflow-hidden text-ellipsis gap-1">
|
||||
<span>Notarize request</span>
|
||||
<span className="font-normal border-b border-dashed border-slate-400 text-slate-500">
|
||||
{requestUrl?.hostname}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-row">
|
||||
<div className="font-bold text-slate-400">Time:</div>
|
||||
<div className="ml-2 text-slate-800">
|
||||
{new Date(charwise.decode(props.requestId, 'hex')).toISOString()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row">
|
||||
<div className="font-bold text-slate-400">Host:</div>
|
||||
<div className="ml-2 text-slate-800">{requestUrl?.host}</div>
|
||||
</div>
|
||||
<div className="flex flex-row">
|
||||
<div className="font-bold text-slate-400">Notary API:</div>
|
||||
<div className="ml-2 text-slate-800">{request?.notaryUrl}</div>
|
||||
</div>
|
||||
<div className="flex flex-row">
|
||||
<div className="font-bold text-slate-400">TLS Proxy API:</div>
|
||||
<div className="ml-2 text-slate-800">
|
||||
{request?.websocketProxyUrl}
|
||||
</div>
|
||||
<div
|
||||
className={classNames('font-semibold px-2 rounded-sm w-fit', {
|
||||
'text-green-600': status === 'success',
|
||||
'text-red-600': status === 'error',
|
||||
})}
|
||||
>
|
||||
{status === 'success' && 'Success'}
|
||||
{status === 'error' && 'Error'}
|
||||
{status === 'pending' && (
|
||||
<div className="text-center flex flex-row flex-grow-0 gap-2 self-end items-center justify-center text-slate-600">
|
||||
<Icon
|
||||
className="animate-spin"
|
||||
fa="fa-solid fa-spinner"
|
||||
size={1}
|
||||
/>
|
||||
<span className="">
|
||||
{request?.progress
|
||||
? `(${(
|
||||
((request.progress + 1) / 6.06) *
|
||||
100
|
||||
).toFixed()}%) ${progressText(request.progress)}`
|
||||
: 'Pending...'}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
{status === 'success' && (
|
||||
<>
|
||||
<ActionButton
|
||||
className="bg-slate-600 text-slate-200 hover:bg-slate-500 hover:text-slate-100"
|
||||
onClick={onView}
|
||||
fa="fa-solid fa-receipt"
|
||||
ctaText="View"
|
||||
hidden={hideActions.includes('view')}
|
||||
/>
|
||||
<ActionButton
|
||||
className="bg-slate-100 text-slate-300 hover:bg-slate-200 hover:text-slate-500"
|
||||
onClick={() =>
|
||||
download(`${request?.id}.json`, JSON.stringify(request?.proof))
|
||||
}
|
||||
fa="fa-solid fa-download"
|
||||
ctaText="Download"
|
||||
hidden={hideActions.includes('download')}
|
||||
/>
|
||||
<ActionButton
|
||||
className="flex flex-row flex-grow-0 gap-2 self-end items-center justify-end px-2 py-1 bg-slate-100 text-slate-300 hover:bg-slate-200 hover:text-slate-500 hover:font-bold"
|
||||
onClick={() => setShowingShareConfirmation(true)}
|
||||
fa="fa-solid fa-upload"
|
||||
ctaText="Share"
|
||||
hidden={hideActions.includes('share')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{status === 'error' && !!request?.error && (
|
||||
<ErrorButton hidden={hideActions.includes('error')} />
|
||||
)}
|
||||
{(!status || status === 'error') && (
|
||||
<RetryButton hidden={hideActions.includes('retry')} />
|
||||
)}
|
||||
{status === 'pending' && (
|
||||
<button className="flex flex-row flex-grow-0 gap-2 self-end items-center justify-end px-2 py-1 bg-slate-100 text-slate-300 font-bold">
|
||||
<Icon className="animate-spin" fa="fa-solid fa-spinner" size={1} />
|
||||
<span className="text-xs font-bold">Pending</span>
|
||||
</button>
|
||||
)}
|
||||
<ActionButton
|
||||
className="flex flex-row flex-grow-0 gap-2 self-end items-center justify-end px-2 py-1 bg-slate-100 text-slate-300 hover:bg-red-100 hover:text-red-500 hover:font-bold"
|
||||
onClick={onDelete}
|
||||
fa="fa-solid fa-trash"
|
||||
ctaText="Delete"
|
||||
hidden={hideActions.includes('delete')}
|
||||
/>
|
||||
<div className="flex flex-col items-end gap-1">
|
||||
<div className="h-4">
|
||||
{!hideActions.length && (
|
||||
<Icon
|
||||
className="text-slate-500 hover:text-slate-600 relative"
|
||||
fa="fa-solid fa-ellipsis"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
showMenu(true);
|
||||
}}
|
||||
>
|
||||
{showingMenu && (
|
||||
<RequestMenu requestId={props.requestId} showMenu={showMenu} />
|
||||
)}
|
||||
</Icon>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-slate-500" title={day.format('LLLL')}>
|
||||
{day.fromNow()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
function RetryButton(p: { hidden?: boolean }): ReactElement {
|
||||
if (p.hidden) return <></>;
|
||||
return (
|
||||
<button
|
||||
className="flex flex-row flex-grow-0 gap-2 self-end items-center justify-end px-2 py-1 bg-slate-100 text-slate-300 hover:bg-slate-200 hover:text-slate-500 hover:font-bold"
|
||||
onClick={onRetry}
|
||||
>
|
||||
<Icon fa="fa-solid fa-arrows-rotate" size={1} />
|
||||
<span className="text-xs font-bold">Retry</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function ErrorButton(p: { hidden?: boolean }): ReactElement {
|
||||
if (p.hidden) return <></>;
|
||||
return (
|
||||
<button
|
||||
className="flex flex-row flex-grow-0 gap-2 self-end items-center justify-end px-2 py-1 bg-red-100 text-red-300 hover:bg-red-200 hover:text-red-500 hover:font-bold"
|
||||
onClick={onShowError}
|
||||
>
|
||||
<Icon fa="fa-solid fa-circle-exclamation" size={1} />
|
||||
<span className="text-xs font-bold">Error</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function ErrorModal(): ReactElement {
|
||||
const msg = typeof request?.error === 'string' && request?.error;
|
||||
return !showingError ? (
|
||||
@@ -253,99 +166,4 @@ export function OneRequestHistory(props: {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function ShareConfirmationModal(): ReactElement {
|
||||
return !showingShareConfirmation ? (
|
||||
<></>
|
||||
) : (
|
||||
<Modal
|
||||
className="flex flex-col items-center text-base cursor-default justify-center !w-auto mx-4 my-[50%] p-4 gap-4"
|
||||
onClose={closeAllModal}
|
||||
>
|
||||
<ModalContent className="flex flex-col w-full gap-4 items-center text-base justify-center">
|
||||
{!cid[props.requestId] ? (
|
||||
<p className="text-slate-500 text-center">
|
||||
{uploadError ||
|
||||
'This will make your proof publicly accessible by anyone with the CID'}
|
||||
</p>
|
||||
) : (
|
||||
<input
|
||||
className="input w-full bg-slate-100 border border-slate-200"
|
||||
readOnly
|
||||
value={`${EXPLORER_API}/ipfs/${cid[props.requestId]}`}
|
||||
onFocus={(e) => e.target.select()}
|
||||
/>
|
||||
)}
|
||||
</ModalContent>
|
||||
<div className="flex flex-row gap-2 justify-center">
|
||||
{!cid[props.requestId] ? (
|
||||
<>
|
||||
{!uploadError && (
|
||||
<button
|
||||
onClick={handleUpload}
|
||||
className="button button--primary flex flex-row items-center justify-center gap-2 m-0"
|
||||
disabled={uploading}
|
||||
>
|
||||
{uploading && (
|
||||
<Icon
|
||||
className="animate-spin"
|
||||
fa="fa-solid fa-spinner"
|
||||
size={1}
|
||||
/>
|
||||
)}
|
||||
I understand
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="m-0 w-24 bg-slate-100 text-slate-400 hover:bg-slate-200 hover:text-slate-600 font-bold"
|
||||
onClick={closeAllModal}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
onClick={() =>
|
||||
copy(`${EXPLORER_API}/ipfs/${cid[props.requestId]}`)
|
||||
}
|
||||
className="m-0 w-24 bg-slate-600 text-slate-200 hover:bg-slate-500 hover:text-slate-100 font-bold"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
<button
|
||||
className="m-0 w-24 bg-slate-100 text-slate-400 hover:bg-slate-200 hover:text-slate-600 font-bold"
|
||||
onClick={closeAllModal}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function ActionButton(props: {
|
||||
onClick: () => void;
|
||||
fa: string;
|
||||
ctaText: string;
|
||||
className?: string;
|
||||
hidden?: boolean;
|
||||
}): ReactElement {
|
||||
if (props.hidden) return <></>;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classNames(
|
||||
'flex flex-row flex-grow-0 gap-2 self-end items-center justify-end px-2 py-1 hover:font-bold',
|
||||
props.className,
|
||||
)}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
<Icon className="" fa={props.fa} size={1} />
|
||||
<span className="text-xs font-bold">{props.ctaText}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
256
src/pages/History/request-menu.tsx
Normal file
256
src/pages/History/request-menu.tsx
Normal file
@@ -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<any, string>({
|
||||
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 && (
|
||||
<ShareConfirmationModal
|
||||
requestId={requestId}
|
||||
setShowingShareConfirmation={setShowingShareConfirmation}
|
||||
showMenu={showMenu}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className="fixed top-0 left-0 w-screen h-screen z-10 cursor-default"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
showMenu(false);
|
||||
}}
|
||||
/>
|
||||
<div className="absolute top-[100%] right-0 rounded-md z-20">
|
||||
<div className="flex flex-col bg-slate-200 w-40 shadow rounded-md py">
|
||||
{status === 'success' && (
|
||||
<>
|
||||
<RequestMenuRow
|
||||
fa="fa-solid fa-download"
|
||||
className="border-b border-slate-300"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
showMenu(false);
|
||||
download(`${request.id}.json`, JSON.stringify(request.proof));
|
||||
}}
|
||||
>
|
||||
Download
|
||||
</RequestMenuRow>
|
||||
<RequestMenuRow
|
||||
fa="fa-solid fa-upload"
|
||||
className="border-b border-slate-300"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowingShareConfirmation(true);
|
||||
}}
|
||||
>
|
||||
Share
|
||||
</RequestMenuRow>
|
||||
</>
|
||||
)}
|
||||
{status === 'error' && (
|
||||
<RequestMenuRow
|
||||
fa="fa-solid fa-arrows-rotate"
|
||||
className="border-b border-slate-300"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onRetry();
|
||||
showMenu(false);
|
||||
}}
|
||||
>
|
||||
Retry
|
||||
</RequestMenuRow>
|
||||
)}
|
||||
<RequestMenuRow
|
||||
fa="fa-solid fa-trash"
|
||||
className="border-b border-slate-300 !text-red-500"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete();
|
||||
showMenu(false);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</RequestMenuRow>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function RequestMenuRow(props: {
|
||||
fa: string;
|
||||
children?: ReactNode;
|
||||
onClick?: MouseEventHandler;
|
||||
className?: string;
|
||||
}): ReactElement {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'flex flex-row items-center py-3 px-4 gap-2 hover:bg-slate-300 cursor-pointer text-slate-800 hover:text-slate-900 font-semibold',
|
||||
props.className,
|
||||
)}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
<Icon size={0.875} fa={props.fa} />
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 ? (
|
||||
<></>
|
||||
) : (
|
||||
<Modal
|
||||
className="flex flex-col items-center text-base cursor-default justify-center !w-auto mx-4 my-[50%] p-4 gap-4"
|
||||
onClose={(e) => {
|
||||
e.stopPropagation();
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<ModalContent className="flex flex-col w-full gap-4 items-center text-base justify-center">
|
||||
{!request.cid ? (
|
||||
<p className="text-slate-500 text-center">
|
||||
{uploadError ||
|
||||
'This will make your proof publicly accessible by anyone with the CID'}
|
||||
</p>
|
||||
) : (
|
||||
<input
|
||||
className="input w-full bg-slate-100 border border-slate-200"
|
||||
readOnly
|
||||
value={`${EXPLORER_API}/ipfs/${request.cid}`}
|
||||
onFocus={(e) => e.target.select()}
|
||||
/>
|
||||
)}
|
||||
</ModalContent>
|
||||
<div className="flex flex-row gap-2 justify-center">
|
||||
{!request.cid ? (
|
||||
<>
|
||||
{!uploadError && (
|
||||
<button
|
||||
onClick={handleUpload}
|
||||
className="button button--primary flex flex-row items-center justify-center gap-2 m-0"
|
||||
disabled={uploading}
|
||||
>
|
||||
{uploading && (
|
||||
<Icon
|
||||
className="animate-spin"
|
||||
fa="fa-solid fa-spinner"
|
||||
size={1}
|
||||
/>
|
||||
)}
|
||||
I understand
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="m-0 w-24 bg-slate-100 text-slate-400 hover:bg-slate-200 hover:text-slate-600 font-bold"
|
||||
onClick={onClose}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
onClick={() => copy(`${EXPLORER_API}/ipfs/${request.cid}`)}
|
||||
className="m-0 w-24 bg-slate-600 text-slate-200 hover:bg-slate-500 hover:text-slate-100 font-bold"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
<button
|
||||
className="m-0 w-24 bg-slate-100 text-slate-400 hover:bg-slate-200 hover:text-slate-600 font-bold"
|
||||
onClick={onClose}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -77,9 +77,13 @@ export default function Home(props: {
|
||||
scrollTop={scrollTop}
|
||||
/>
|
||||
<div
|
||||
className={classNames('flex flex-row justify-center items-center', {
|
||||
'fixed top-9 w-full bg-white shadow lg:w-[598px] lg:mt-40': shouldFix,
|
||||
})}
|
||||
className={classNames(
|
||||
'flex flex-row justify-center items-center z-10',
|
||||
{
|
||||
'fixed top-9 w-full bg-white shadow lg:w-[598px] lg:mt-40':
|
||||
shouldFix,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<TabSelector
|
||||
onClick={() => setTab('network')}
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
className={classNames(
|
||||
@@ -44,7 +57,7 @@ export default function ProofViewer(props?: {
|
||||
<TabLabel onClick={() => setTab('recv')} active={tab === 'recv'}>
|
||||
Recv
|
||||
</TabLabel>
|
||||
<div className="flex flex-row flex-grow items-center justify-end">
|
||||
<div className="flex flex-row flex-grow items-center justify-end gap-2">
|
||||
{!props?.recv && (
|
||||
<button
|
||||
className="button"
|
||||
@@ -56,6 +69,9 @@ export default function ProofViewer(props?: {
|
||||
Download
|
||||
</button>
|
||||
)}
|
||||
<button className="button !text-red-500" onClick={onDelete}>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ enum ActionType {
|
||||
'/history/addRequest' = '/history/addRequest',
|
||||
'/history/setRequests' = '/history/setRequests',
|
||||
'/history/deleteRequest' = '/history/deleteRequest',
|
||||
'/history/addRequestCid' = '/history/addRequestCid',
|
||||
}
|
||||
|
||||
type Action<payload> = {
|
||||
@@ -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<any, string>({
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user