Compare commits

...

7 Commits
iv ... p2p

Author SHA1 Message Date
tsukino
fe56de0b6c wip 2024-09-01 20:38:14 +08:00
tsukino
0a4f152cea wip 2024-08-31 17:49:44 +08:00
tsukino
855ab19b04 wip 2024-08-31 02:21:04 +08:00
tsukino
1021200fca add rendezvous option 2024-08-30 22:27:37 +08:00
tsukino
00cae21dd9 wip 2024-08-30 21:48:37 +08:00
tsukino
b3e9a56dd9 wip 2024-08-29 18:44:16 +08:00
tsukino
f550001580 wip 2024-08-29 12:16:53 +08:00
21 changed files with 1947 additions and 148 deletions

View File

@@ -6,7 +6,7 @@
padding: 0;
overflow: hidden;
transition: 200ms opacity;
transition-delay: 200ms;
}
&:hover {

View File

@@ -12,7 +12,11 @@ import {
runPlugin,
} from '../../utils/rpc';
import { usePluginHashes } from '../../reducers/plugins';
import { PluginConfig } from '../../utils/misc';
import {
getPluginConfig,
hexToArrayBuffer,
PluginConfig,
} from '../../utils/misc';
import DefaultPluginIcon from '../../assets/img/default-plugin-icon.png';
import classNames from 'classnames';
import Icon from '../Icon';
@@ -25,8 +29,19 @@ import {
PluginInfoModalHeader,
} from '../PluginInfo';
import { getPluginConfigByHash } from '../../entries/Background/db';
import { OffscreenActionTypes } from '../../entries/Offscreen/types';
import { SidePanelActionTypes } from '../../entries/SidePanel/types';
import { openSidePanel } from '../../entries/utils';
export function PluginList(props: { className?: string }): ReactElement {
export function PluginList({
className,
unremovable,
onClick,
}: {
className?: string;
unremovable?: boolean;
onClick?: (hash: string) => void;
}): ReactElement {
const hashes = usePluginHashes();
useEffect(() => {
@@ -34,65 +49,85 @@ export function PluginList(props: { className?: string }): ReactElement {
}, []);
return (
<div
className={classNames('flex flex-col flex-nowrap gap-1', props.className)}
>
<div className={classNames('flex flex-col flex-nowrap gap-1', className)}>
{!hashes.length && (
<div className="flex flex-col items-center justify-center text-slate-400 cursor-default select-none">
<div>No available plugins</div>
</div>
)}
{hashes.map((hash) => (
<Plugin key={hash} hash={hash} />
<Plugin
key={hash}
hash={hash}
unremovable={unremovable}
onClick={onClick}
/>
))}
</div>
);
}
export function Plugin(props: {
export function Plugin({
hash,
hex,
unremovable,
onClick,
className,
}: {
hash: string;
onClick?: () => void;
hex?: string;
className?: string;
onClick?: (hash: string) => void;
unremovable?: boolean;
}): ReactElement {
const [error, showError] = useState('');
const [config, setConfig] = useState<PluginConfig | null>(null);
const [pluginInfo, showPluginInfo] = useState(false);
const [remove, showRemove] = useState(false);
const onClick = useCallback(async () => {
const onRunPlugin = useCallback(async () => {
if (!config || remove) return;
try {
await runPlugin(props.hash, 'start');
if (onClick) {
onClick(hash);
return;
}
const [tab] = await browser.tabs.query({
active: true,
currentWindow: true,
try {
await openSidePanel();
browser.runtime.sendMessage({
type: SidePanelActionTypes.execute_plugin_request,
data: {
pluginHash: hash,
},
});
await browser.storage.local.set({ plugin_hash: props.hash });
// @ts-ignore
if (chrome.sidePanel) await chrome.sidePanel.open({ tabId: tab.id });
await runPlugin(hash, 'start');
window.close();
} catch (e: any) {
showError(e.message);
}
}, [props.hash, config, remove]);
}, [hash, config, remove, onClick]);
useEffect(() => {
(async function () {
setConfig(await getPluginConfigByHash(props.hash));
if (hex) {
setConfig(await getPluginConfig(hexToArrayBuffer(hex)));
} else {
setConfig(await getPluginConfigByHash(hash));
}
})();
}, [props.hash]);
}, [hash, hex]);
const onRemove: MouseEventHandler = useCallback(
(e) => {
e.stopPropagation();
removePlugin(props.hash);
removePlugin(hash);
showRemove(false);
},
[props.hash, remove],
[hash, remove],
);
const onConfirmRemove: MouseEventHandler = useCallback(
@@ -100,7 +135,7 @@ export function Plugin(props: {
e.stopPropagation();
showRemove(true);
},
[props.hash, remove],
[hash, remove],
);
const onPluginInfo: MouseEventHandler = useCallback(
@@ -108,7 +143,7 @@ export function Plugin(props: {
e.stopPropagation();
showPluginInfo(true);
},
[props.hash, pluginInfo],
[hash, pluginInfo],
);
if (!config) return <></>;
@@ -118,8 +153,9 @@ export function Plugin(props: {
className={classNames(
'flex flex-row justify-center border rounded border-slate-300 p-2 gap-2 plugin-box',
'cursor-pointer hover:bg-slate-100 hover:border-slate-400 active:bg-slate-200',
className,
)}
onClick={onClick}
onClick={onRunPlugin}
>
{!!error && <ErrorModal onClose={() => showError('')} message={error} />}
{!remove ? (
@@ -134,11 +170,13 @@ export function Plugin(props: {
className="flex flex-row items-center justify-center cursor-pointer plugin-box__remove-icon"
onClick={onPluginInfo}
/>
<Icon
fa="fa-solid fa-xmark"
className="flex flex-row items-center justify-center cursor-pointer text-red-500 bg-red-200 rounded-full plugin-box__remove-icon"
onClick={onConfirmRemove}
/>
{!unremovable && (
<Icon
fa="fa-solid fa-xmark"
className="flex flex-row items-center justify-center cursor-pointer text-red-500 bg-red-200 rounded-full plugin-box__remove-icon"
onClick={onConfirmRemove}
/>
)}
</div>
</div>
<div>{config.description}</div>

View File

@@ -41,11 +41,31 @@ import {
getMaxSent,
getNotaryApi,
getProxyApi,
getRendezvousApi,
} from '../../utils/storage';
import { deferredPromise } from '../../utils/promise';
import { minimatch } from 'minimatch';
import { OffscreenActionTypes } from '../Offscreen/types';
import { SidePanelActionTypes } from '../SidePanel/types';
import { pushToRedux } from '../utils';
import {
acceptPairRequest,
cancelPairRequest,
connectSession,
disconnectSession,
getP2PState,
rejectPairRequest,
requestProofByHash,
requestProof,
sendPairRequest,
cancelProofRequest,
acceptProofRequest,
rejectProofRequest,
startProofRequest,
startedVerifier,
startedProver,
endProofRequest,
} from './ws';
const charwise = require('charwise');
@@ -54,6 +74,7 @@ export enum BackgroundActiontype {
clear_requests = 'clear_requests',
push_action = 'push_action',
execute_plugin_prover = 'execute_plugin_prover',
execute_p2p_plugin_prover = 'execute_p2p_plugin_prover',
get_prove_requests = 'get_prove_requests',
prove_request_start = 'prove_request_start',
process_prove_request = 'process_prove_request',
@@ -64,12 +85,15 @@ export enum BackgroundActiontype {
retry_prove_request = 'retry_prove_request',
get_cookies_by_hostname = 'get_cookies_by_hostname',
get_headers_by_hostname = 'get_headers_by_hostname',
// Plugins
add_plugin = 'add_plugin',
remove_plugin = 'remove_plugin',
get_plugin_by_hash = 'get_plugin_by_hash',
read_plugin_config = 'read_plugin_config',
get_plugin_config_by_hash = 'get_plugin_config_by_hash',
run_plugin = 'run_plugin',
get_plugin_hashes = 'get_plugin_hashes',
// Content Script
open_popup = 'open_popup',
change_route = 'change_route',
connect_request = 'connect_request',
@@ -86,9 +110,27 @@ export enum BackgroundActiontype {
get_plugins_response = 'get_plugins_response',
run_plugin_request = 'run_plugin_request',
run_plugin_response = 'run_plugin_response',
// App State
get_logging_level = 'get_logging_level',
get_app_state = 'get_app_state',
set_default_plugins_installed = 'set_default_plugins_installed',
// P2P
connect_rendezvous = 'connect_rendezvous',
disconnect_rendezvous = 'disconnect_rendezvous',
send_pair_request = 'send_pair_request',
cancel_pair_request = 'cancel_pair_request',
accept_pair_request = 'accept_pair_request',
reject_pair_request = 'reject_pair_request',
cancel_proof_request = 'cancel_proof_request',
accept_proof_request = 'accept_proof_request',
reject_proof_request = 'reject_proof_request',
start_proof_request = 'start_proof_request',
proof_request_end = 'proof_request_end',
verifier_started = 'verifier_started',
prover_started = 'prover_started',
get_p2p_state = 'get_p2p_state',
request_p2p_proof = 'request_p2p_proof',
request_p2p_proof_by_hash = 'request_p2p_proof_by_hash',
}
export type BackgroundAction = {
@@ -171,12 +213,17 @@ export const initRPC = () => {
return handleGetPluginHashes(request, sendResponse);
case BackgroundActiontype.get_plugin_by_hash:
return handleGetPluginByHash(request, sendResponse);
case BackgroundActiontype.read_plugin_config:
getPluginConfig(request.data).then(sendResponse);
return true;
case BackgroundActiontype.get_plugin_config_by_hash:
return handleGetPluginConfigByHash(request, sendResponse);
case BackgroundActiontype.run_plugin:
return handleRunPlugin(request, sendResponse);
case BackgroundActiontype.execute_plugin_prover:
return handleExecPluginProver(request);
case BackgroundActiontype.execute_p2p_plugin_prover:
return handleExecP2PPluginProver(request);
case BackgroundActiontype.open_popup:
return handleOpenPopup(request);
case BackgroundActiontype.connect_request:
@@ -202,6 +249,54 @@ export const initRPC = () => {
case BackgroundActiontype.set_default_plugins_installed:
setDefaultPluginsInstalled(request.data).then(sendResponse);
return true;
case BackgroundActiontype.connect_rendezvous:
connectSession().then(sendResponse);
return;
case BackgroundActiontype.disconnect_rendezvous:
disconnectSession().then(sendResponse);
return;
case BackgroundActiontype.send_pair_request:
sendPairRequest(request.data).then(sendResponse);
return;
case BackgroundActiontype.cancel_pair_request:
cancelPairRequest(request.data).then(sendResponse);
return;
case BackgroundActiontype.accept_pair_request:
acceptPairRequest(request.data).then(sendResponse);
return;
case BackgroundActiontype.reject_pair_request:
rejectPairRequest(request.data).then(sendResponse);
return;
case BackgroundActiontype.cancel_proof_request:
cancelProofRequest(request.data).then(sendResponse);
return;
case BackgroundActiontype.accept_proof_request:
acceptProofRequest(request.data).then(sendResponse);
return;
case BackgroundActiontype.reject_proof_request:
rejectProofRequest(request.data).then(sendResponse);
return;
case BackgroundActiontype.start_proof_request:
startProofRequest(request.data.pluginHash).then(sendResponse);
return;
case BackgroundActiontype.proof_request_end:
endProofRequest(request.data).then(sendResponse);
return;
case BackgroundActiontype.verifier_started:
startedVerifier(request.data.pluginHash).then(sendResponse);
return;
case BackgroundActiontype.prover_started:
startedProver(request.data.pluginHash).then(sendResponse);
return;
case BackgroundActiontype.request_p2p_proof:
requestProof(request.data).then(sendResponse);
return;
case BackgroundActiontype.request_p2p_proof_by_hash:
requestProofByHash(request.data).then(sendResponse);
return;
case BackgroundActiontype.get_p2p_state:
getP2PState();
return;
default:
break;
}
@@ -226,13 +321,7 @@ function handleGetProveRequests(
): boolean {
getNotaryRequests().then(async (reqs) => {
for (const req of reqs) {
await browser.runtime.sendMessage({
type: BackgroundActiontype.push_action,
data: {
tabId: 'background',
},
action: addRequestHistory(req),
});
await pushToRedux(addRequestHistory(req));
}
sendResponse(reqs);
});
@@ -250,39 +339,21 @@ async function handleFinishProveRequest(
const newReq = await addNotaryRequestProofs(id, proof);
if (!newReq) return;
await browser.runtime.sendMessage({
type: BackgroundActiontype.push_action,
data: {
tabId: 'background',
},
action: addRequestHistory(await getNotaryRequest(id)),
});
await pushToRedux(addRequestHistory(await getNotaryRequest(id)));
}
if (error) {
const newReq = await setNotaryRequestError(id, error);
if (!newReq) return;
await browser.runtime.sendMessage({
type: BackgroundActiontype.push_action,
data: {
tabId: 'background',
},
action: addRequestHistory(await getNotaryRequest(id)),
});
await pushToRedux(addRequestHistory(await getNotaryRequest(id)));
}
if (verification) {
const newReq = await setNotaryRequestVerification(id, verification);
if (!newReq) return;
await browser.runtime.sendMessage({
type: BackgroundActiontype.push_action,
data: {
tabId: 'background',
},
action: addRequestHistory(await getNotaryRequest(id)),
});
await pushToRedux(addRequestHistory(await getNotaryRequest(id)));
}
return sendResponse();
@@ -299,13 +370,7 @@ async function handleRetryProveReqest(
const req = await getNotaryRequest(id);
await browser.runtime.sendMessage({
type: BackgroundActiontype.push_action,
data: {
tabId: 'background',
},
action: addRequestHistory(req),
});
await pushToRedux(addRequestHistory(req));
await browser.runtime.sendMessage({
type: BackgroundActiontype.process_prove_request,
@@ -353,13 +418,7 @@ async function handleProveRequestStart(
await setNotaryRequestStatus(id, 'pending');
await browser.runtime.sendMessage({
type: BackgroundActiontype.push_action,
data: {
tabId: 'background',
},
action: addRequestHistory(await getNotaryRequest(id)),
});
await pushToRedux(addRequestHistory(await getNotaryRequest(id)));
browser.runtime.sendMessage({
type: BackgroundActiontype.process_prove_request,
@@ -417,13 +476,7 @@ async function runPluginProver(request: BackgroundAction, now = Date.now()) {
await setNotaryRequestStatus(id, 'pending');
await browser.runtime.sendMessage({
type: BackgroundActiontype.push_action,
data: {
tabId: 'background',
},
action: addRequestHistory(await getNotaryRequest(id)),
});
await pushToRedux(addRequestHistory(await getNotaryRequest(id)));
await browser.runtime.sendMessage({
type: BackgroundActiontype.process_prove_request,
@@ -444,6 +497,44 @@ async function runPluginProver(request: BackgroundAction, now = Date.now()) {
});
}
async function runP2PPluginProver(request: BackgroundAction, now = Date.now()) {
const {
pluginHash,
url,
method,
headers,
body,
secretHeaders,
secretResps,
websocketProxyUrl: _websocketProxyUrl,
maxSentData: _maxSentData,
maxRecvData: _maxRecvData,
clientId,
} = request.data;
const rendezvousApi = await getRendezvousApi();
const proverUrl = `${rendezvousApi}?clientId=${clientId}:proof`;
const websocketProxyUrl = _websocketProxyUrl || (await getProxyApi());
const maxSentData = _maxSentData || (await getMaxSent());
const maxRecvData = _maxRecvData || (await getMaxRecv());
await browser.runtime.sendMessage({
type: OffscreenActionTypes.start_p2p_prover,
data: {
pluginHash,
url,
method,
headers,
body,
proverUrl,
websocketProxyUrl,
maxRecvData,
maxSentData,
secretHeaders,
secretResps,
},
});
}
export async function handleExecPluginProver(request: BackgroundAction) {
const now = request.data.now;
const id = charwise.encode(now).toString('hex');
@@ -451,6 +542,13 @@ export async function handleExecPluginProver(request: BackgroundAction) {
return id;
}
export async function handleExecP2PPluginProver(request: BackgroundAction) {
const now = request.data.now;
const id = charwise.encode(now).toString('hex');
runP2PPluginProver(request, now);
return id;
}
function handleGetCookiesByHostname(
request: BackgroundAction,
sendResponse: (data?: any) => void,
@@ -486,13 +584,7 @@ async function handleAddPlugin(
if (hash) {
await addPluginConfig(hash, config);
await browser.runtime.sendMessage({
type: BackgroundActiontype.push_action,
data: {
tabId: 'background',
},
action: addOnePlugin(hash),
});
await pushToRedux(addOnePlugin(hash));
}
}
} finally {
@@ -506,13 +598,7 @@ async function handleRemovePlugin(
) {
await removePlugin(request.data);
await removePluginConfig(request.data);
await browser.runtime.sendMessage({
type: BackgroundActiontype.push_action,
data: {
tabId: 'background',
},
action: removeOnePlugin(request.data),
});
await pushToRedux(removeOnePlugin(request.data));
return sendResponse();
}
@@ -523,13 +609,7 @@ async function handleGetPluginHashes(
) {
const hashes = await getPluginHashes();
for (const hash of hashes) {
await browser.runtime.sendMessage({
type: BackgroundActiontype.push_action,
data: {
tabId: 'background',
},
action: addOnePlugin(hash),
});
await pushToRedux(addOnePlugin(hash));
}
return sendResponse();
}
@@ -557,11 +637,11 @@ function handleRunPlugin(
sendResponse: (data?: any) => void,
) {
(async () => {
const { hash, method, params } = request.data;
const { hash, method, params, meta } = request.data;
const hex = await getPluginByHash(hash);
const arrayBuffer = hexToArrayBuffer(hex!);
const config = await getPluginConfig(arrayBuffer);
const plugin = await makePlugin(arrayBuffer, config);
const plugin = await makePlugin(arrayBuffer, config, meta?.p2p);
devlog(`plugin::${method}`, params);
const out = await plugin.call(method, params);
devlog(`plugin response: `, out.string());
@@ -1059,7 +1139,6 @@ async function handleRunPluginCSRequest(request: BackgroundAction) {
);
const onPluginRequest = async (req: any) => {
console.log(req);
if (req.type !== SidePanelActionTypes.execute_plugin_response) return;
if (req.data.hash !== hash) return;

View File

@@ -0,0 +1,584 @@
import { devlog, safeParseJSON, sha256 } from '../../utils/misc';
import {
appendIncomingPairingRequests,
appendIncomingProofRequests,
appendOutgoingPairingRequests,
appendOutgoingProofRequest,
setClientId,
setConnected,
setIncomingPairingRequest,
setIncomingProofRequest,
setOutgoingPairingRequest,
setOutgoingProofRequest,
setP2PError,
setPairing,
} from '../../reducers/p2p';
import { pushToRedux } from '../utils';
import { getPluginByHash } from './db';
import browser from 'webextension-polyfill';
import { OffscreenActionTypes } from '../Offscreen/types';
import { getMaxRecv, getMaxSent, getRendezvousApi } from '../../utils/storage';
import { SidePanelActionTypes } from '../SidePanel/types';
const state: {
clientId: string;
pairing: string;
socket: WebSocket | null;
connected: boolean;
reqId: number;
incomingPairingRequests: string[];
outgoingPairingRequests: string[];
incomingProofRequests: string[];
outgoingProofRequests: string[];
} = {
clientId: '',
pairing: '',
socket: null,
connected: false,
reqId: 0,
incomingPairingRequests: [],
outgoingPairingRequests: [],
incomingProofRequests: [],
outgoingProofRequests: [],
};
export const getP2PState = async () => {
pushToRedux(setPairing(state.pairing));
pushToRedux(setConnected(state.connected));
pushToRedux(setClientId(state.clientId));
pushToRedux(setIncomingPairingRequest(state.incomingPairingRequests));
pushToRedux(setOutgoingPairingRequest(state.outgoingPairingRequests));
pushToRedux(setIncomingProofRequest(state.incomingProofRequests));
pushToRedux(setOutgoingProofRequest(state.outgoingProofRequests));
};
export const connectSession = async () => {
if (state.socket) return;
const socket = new WebSocket('ws://localhost:3001');
socket.onopen = () => {
devlog('Connected to websocket');
state.connected = true;
state.socket = socket;
pushToRedux(setConnected(true));
};
socket.onmessage = async (event) => {
const message: any = safeParseJSON(await event.data.text());
if (message.error) {
pushToRedux(setP2PError(message.error.message));
return;
}
switch (message.method) {
case 'client_connect': {
const { clientId } = message.params;
state.clientId = clientId;
pushToRedux(setClientId(clientId));
break;
}
case 'pair_request': {
const { from } = message.params;
state.incomingPairingRequests = [
...new Set(state.incomingPairingRequests.concat(from)),
];
pushToRedux(appendIncomingPairingRequests(from));
break;
}
case 'pair_request_sent': {
const { pairId } = message.params;
state.outgoingPairingRequests = [
...new Set(state.outgoingPairingRequests.concat(pairId)),
];
pushToRedux(appendOutgoingPairingRequests(pairId));
break;
}
case 'pair_request_cancel': {
const { from } = message.params;
state.incomingPairingRequests = state.incomingPairingRequests.filter(
(id) => id !== from,
);
pushToRedux(setIncomingPairingRequest(state.incomingPairingRequests));
break;
}
case 'pair_request_cancelled': {
const { pairId } = message.params;
state.outgoingPairingRequests = state.outgoingPairingRequests.filter(
(id) => id !== pairId,
);
pushToRedux(setOutgoingPairingRequest(state.outgoingPairingRequests));
break;
}
case 'pair_request_reject': {
const { from } = message.params;
state.outgoingPairingRequests = state.outgoingPairingRequests.filter(
(id) => id !== from,
);
pushToRedux(setOutgoingPairingRequest(state.outgoingPairingRequests));
break;
}
case 'pair_request_accept': {
const { from } = message.params;
state.pairing = from;
state.outgoingPairingRequests = state.outgoingPairingRequests.filter(
(id) => id !== from,
);
pushToRedux(setOutgoingPairingRequest(state.outgoingPairingRequests));
pushToRedux(setPairing(from));
break;
}
case 'pair_request_success': {
const { pairId } = message.params;
state.pairing = pairId;
pushToRedux(setPairing(pairId));
state.incomingPairingRequests = state.incomingPairingRequests.filter(
(id) => id !== pairId,
);
pushToRedux(setIncomingPairingRequest(state.incomingPairingRequests));
break;
}
case 'pair_request_rejected': {
const { pairId } = message.params;
state.incomingPairingRequests = state.incomingPairingRequests.filter(
(id) => id !== pairId,
);
pushToRedux(setIncomingPairingRequest(state.incomingPairingRequests));
break;
}
case 'request_proof': {
const { plugin, pluginHash } = message.params;
state.incomingProofRequests = [
...new Set(state.incomingProofRequests.concat(plugin)),
];
pushToRedux(appendIncomingProofRequests(plugin));
handleProofRequestReceived(pluginHash);
break;
}
case 'request_proof_by_hash': {
const { pluginHash } = message.params;
const plugin = await getPluginByHash(pluginHash);
if (plugin) {
state.incomingProofRequests = [
...new Set(state.incomingProofRequests.concat(plugin)),
];
pushToRedux(appendIncomingProofRequests(plugin));
handleProofRequestReceived(pluginHash);
} else {
handleNoPluginHash(pluginHash);
}
break;
}
case 'request_proof_by_hash_failed': {
const { pluginHash } = message.params;
requestProof(pluginHash);
break;
}
case 'proof_request_received': {
const { pluginHash } = message.params;
state.outgoingProofRequests = [
...new Set(state.outgoingProofRequests.concat(pluginHash)),
];
pushToRedux(appendOutgoingProofRequest(pluginHash));
break;
}
case 'proof_request_cancelled':
case 'proof_request_reject':
await handleRemoveOutgoingProofRequest(message);
break;
case 'proof_request_cancel':
case 'proof_request_rejected':
await handleRemoveIncomingProofRequest(message);
break;
case 'proof_request_accept': {
const { pluginHash, from } = message.params;
const maxSentData = await getMaxSent();
const maxRecvData = await getMaxRecv();
const rendezvousApi = await getRendezvousApi();
browser.runtime.sendMessage({
type: OffscreenActionTypes.start_p2p_verifier,
data: {
pluginHash,
maxSentData,
maxRecvData,
verifierUrl:
rendezvousApi + '?clientId=' + state.clientId + ':proof',
peerId: state.pairing,
},
});
break;
}
case 'verifier_started': {
const { pluginHash } = message.params;
browser.runtime.sendMessage({
type: SidePanelActionTypes.start_p2p_plugin,
data: {
pluginHash: pluginHash,
},
});
break;
}
case 'prover_started': {
const { pluginHash } = message.params;
browser.runtime.sendMessage({
type: OffscreenActionTypes.prover_started,
data: {
pluginHash: pluginHash,
},
});
break;
}
case 'proof_request_start': {
const { pluginHash, from } = message.params;
browser.runtime.sendMessage({
type: OffscreenActionTypes.start_p2p_proof_request,
data: {
pluginHash: pluginHash,
},
});
break;
}
case 'proof_request_end': {
const { pluginHash, proof } = message.params;
browser.runtime.sendMessage({
type: OffscreenActionTypes.end_p2p_proof_request,
data: {
pluginHash: pluginHash,
proof: proof,
},
});
break;
}
default:
console.warn(`Unknown message type "${message.method}"`);
break;
}
};
socket.onerror = () => {
console.error('Error connecting to websocket');
pushToRedux(setConnected(false));
};
};
async function handleRemoveOutgoingProofRequest(message: {
params: { pluginHash: string };
}) {
const { pluginHash } = message.params;
state.outgoingProofRequests = state.outgoingProofRequests.filter(
(hash) => hash !== pluginHash,
);
pushToRedux(setOutgoingProofRequest(state.outgoingProofRequests));
}
async function handleRemoveIncomingProofRequest(message: {
params: { pluginHash: string };
}) {
const { pluginHash } = message.params;
const plugin = await getPluginByHash(pluginHash);
const incomingProofRequest = [];
for (const hex of state.incomingProofRequests) {
if (plugin) {
if (plugin !== hex) incomingProofRequest.push(hex);
} else {
if ((await sha256(hex)) !== pluginHash) incomingProofRequest.push(hex);
}
}
state.incomingProofRequests = incomingProofRequest;
pushToRedux(setIncomingProofRequest(state.incomingProofRequests));
}
export const disconnectSession = async () => {
if (!state.socket) return;
await state.socket.close();
state.socket = null;
state.clientId = '';
state.pairing = '';
state.connected = false;
state.incomingPairingRequests = [];
state.outgoingPairingRequests = [];
state.incomingProofRequests = [];
state.outgoingProofRequests = [];
pushToRedux(setPairing(''));
pushToRedux(setConnected(false));
pushToRedux(setClientId(''));
pushToRedux(setIncomingPairingRequest([]));
pushToRedux(setOutgoingPairingRequest([]));
pushToRedux(setIncomingProofRequest([]));
pushToRedux(setOutgoingProofRequest([]));
};
export const sendPairRequest = async (target: string) => {
const { socket, clientId } = state;
if (clientId === target) return;
if (socket && clientId) {
socket.send(
bufferify({
method: 'pair_request',
params: {
from: clientId,
to: target,
id: state.reqId++,
},
}),
);
}
};
export const cancelPairRequest = async (target: string) => {
const { socket, clientId } = state;
if (clientId === target) return;
if (socket && clientId) {
socket.send(
bufferify({
method: 'pair_request_cancel',
params: {
from: clientId,
to: target,
id: state.reqId++,
},
}),
);
}
};
export const acceptPairRequest = async (target: string) => {
const { socket, clientId } = state;
if (clientId === target) return;
if (socket && clientId) {
socket.send(
bufferify({
method: 'pair_request_accept',
params: {
from: clientId,
to: target,
id: state.reqId++,
},
}),
);
}
};
export const rejectPairRequest = async (target: string) => {
const { socket, clientId } = state;
if (clientId === target) return;
if (socket && clientId) {
socket.send(
bufferify({
method: 'pair_request_reject',
params: {
from: clientId,
to: target,
id: state.reqId++,
},
}),
);
}
};
export const requestProof = async (pluginHash: string) => {
const { socket, clientId, pairing } = state;
const pluginHex = await getPluginByHash(pluginHash);
if (socket && clientId && pairing) {
socket.send(
bufferify({
method: 'request_proof',
params: {
from: clientId,
to: pairing,
plugin: pluginHex,
pluginHash,
id: state.reqId++,
},
}),
);
}
};
export const requestProofByHash = async (pluginHash: string) => {
const { socket, clientId, pairing } = state;
if (socket && clientId && pairing) {
socket.send(
bufferify({
method: 'request_proof_by_hash',
params: {
from: clientId,
to: pairing,
pluginHash,
id: state.reqId++,
},
}),
);
}
};
export const cancelProofRequest = async (pluginHash: string) => {
const { socket, clientId, pairing } = state;
if (socket && clientId && pairing) {
socket.send(
bufferify({
method: 'proof_request_cancel',
params: {
from: clientId,
to: pairing,
pluginHash,
id: state.reqId++,
},
}),
);
}
};
export const acceptProofRequest = async (pluginHash: string) => {
const { socket, clientId, pairing } = state;
if (socket && clientId && pairing) {
socket.send(
bufferify({
method: 'proof_request_accept',
params: {
from: clientId,
to: pairing,
pluginHash,
id: state.reqId++,
},
}),
);
}
};
export const startProofRequest = async (pluginHash: string) => {
const { socket, clientId, pairing } = state;
if (socket && clientId && pairing) {
socket.send(
bufferify({
method: 'proof_request_start',
params: {
from: clientId,
to: pairing,
pluginHash,
id: state.reqId++,
},
}),
);
}
};
export const endProofRequest = async (data: {
pluginHash: string;
proof: any;
}) => {
const { socket, clientId, pairing } = state;
if (socket && clientId && pairing) {
socket.send(
bufferify({
method: 'proof_request_end',
params: {
from: clientId,
to: pairing,
pluginHash: data.pluginHash,
proof: data.proof,
id: state.reqId++,
},
}),
);
}
};
export const rejectProofRequest = async (pluginHash: string) => {
const { socket, clientId, pairing } = state;
if (socket && clientId && pairing) {
socket.send(
bufferify({
method: 'proof_request_reject',
params: {
from: clientId,
to: pairing,
pluginHash,
id: state.reqId++,
},
}),
);
}
};
export const startedVerifier = async (pluginHash: string) => {
const { socket, clientId, pairing } = state;
if (socket && clientId && pairing) {
socket.send(
bufferify({
method: 'verifier_started',
params: {
from: clientId,
to: pairing,
pluginHash,
id: state.reqId++,
},
}),
);
}
};
export const startedProver = async (pluginHash: string) => {
const { socket, clientId, pairing } = state;
if (socket && clientId && pairing) {
socket.send(
bufferify({
method: 'prover_started',
params: {
from: clientId,
to: pairing,
pluginHash,
id: state.reqId++,
},
}),
);
}
};
export const handleNoPluginHash = async (pluginHash: string) => {
const { socket, clientId, pairing } = state;
if (socket && clientId && pairing) {
socket.send(
bufferify({
method: 'request_proof_by_hash_failed',
params: {
from: clientId,
to: pairing,
pluginHash,
id: state.reqId++,
},
}),
);
}
};
export const handleProofRequestReceived = async (pluginHash: string) => {
const { socket, clientId, pairing } = state;
if (socket && clientId && pairing) {
socket.send(
bufferify({
method: 'proof_request_received',
params: {
from: clientId,
to: pairing,
pluginHash,
id: state.reqId++,
},
}),
);
}
};
function bufferify(data: any): Buffer {
return Buffer.from(JSON.stringify(data));
}

View File

@@ -1,9 +1,10 @@
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import * as Comlink from 'comlink';
import { OffscreenActionTypes } from './types';
import {
NotaryServer,
Prover as _Prover,
Verifier as _Verifier,
NotarizedSession as _NotarizedSession,
TlsProof as _TlsProof,
} from 'tlsn-js';
@@ -14,16 +15,18 @@ import { BackgroundActiontype } from '../Background/rpc';
import browser from 'webextension-polyfill';
import { Proof, ProofV1 } from '../../utils/types';
import { Method } from 'tlsn-js/wasm/pkg';
import { waitForEvent } from '../utils';
import type { ParsedTranscriptData } from '../../../../tlsn-js/src/types';
const { init, Prover, NotarizedSession, TlsProof }: any = Comlink.wrap(
new Worker(new URL('./worker.ts', import.meta.url)),
);
const { init, Prover, NotarizedSession, TlsProof, Verifier }: any =
Comlink.wrap(new Worker(new URL('./worker.ts', import.meta.url)));
const Offscreen = () => {
useEffect(() => {
(async () => {
const loggingLevel = await browser.runtime.sendMessage({
type: BackgroundActiontype.get_logging_level,
hardwareConcurrency: 1,
});
await init({ loggingLevel });
// @ts-ignore
@@ -128,6 +131,150 @@ const Offscreen = () => {
})();
break;
}
case OffscreenActionTypes.start_p2p_verifier: {
(async () => {
const { pluginHash, maxSentData, maxRecvData, verifierUrl } =
request.data;
const verifier: _Verifier = await new Verifier({
id: pluginHash,
max_sent_data: maxSentData,
max_recv_data: maxRecvData,
});
await verifier.connect(verifierUrl);
// await new Promise((r) => setTimeout(r, 10000));
const proverStarted = waitForEvent(
OffscreenActionTypes.prover_started,
);
verifier.verify().then((res) => {
console.log(res);
browser.runtime.sendMessage({
type: BackgroundActiontype.proof_request_end,
data: {
pluginHash,
proof: res,
},
});
});
browser.runtime.sendMessage({
type: BackgroundActiontype.verifier_started,
data: {
pluginHash,
},
});
await proverStarted;
browser.runtime.sendMessage({
type: BackgroundActiontype.start_proof_request,
data: {
pluginHash,
},
});
})();
break;
}
case OffscreenActionTypes.start_p2p_prover: {
(async () => {
const {
pluginHash,
url,
method,
headers,
body,
proverUrl,
websocketProxyUrl,
maxRecvData,
maxSentData,
secretHeaders,
secretResps,
} = request.data;
const hostname = urlify(url)?.hostname || '';
const prover: _Prover = await new Prover({
id: pluginHash,
serverDns: hostname,
maxSentData,
maxRecvData,
});
const proofRequestStart = waitForEvent(
OffscreenActionTypes.start_p2p_proof_request,
);
const proverSetup = prover.setup(proverUrl);
// await new Promise((r) => setTimeout(r, 10000));
await proverSetup;
browser.runtime.sendMessage({
type: BackgroundActiontype.prover_started,
data: {
pluginHash,
},
});
await proofRequestStart;
await prover.sendRequest(
websocketProxyUrl + `?token=${hostname}`,
{
url,
method,
headers,
body,
},
);
const transcript = await prover.transcript();
const commit = {
sent: subtractRanges(
transcript.ranges.sent.all,
secretHeaders
.map((secret: string) => {
const index = transcript.sent.indexOf(secret);
return index > -1
? {
start: index,
end: index + secret.length,
}
: null;
})
.filter((data: any) => !!data) as {
start: number;
end: number;
}[],
),
recv: subtractRanges(
transcript.ranges.recv.all,
secretResps
.map((secret: string) => {
const index = transcript.recv.indexOf(secret);
return index > -1
? {
start: index,
end: index + secret.length,
}
: null;
})
.filter((data: any) => !!data) as {
start: number;
end: number;
}[],
),
};
const endRequest = waitForEvent(
OffscreenActionTypes.end_p2p_proof_request,
);
await prover.reveal(commit);
console.log(await endRequest);
})();
break;
}
default:
break;
}
@@ -276,6 +423,55 @@ async function createProof(options: {
return proof;
}
function getCommitFromTranscript(
transcript: {
sent: string;
recv: string;
ranges: { recv: ParsedTranscriptData; sent: ParsedTranscriptData };
},
secretHeaders: string[],
secretResps: string[],
) {
const commit = {
sent: subtractRanges(
transcript.ranges.sent.all,
secretHeaders
.map((secret: string) => {
const index = transcript.sent.indexOf(secret);
return index > -1
? {
start: index,
end: index + secret.length,
}
: null;
})
.filter((data: any) => !!data) as {
start: number;
end: number;
}[],
),
recv: subtractRanges(
transcript.ranges.recv.all,
secretResps
.map((secret: string) => {
const index = transcript.recv.indexOf(secret);
return index > -1
? {
start: index,
end: index + secret.length,
}
: null;
})
.filter((data: any) => !!data) as {
start: number;
end: number;
}[],
),
};
return commit;
}
async function verifyProof(
proof: Proof,
): Promise<{ sent: string; recv: string }> {

View File

@@ -1,4 +1,9 @@
export enum OffscreenActionTypes {
notarization_request = 'offscreen/notarization_request',
notarization_response = 'offscreen/notarization_response',
start_p2p_verifier = 'offscreen/start_p2p_verifier',
start_p2p_prover = 'offscreen/start_p2p_prover',
prover_started = 'offscreen/prover_started',
start_p2p_proof_request = 'offscreen/start_p2p_proof_request',
end_p2p_proof_request = 'offscreen/end_p2p_proof_request',
}

View File

@@ -1,9 +1,10 @@
import * as Comlink from 'comlink';
import init, { Prover, NotarizedSession, TlsProof } from 'tlsn-js';
import init, { Prover, NotarizedSession, TlsProof, Verifier } from 'tlsn-js';
Comlink.expose({
init,
Prover,
Verifier,
NotarizedSession,
TlsProof,
});

View File

@@ -33,11 +33,17 @@ import Icon from '../../components/Icon';
import classNames from 'classnames';
import { getConnection } from '../Background/db';
import { useIsConnected, setConnection } from '../../reducers/requests';
import { P2PHome } from '../../pages/PeerToPeer';
import { fetchP2PState } from '../../reducers/p2p';
const Popup = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
useEffect(() => {
fetchP2PState();
}, []);
useEffect(() => {
(async () => {
const [tab] = await browser.tabs.query({
@@ -111,6 +117,7 @@ const Popup = () => {
<Route path="/notarize-approval" element={<NotarizeApproval />} />
<Route path="/get-plugins-approval" element={<GetPluginsApproval />} />
<Route path="/run-plugin-approval" element={<RunPluginApproval />} />
<Route path="/p2p" element={<P2PHome />} />
<Route
path="/install-plugin-approval"
element={<InstallPluginApproval />}

View File

@@ -1,12 +1,12 @@
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
import './sidePanel.scss';
import browser from 'webextension-polyfill';
import { fetchPluginConfigByHash, runPlugin } from '../../utils/rpc';
import {
getPluginConfig,
hexToArrayBuffer,
makePlugin,
PluginConfig,
sha256,
StepConfig,
} from '../../utils/misc';
import { PluginList } from '../../components/PluginList';
@@ -17,28 +17,62 @@ import Icon from '../../components/Icon';
import { useRequestHistory } from '../../reducers/history';
import { BackgroundActiontype } from '../Background/rpc';
import { getPluginByHash, getPluginConfigByHash } from '../Background/db';
import type { Plugin } from '@extism/extism';
import { OffscreenActionTypes } from '../Offscreen/types';
import { SidePanelActionTypes } from './types';
import {
fetchP2PState,
useClientId,
useIncomingProofRequests,
} from '../../reducers/p2p';
export default function SidePanel(): ReactElement {
const [config, setConfig] = useState<PluginConfig | null>(null);
const [hash, setHash] = useState('');
const [hex, setHex] = useState('');
const [p2p, setP2P] = useState(false);
const [started, setStarted] = useState(false);
const clientId = useClientId();
useEffect(() => {
(async function () {
const result = await browser.storage.local.get('plugin_hash');
const { plugin_hash } = result;
const config = await getPluginConfigByHash(plugin_hash);
setHash(plugin_hash);
setConfig(config);
// await browser.storage.local.set({ plugin_hash: '' });
})();
fetchP2PState();
browser.runtime.sendMessage({
type: SidePanelActionTypes.panel_opened,
});
}, []);
useEffect(() => {
browser.runtime.onMessage.addListener(async (request) => {
const { type, data } = request;
switch (type) {
case SidePanelActionTypes.execute_plugin_request: {
setConfig(await getPluginConfigByHash(data.pluginHash));
setHash(data.pluginHash);
setStarted(true);
break;
}
case SidePanelActionTypes.run_p2p_plugin_request: {
const { pluginHash, plugin } = data;
const config =
(await getPluginConfigByHash(pluginHash)) ||
(await getPluginConfig(hexToArrayBuffer(plugin)));
setHash(pluginHash);
setHex(plugin);
setP2P(true);
setConfig(config);
break;
}
case SidePanelActionTypes.start_p2p_plugin: {
setStarted(true);
break;
}
}
});
}, []);
return (
<div className="flex flex-col bg-slate-100 w-screen h-screen">
<div className="relative flex flex-nowrap flex-shrink-0 flex-row items-center relative gap-2 h-9 p-2 cursor-default justify-center bg-slate-300 w-full">
<div className="relative flex flex-nowrap flex-shrink-0 flex-row items-center gap-2 h-9 p-2 cursor-default justify-center bg-slate-300 w-full">
<img className="h-5" src={logo} alt="logo" />
<button
className="button absolute right-2"
@@ -47,8 +81,16 @@ export default function SidePanel(): ReactElement {
Close
</button>
</div>
{!config && <PluginList />}
{config && <PluginBody hash={hash} config={config} />}
{/*{!config && <PluginList />}*/}
{started && config && (
<PluginBody
hash={hash}
hex={hex}
config={config}
p2p={p2p}
clientId={clientId}
/>
)}
</div>
);
}
@@ -56,9 +98,12 @@ export default function SidePanel(): ReactElement {
function PluginBody(props: {
config: PluginConfig;
hash: string;
hex?: string;
clientId?: string;
p2p?: boolean;
}): ReactElement {
const { hash } = props;
const { title, description, icon, steps } = props.config;
const { hash, hex, config, p2p, clientId } = props;
const { title, description, icon, steps } = config;
const [responses, setResponses] = useState<any[]>([]);
const [notarizationId, setNotarizationId] = useState('');
const notaryRequest = useRequestHistory(notarizationId);
@@ -110,10 +155,14 @@ function PluginBody(props: {
{steps?.map((step, i) => (
<StepContent
hash={hash}
config={config}
hex={hex}
index={i}
setResponse={setResponse}
lastResponse={i > 0 ? responses[i - 1] : undefined}
responses={responses}
p2p={p2p}
clientId={clientId}
{...step}
/>
))}
@@ -125,10 +174,14 @@ function PluginBody(props: {
function StepContent(
props: StepConfig & {
hash: string;
hex?: string;
clientId?: string;
index: number;
setResponse: (resp: any, i: number) => void;
responses: any[];
lastResponse?: any;
config: PluginConfig;
p2p?: boolean;
},
): ReactElement {
const {
@@ -141,6 +194,10 @@ function StepContent(
lastResponse,
prover,
hash,
hex: _hex,
config,
p2p = false,
clientId = '',
} = props;
const [completed, setCompleted] = useState(false);
const [pending, setPending] = useState(false);
@@ -149,11 +206,10 @@ function StepContent(
const notaryRequest = useRequestHistory(notarizationId);
const getPlugin = useCallback(async () => {
const hex = await getPluginByHash(hash);
const config = await getPluginConfigByHash(hash);
const hex = (await getPluginByHash(hash)) || _hex;
const arrayBuffer = hexToArrayBuffer(hex!);
return makePlugin(arrayBuffer, config!);
}, [hash]);
return makePlugin(arrayBuffer, config, { p2p, clientId });
}, [hash, _hex, config, p2p, clientId]);
const processStep = useCallback(async () => {
const plugin = await getPlugin();

View File

@@ -1,4 +1,8 @@
export enum SidePanelActionTypes {
panel_opened = 'sidePanel/panel_opened',
execute_plugin_request = 'sidePanel/execute_plugin_request',
execute_plugin_response = 'sidePanel/execute_plugin_response',
run_p2p_plugin_request = 'sidePanel/run_p2p_plugin_request',
run_p2p_plugin_response = 'sidePanel/run_p2p_plugin_response',
start_p2p_plugin = 'sidePanel/start_p2p_plugin',
}

62
src/entries/utils.ts Normal file
View File

@@ -0,0 +1,62 @@
import browser from 'webextension-polyfill';
import { BackgroundActiontype } from './Background/rpc';
import { SidePanelActionTypes } from './SidePanel/types';
import { deferredPromise } from '../utils/promise';
import { devlog } from '../utils/misc';
export const pushToRedux = async (action: {
type: string;
payload?: any;
error?: boolean;
meta?: any;
}) => {
return browser.runtime.sendMessage({
type: BackgroundActiontype.push_action,
data: {
tabId: 'background',
},
action,
});
};
export const openSidePanel = async () => {
const { promise, resolve, reject } = deferredPromise();
try {
const [tab] = await browser.tabs.query({
active: true,
currentWindow: true,
});
const listener = async (request: any) => {
if (request.type === SidePanelActionTypes.panel_opened) {
browser.runtime.onMessage.removeListener(listener);
resolve();
}
};
browser.runtime.onMessage.addListener(listener);
// @ts-ignore
if (chrome.sidePanel) await chrome.sidePanel.open({ tabId: tab.id });
} catch (e) {
reject(e);
}
return promise;
};
export const waitForEvent = async (event: string) => {
const { promise, resolve } = deferredPromise();
const listener = async (request: any) => {
if (request.type === event) {
devlog('received event:', event);
browser.runtime.onMessage.removeListener(listener);
resolve(request);
}
};
browser.runtime.onMessage.addListener(listener);
return promise;
};

View File

@@ -11,11 +11,13 @@ import { useRequests } from '../../reducers/requests';
import { PluginList } from '../../components/PluginList';
import PluginUploadInfo from '../../components/PluginInfo';
import { ErrorModal } from '../../components/ErrorModal';
import { useClientId } from '../../reducers/p2p';
export default function Home(): ReactElement {
const requests = useRequests();
const navigate = useNavigate();
const [error, showError] = useState('');
const clientId = useClientId();
return (
<div className="flex flex-col gap-4 py-4 overflow-y-auto">
@@ -41,6 +43,17 @@ export default function Home(): ReactElement {
<PluginUploadInfo />
Add a plugin
</NavButton>
<NavButton
className={'relative'}
fa="fa-solid fa-circle"
iconSize={0.5}
iconClassName={classNames({
'!text-green-500': clientId,
})}
onClick={() => navigate('/p2p')}
>
P2P
</NavButton>
<NavButton fa="fa-solid fa-gear" onClick={() => navigate('/options')}>
Options
</NavButton>
@@ -55,12 +68,14 @@ function NavButton(props: {
children?: ReactNode;
onClick?: MouseEventHandler;
className?: string;
iconClassName?: string;
disabled?: boolean;
iconSize?: number;
}): ReactElement {
return (
<button
className={classNames(
'flex flex-row flex-nowrap items-center justify-center',
'flex flex-row flex-nowrap items-center justify-center relative',
'text-white rounded px-2 py-1 gap-1',
{
'bg-primary/[.8] hover:bg-primary/[.7] active:bg-primary':
@@ -72,8 +87,15 @@ function NavButton(props: {
onClick={props.onClick}
disabled={props.disabled}
>
<Icon className="flex-grow-0 flex-shrink-0" fa={props.fa} size={1} />
<span className="flex-grow flex-shrink w-0 flex-grow font-bold">
<Icon
className={classNames(
'absolute w-6 justify-center left-2',
props.iconClassName,
)}
fa={props.fa}
size={props.iconSize || 1}
/>
<span className="flex-grow flex-shrink w-0 font-bold">
{props.children}
</span>
</button>

View File

@@ -17,6 +17,8 @@ import {
getProxyApi,
getLoggingFilter,
LOGGING_FILTER_KEY,
getRendezvousApi,
RENDEZVOUS_API_LS_KEY,
} from '../../utils/storage';
import {
EXPLORER_API,
@@ -24,6 +26,7 @@ import {
NOTARY_PROXY,
MAX_RECV,
MAX_SENT,
RENDEZVOUS_API,
} from '../../utils/constants';
import Modal, { ModalContent } from '../../components/Modal/Modal';
import browser from 'webextension-polyfill';
@@ -36,6 +39,7 @@ export default function Options(): ReactElement {
const [maxSent, setMaxSent] = useState(MAX_SENT);
const [maxReceived, setMaxReceived] = useState(MAX_RECV);
const [loggingLevel, setLoggingLevel] = useState<LoggingLevel>('Info');
const [rendezvous, setRendezvous] = useState(RENDEZVOUS_API);
const [dirty, setDirty] = useState(false);
const [shouldReload, setShouldReload] = useState(false);
@@ -44,11 +48,17 @@ export default function Options(): ReactElement {
useEffect(() => {
(async () => {
setNotary((await getNotaryApi()) || NOTARY_API);
setProxy((await getProxyApi()) || NOTARY_PROXY);
setNotary(await getNotaryApi());
setProxy(await getProxyApi());
})();
}, []);
useEffect(() => {
(async () => {
setMaxReceived((await getMaxRecv()) || MAX_RECV);
setMaxSent((await getMaxSent()) || MAX_SENT);
setLoggingLevel((await getLoggingFilter()) || 'Info');
setRendezvous((await getRendezvousApi()) || RENDEZVOUS_API);
})();
}, [advanced]);
@@ -63,9 +73,18 @@ export default function Options(): ReactElement {
await set(MAX_SENT_LS_KEY, maxSent.toString());
await set(MAX_RECEIVED_LS_KEY, maxReceived.toString());
await set(LOGGING_FILTER_KEY, loggingLevel);
await set(RENDEZVOUS_API_LS_KEY, rendezvous);
setDirty(false);
},
[notary, proxy, maxSent, maxReceived, loggingLevel, shouldReload],
[
notary,
proxy,
maxSent,
maxReceived,
loggingLevel,
rendezvous,
shouldReload,
],
);
const onSaveAndReload = useCallback(
@@ -81,7 +100,7 @@ export default function Options(): ReactElement {
}, [advanced]);
return (
<div className="flex flex-col flex-nowrap flex-grow">
<div className="flex flex-col flex-nowrap flex-grow overflow-y-auto">
{showReloadModal && (
<Modal
className="flex flex-col items-center text-base cursor-default justify-center !w-auto mx-4 my-[50%] p-4 gap-4"
@@ -141,6 +160,8 @@ export default function Options(): ReactElement {
loggingLevel={loggingLevel}
setLoggingLevel={setLoggingLevel}
setShouldReload={setShouldReload}
rendezvous={rendezvous}
setRendezvous={setRendezvous}
/>
)}
<div className="flex flex-row flex-nowrap justify-end gap-2 p-2">
@@ -228,11 +249,13 @@ function AdvancedOptions(props: {
maxSent: number;
maxReceived: number;
loggingLevel: LoggingLevel;
rendezvous: string;
setShouldReload: (reload: boolean) => void;
setMaxSent: (value: number) => void;
setMaxReceived: (value: number) => void;
setDirty: (value: boolean) => void;
setLoggingLevel: (level: LoggingLevel) => void;
setRendezvous: (api: string) => void;
}) {
const {
maxSent,
@@ -243,6 +266,8 @@ function AdvancedOptions(props: {
setLoggingLevel,
loggingLevel,
setShouldReload,
rendezvous,
setRendezvous,
} = props;
return (
@@ -267,6 +292,15 @@ function AdvancedOptions(props: {
setDirty(true);
}}
/>
<InputField
label="Rendezvous API (for P2P)"
value={rendezvous}
type="text"
onChange={(e) => {
setRendezvous(e.target.value);
setDirty(true);
}}
/>
<div className="flex flex-col flex-nowrap py-1 px-2 gap-2">
<div className="font-semibold">Logging Level</div>
<select

View File

@@ -0,0 +1,354 @@
import React, {
ChangeEvent,
ReactElement,
useCallback,
useEffect,
useState,
} from 'react';
import Icon from '../../components/Icon';
import classNames from 'classnames';
import {
connectRendezvous,
disconnectRendezvous,
fetchP2PState,
sendPairRequest,
useClientId,
useIncomingPairingRequests,
useOutgoingPairingRequests,
cancelPairRequest,
useP2PError,
setP2PError,
acceptPairRequest,
rejectPairRequest,
usePairId,
useIncomingProofRequests,
requestProofByHash,
useOutgoingProofRequests,
acceptProofRequest,
rejectProofRequest,
cancelProofRequest,
} from '../../reducers/p2p';
import { useDispatch } from 'react-redux';
import Modal, { ModalHeader } from '../../components/Modal/Modal';
import { Plugin, PluginList } from '../../components/PluginList';
import browser from 'webextension-polyfill';
import { sha256 } from '../../utils/misc';
import { openSidePanel } from '../../entries/utils';
import { SidePanelActionTypes } from '../../entries/SidePanel/types';
export function P2PHome(): ReactElement {
const clientId = useClientId();
useEffect(() => {
fetchP2PState();
}, []);
const toggleConnection = useCallback(async () => {
if (!clientId) {
connectRendezvous();
} else {
disconnectRendezvous();
}
}, [clientId]);
return (
<div className="flex flex-col h-full cursor-default gap-2 my-2">
<div className="flex flex-row border border-slate-300 rounded mx-2">
<div className="bg-slate-200 px-2 py-1 flex-grow-0 border-r border-slate-300">
Client ID
</div>
<input
className={classNames(
'flex-grow outline-0 px-2 py-1 cursor-default font-semibold',
{
'text-slate-400 bg-slate-100': !clientId,
'text-green-500 cursor-pointer': clientId,
},
)}
onClick={(e) => {
// @ts-ignore
if (e.target.select && clientId) e.target.select();
}}
value={clientId ? clientId : '--'}
readOnly
/>
<button
className="flex-grow-0 px-2 py-1 button border-l border-slate-300"
onClick={toggleConnection}
>
{clientId ? 'Stop' : 'Start'}
</button>
</div>
<ClientStatus />
<div className="flex flex-row mx-2 flex-grow flex-shrink h-0 p-2">
<div className="text-slate-400 text-center w-full font-semibold">
No proofs history
</div>
</div>
</div>
);
}
function ClientStatus() {
const clientId = useClientId();
const error = useP2PError();
const pairId = usePairId();
const [incomingPairingRequest] = useIncomingPairingRequests();
const [outgoingPairingRequest] = useOutgoingPairingRequests();
let body = null;
if (!clientId) {
body = <ClientNotStarted />;
} else if (pairId) {
body = <Paired />;
} else if (!incomingPairingRequest && !outgoingPairingRequest) {
body = <PendingConnection />;
} else if (incomingPairingRequest) {
body = <IncomingRequest />;
} else if (outgoingPairingRequest) {
body = <OutgoingRequest />;
}
return (
<div
className={classNames(
'flex flex-col items-center justify-center border border-slate-300',
'flex-grow-0 flex-shrink rounded mx-2 bg-slate-100 py-4 gap-4',
)}
>
{body}
{error && <span className="text-xs text-red-500">{error}</span>}
</div>
);
}
function Paired() {
const pairId = usePairId();
const clientId = useClientId();
const [incomingProofRequest] = useIncomingProofRequests();
const [outgoingPluginHash] = useOutgoingProofRequests();
const [incomingPluginHash, setIncomingPluginHash] = useState('');
const [showingModal, showModal] = useState(false);
useEffect(() => {
(async () => {
if (!incomingProofRequest) {
setIncomingPluginHash('');
return;
}
const hash = await sha256(incomingProofRequest);
setIncomingPluginHash(hash);
})();
}, [incomingProofRequest]);
useEffect(() => {
showModal(false);
}, [outgoingPluginHash]);
const accept = useCallback(async () => {
if (incomingPluginHash) {
await openSidePanel();
browser.runtime.sendMessage({
type: SidePanelActionTypes.run_p2p_plugin_request,
data: {
pluginHash: incomingPluginHash,
plugin: incomingProofRequest,
},
});
acceptProofRequest(incomingPluginHash);
window.close();
}
}, [incomingPluginHash, incomingProofRequest, clientId]);
const reject = useCallback(() => {
if (incomingPluginHash) rejectProofRequest(incomingPluginHash);
}, [incomingPluginHash]);
const cancel = useCallback(() => {
if (outgoingPluginHash) cancelProofRequest(outgoingPluginHash);
}, [outgoingPluginHash]);
return (
<div className="flex flex-col items-center gap-2 px-4 w-full">
{showingModal && <PluginListModal onClose={() => showModal(false)} />}
<div>
<span>Paired with </span>
<span className="font-semibold text-blue-500">{pairId}</span>
</div>
{incomingPluginHash ? (
<>
<div className="font-semibold text-orange-500">
Your peer is requesting the following proof:
</div>
<Plugin
className="w-full bg-white !cursor-default hover:!bg-white active:!bg-white hover:!border-slate-300"
hash={incomingPluginHash}
hex={incomingProofRequest}
unremovable
/>
<div className="flex flex-row gap-2">
<button className="button" onClick={reject}>
Decline
</button>
<button className="button button--primary" onClick={accept}>
Accept
</button>
</div>
</>
) : outgoingPluginHash ? (
<>
<div className="font-semibold text-orange-500">
Sent request for following proof:
</div>
<Plugin
className="w-full bg-white !cursor-default hover:!bg-white active:!bg-white hover:!border-slate-300"
hash={outgoingPluginHash}
onClick={() => null}
unremovable
/>
<button className="button" onClick={cancel}>
Cancel
</button>
</>
) : (
<button
className="button button--primary"
onClick={() => showModal(true)}
>
Request Proof
</button>
)}
</div>
);
}
function PluginListModal({ onClose }: { onClose: () => void }) {
const onRequestProof = useCallback(async (hash: string) => {
requestProofByHash(hash);
}, []);
return (
<Modal className="mx-4" onClose={onClose}>
<ModalHeader onClose={onClose}>Choose a plugin to continue</ModalHeader>
<PluginList className="m-2" onClick={onRequestProof} unremovable />
</Modal>
);
}
function IncomingRequest() {
const [incomingRequest] = useIncomingPairingRequests();
const accept = useCallback(() => {
if (incomingRequest) acceptPairRequest(incomingRequest);
}, [incomingRequest]);
const reject = useCallback(() => {
if (incomingRequest) rejectPairRequest(incomingRequest);
}, [incomingRequest]);
return (
<div className="flex flex-col items-center gap-2">
<div>
<span className="font-semibold text-blue-500">{incomingRequest}</span>
<span> wants to pair with you.</span>
</div>
<div className="flex flex-row gap-2">
<button className="button" onClick={reject}>
Decline
</button>
<button className="button button--primary" onClick={accept}>
Accept
</button>
</div>
</div>
);
}
function OutgoingRequest() {
const [outgoingRequest] = useOutgoingPairingRequests();
const cancel = useCallback(() => {
if (outgoingRequest) {
cancelPairRequest(outgoingRequest);
}
}, [outgoingRequest]);
return (
<div className="flex flex-col items-center gap-2">
<span className="flex flex-row items-center gap-2 mx-2">
<Icon
className="animate-spin w-fit text-slate-500"
fa="fa-solid fa-spinner"
size={1}
/>
<span>
<span>Awaiting response from </span>
<span className="font-semibold text-blue-500">{outgoingRequest}</span>
<span>...</span>
</span>
</span>
<button className="button" onClick={cancel}>
Cancel
</button>
</div>
);
}
function ClientNotStarted() {
return (
<div className="flex flex-col text-slate-500 font-semibold gap-2">
Client has not started
<button className="button button--primary" onClick={connectRendezvous}>
Start Client
</button>
</div>
);
}
function PendingConnection() {
const dispatch = useDispatch();
const [target, setTarget] = useState('');
const onSend = useCallback(() => {
dispatch(setP2PError(''));
sendPairRequest(target);
}, [target]);
const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
dispatch(setP2PError(''));
setTarget(e.target.value);
}, []);
return (
<div className="flex flex-col w-full items-center gap-2">
<div className="flex flex-row justify-center gap-2">
<Icon
className="animate-spin w-fit text-slate-500"
fa="fa-solid fa-spinner"
size={1}
/>
<div className="text-slate-500 font-semibold">
Waiting for pairing request...
</div>
</div>
<div className="text-slate-500">or</div>
<div className="w-full flex flex-row px-2 items-center">
<input
className="flex-grow flex-shrink w-0 outline-0 px-2 py-1 cursor-default"
placeholder="Enter Peer ID to send pairing request"
onChange={onChange}
value={target}
/>
<button
className="button button--primary w-fit h-full"
onClick={onSend}
>
Send Pairing Request
</button>
</div>
</div>
);
}

View File

@@ -2,11 +2,13 @@ import { combineReducers } from 'redux';
import requests from './requests';
import history from './history';
import plugins from './plugins';
import p2p from './p2p';
const rootReducer = combineReducers({
requests,
history,
plugins,
p2p,
});
export type AppRootState = ReturnType<typeof rootReducer>;

321
src/reducers/p2p.ts Normal file
View File

@@ -0,0 +1,321 @@
import { useSelector } from 'react-redux';
import { AppRootState } from './index';
import deepEqual from 'fast-deep-equal';
import { Dispatch } from 'redux';
import browser from 'webextension-polyfill';
import { BackgroundActiontype } from '../entries/Background/rpc';
enum ActionType {
'/p2p/setConnected' = '/p2p/setConnected',
'/p2p/setClientId' = '/p2p/setClientId',
'/p2p/setPairing' = '/p2p/setPairing',
'/p2p/setError' = '/p2p/setError',
'/p2p/appendIncomingPairingRequest' = '/p2p/appendIncomingPairingRequest',
'/p2p/appendOutgoingPairingRequest' = '/p2p/appendOutgoingPairingRequest',
'/p2p/setIncomingPairingRequest' = '/p2p/setIncomingPairingRequest',
'/p2p/setOutgoingPairingRequest' = '/p2p/setOutgoingPairingRequest',
'/p2p/appendIncomingProofRequest' = '/p2p/appendIncomingProofRequest',
'/p2p/appendOutgoingProofRequest' = '/p2p/appendOutgoingProofRequest',
'/p2p/setIncomingProofRequest' = '/p2p/setIncomingProofRequest',
'/p2p/setOutgoingProofRequest' = '/p2p/setOutgoingProofRequest',
}
type Action<payload> = {
type: ActionType;
payload?: payload;
error?: boolean;
meta?: any;
};
type State = {
clientId: string;
pairing: string;
connected: boolean;
error: string;
incomingPairingRequests: string[];
outgoingPairingRequests: string[];
incomingProofRequests: string[];
outgoingProofRequests: string[];
};
export type RequestProofMessage = {
to: string;
from: string;
id: number;
text?: undefined;
};
const initialState: State = {
clientId: '',
pairing: '',
error: '',
connected: false,
incomingPairingRequests: [],
outgoingPairingRequests: [],
incomingProofRequests: [],
outgoingProofRequests: [],
};
export const fetchP2PState = async () => {
return browser.runtime.sendMessage({
type: BackgroundActiontype.get_p2p_state,
});
};
export const connectRendezvous = () => {
return browser.runtime.sendMessage({
type: BackgroundActiontype.connect_rendezvous,
});
};
export const disconnectRendezvous = () => {
return browser.runtime.sendMessage({
type: BackgroundActiontype.disconnect_rendezvous,
});
};
export const setConnected = (connected = false) => ({
type: ActionType['/p2p/setConnected'],
payload: connected,
});
export const setClientId = (clientId: string) => ({
type: ActionType['/p2p/setClientId'],
payload: clientId,
});
export const setPairing = (clientId: string) => ({
type: ActionType['/p2p/setPairing'],
payload: clientId,
});
export const appendIncomingPairingRequests = (peerId: string) => ({
type: ActionType['/p2p/appendIncomingPairingRequest'],
payload: peerId,
});
export const appendIncomingProofRequests = (peerId: string) => ({
type: ActionType['/p2p/appendIncomingProofRequest'],
payload: peerId,
});
export const appendOutgoingPairingRequests = (peerId: string) => ({
type: ActionType['/p2p/appendOutgoingPairingRequest'],
payload: peerId,
});
export const appendOutgoingProofRequest = (peerId: string) => ({
type: ActionType['/p2p/appendOutgoingProofRequest'],
payload: peerId,
});
export const setIncomingPairingRequest = (peerIds: string[]) => ({
type: ActionType['/p2p/setIncomingPairingRequest'],
payload: peerIds,
});
export const setOutgoingPairingRequest = (peerIds: string[]) => ({
type: ActionType['/p2p/setOutgoingPairingRequest'],
payload: peerIds,
});
export const setIncomingProofRequest = (peerIds: string[]) => ({
type: ActionType['/p2p/setIncomingProofRequest'],
payload: peerIds,
});
export const setOutgoingProofRequest = (peerIds: string[]) => ({
type: ActionType['/p2p/setOutgoingProofRequest'],
payload: peerIds,
});
export const setP2PError = (error: string) => ({
type: ActionType['/p2p/setError'],
payload: error,
});
export const requestProof = (pluginHash: string) => {
return browser.runtime.sendMessage({
type: BackgroundActiontype.request_p2p_proof,
data: pluginHash,
});
};
export const requestProofByHash = (pluginHash: string) => {
return browser.runtime.sendMessage({
type: BackgroundActiontype.request_p2p_proof_by_hash,
data: pluginHash,
});
};
export const sendPairRequest = async (targetId: string) => {
return browser.runtime.sendMessage({
type: BackgroundActiontype.send_pair_request,
data: targetId,
});
};
export const cancelPairRequest = async (targetId: string) => {
return browser.runtime.sendMessage({
type: BackgroundActiontype.cancel_pair_request,
data: targetId,
});
};
export const acceptPairRequest = async (targetId: string) => {
return browser.runtime.sendMessage({
type: BackgroundActiontype.accept_pair_request,
data: targetId,
});
};
export const rejectPairRequest = async (targetId: string) => {
return browser.runtime.sendMessage({
type: BackgroundActiontype.reject_pair_request,
data: targetId,
});
};
export const cancelProofRequest = async (plughinHash: string) => {
return browser.runtime.sendMessage({
type: BackgroundActiontype.cancel_proof_request,
data: plughinHash,
});
};
export const acceptProofRequest = async (plughinHash: string) => {
return browser.runtime.sendMessage({
type: BackgroundActiontype.accept_proof_request,
data: plughinHash,
});
};
export const rejectProofRequest = async (plughinHash: string) => {
return browser.runtime.sendMessage({
type: BackgroundActiontype.reject_proof_request,
data: plughinHash,
});
};
export default function p2p(state = initialState, action: Action<any>): State {
switch (action.type) {
case ActionType['/p2p/setConnected']:
return {
...state,
connected: action.payload,
};
case ActionType['/p2p/setClientId']:
return {
...state,
clientId: action.payload,
};
case ActionType['/p2p/setPairing']:
return {
...state,
pairing: action.payload,
};
case ActionType['/p2p/appendIncomingPairingRequest']:
return {
...state,
incomingPairingRequests: [
...new Set(state.incomingPairingRequests.concat(action.payload)),
],
};
case ActionType['/p2p/appendOutgoingPairingRequest']:
return {
...state,
outgoingPairingRequests: [
...new Set(state.outgoingPairingRequests.concat(action.payload)),
],
};
case ActionType['/p2p/setIncomingPairingRequest']:
return {
...state,
incomingPairingRequests: action.payload,
};
case ActionType['/p2p/setOutgoingPairingRequest']:
return {
...state,
outgoingPairingRequests: action.payload,
};
case ActionType['/p2p/appendIncomingProofRequest']:
return {
...state,
incomingProofRequests: [
...new Set(state.incomingProofRequests.concat(action.payload)),
],
};
case ActionType['/p2p/appendOutgoingProofRequest']:
return {
...state,
outgoingProofRequests: [
...new Set(state.outgoingProofRequests.concat(action.payload)),
],
};
case ActionType['/p2p/setIncomingProofRequest']:
return {
...state,
incomingProofRequests: action.payload,
};
case ActionType['/p2p/setOutgoingProofRequest']:
return {
...state,
outgoingProofRequests: action.payload,
};
case ActionType['/p2p/setError']:
return {
...state,
error: action.payload,
};
default:
return state;
}
}
export function useClientId() {
return useSelector((state: AppRootState) => {
return state.p2p.clientId;
}, deepEqual);
}
export function useConnected() {
return useSelector((state: AppRootState) => {
return state.p2p.connected;
}, deepEqual);
}
export function usePairId(): string {
return useSelector((state: AppRootState) => {
return state.p2p.pairing;
}, deepEqual);
}
export function useIncomingPairingRequests(): string[] {
return useSelector((state: AppRootState) => {
return state.p2p.incomingPairingRequests;
}, deepEqual);
}
export function useOutgoingPairingRequests(): string[] {
return useSelector((state: AppRootState) => {
return state.p2p.outgoingPairingRequests;
}, deepEqual);
}
export function useIncomingProofRequests(): string[] {
return useSelector((state: AppRootState) => {
return state.p2p.incomingProofRequests;
}, deepEqual);
}
export function useOutgoingProofRequests(): string[] {
return useSelector((state: AppRootState) => {
return state.p2p.outgoingProofRequests;
}, deepEqual);
}
export function useP2PError(): string {
return useSelector((state: AppRootState) => {
return state.p2p.error;
}, deepEqual);
}

View File

@@ -1,5 +1,6 @@
export const EXPLORER_API = 'https://explorer.tlsnotary.org';
export const NOTARY_API = 'https://notary.pse.dev/v0.1.0-alpha.5';
export const RENDEZVOUS_API = 'wss://explorer.tlsnotary.org:3000';
export const NOTARY_PROXY = 'wss://notary.pse.dev/proxy';
export const MAX_RECV = 16384;
export const MAX_SENT = 4096;

View File

@@ -1,5 +1,6 @@
import {
BackgroundActiontype,
handleExecP2PPluginProver,
handleExecPluginProver,
RequestLog,
} from '../entries/Background/rpc';
@@ -148,6 +149,10 @@ const VALID_HOST_FUNCS: { [name: string]: string } = {
export const makePlugin = async (
arrayBuffer: ArrayBuffer,
config?: PluginConfig,
meta?: {
p2p: boolean;
clientId: string;
},
) => {
const module = await WebAssembly.compile(arrayBuffer);
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
@@ -219,15 +224,31 @@ export const makePlugin = async (
secretResps = JSON.parse(out.string());
}
handleExecPluginProver({
type: BackgroundActiontype.execute_plugin_prover,
data: {
...params,
body: reqBody,
secretResps,
now,
},
});
if (meta?.p2p) {
handleExecP2PPluginProver({
type: BackgroundActiontype.execute_p2p_plugin_prover,
data: {
...params,
pluginHash: await sha256(
Buffer.from(arrayBuffer).toString('hex'),
),
body: reqBody,
secretResps,
now,
clientId: meta.clientId,
},
});
} else {
handleExecPluginProver({
type: BackgroundActiontype.execute_plugin_prover,
data: {
...params,
body: reqBody,
secretResps,
now,
},
});
}
})();
return context.store(`${id}`);

View File

@@ -13,7 +13,7 @@ export const HostFunctionsDescriptions: {
);
},
notarize: ({ notaryUrls, proxyUrls }) => {
const notaries = ['default notary'].concat(notaryUrls || []);
const notaries = ['default notary', 'your peer'].concat(notaryUrls || []);
const proxies = ['default proxy'].concat(proxyUrls || []);
return (

View File

@@ -38,7 +38,12 @@ export async function fetchPluginConfigByHash(
});
}
export async function runPlugin(hash: string, method: string, params?: string) {
export async function runPlugin(
hash: string,
method: string,
params?: string,
meta?: any,
) {
return browser.runtime.sendMessage({
type: BackgroundActiontype.run_plugin,
data: {
@@ -46,5 +51,6 @@ export async function runPlugin(hash: string, method: string, params?: string) {
method,
params,
},
meta,
});
}

View File

@@ -1,10 +1,12 @@
import { LoggingLevel } from 'tlsn-js';
import { RENDEZVOUS_API } from './constants';
export const NOTARY_API_LS_KEY = 'notary-api';
export const PROXY_API_LS_KEY = 'proxy-api';
export const MAX_SENT_LS_KEY = 'max-sent';
export const MAX_RECEIVED_LS_KEY = 'max-received';
export const LOGGING_FILTER_KEY = 'logging-filter-2';
export const RENDEZVOUS_API_LS_KEY = 'rendezvous-api';
export async function set(key: string, value: string) {
return chrome.storage.sync.set({ [key]: value });
@@ -36,3 +38,7 @@ export async function getProxyApi() {
export async function getLoggingFilter(): Promise<LoggingLevel> {
return await get(LOGGING_FILTER_KEY, 'Info');
}
export async function getRendezvousApi(): Promise<string> {
return await get(RENDEZVOUS_API_LS_KEY, RENDEZVOUS_API);
}