Compare commits

...

28 Commits

Author SHA1 Message Date
tsukino
b1dc26d9ef chore: commit wasm for interactive example 2024-05-28 18:05:01 +08:00
tsukino
2587f11adc fix client id 2024-04-15 16:21:53 +02:00
tsukino
a8b9449e70 it works!!! 2024-04-14 14:50:27 +02:00
tsukino
37f48c7b9e adding plugin to p2p 2024-04-14 14:06:59 +02:00
tsukino
b943adeb80 please work 2024-04-13 20:56:04 +02:00
tsukino
a0fba10d7a lets try this 2024-04-13 20:45:22 +02:00
tsukino
0904f50c1b fix: remove force client id 2024-04-13 20:07:38 +02:00
tsukino
b1bfa543a2 fix: add back lock file 2024-04-13 20:05:32 +02:00
John Shaw
87f5d7c375 chatbox fix 2024-04-13 19:59:46 +02:00
tsukino
b373e7384d fix: auto scroll messages 2024-04-13 19:59:46 +02:00
John Shaw
125f35eeb4 chatbox styling 2024-04-13 19:59:46 +02:00
John Shaw
6c18010614 wip 2024-04-13 19:59:46 +02:00
tsukino
e0686a50f4 fix: styling change 2024-04-13 19:59:46 +02:00
tsukino
6e5dee2962 fix: use correct method name 2024-04-13 19:59:46 +02:00
John Shaw
89d86659b6 wip 2024-04-13 19:59:46 +02:00
tsukino
bc73567f48 wip: move create session to p2p 2024-04-13 19:59:46 +02:00
tsukino
c43e5faef2 wip 2024-04-13 19:59:46 +02:00
John Shaw
aabb2eca65 feature: implementing client connect 2024-04-13 19:59:46 +02:00
tsukino
d36a101c4e append message to list 2024-04-13 19:59:46 +02:00
tsukino
41e5222873 wip: use pair id in chatbox 2024-04-13 19:59:46 +02:00
tsukino
eaeb2cfb7c wip: consolidate logic to ChatBox 2024-04-13 19:59:46 +02:00
tsukino
8e42ec5f50 wip: add new rpc method to p2p 2024-04-13 19:59:46 +02:00
tsukino
ae846e787c wip: add chatbox 2024-04-13 19:59:46 +02:00
tsukino
f49fe3b8a2 wip: adding verifier ux 2024-04-13 19:59:46 +02:00
John Shaw
aeeb0d2b7b refactor: p2p chatting 2024-04-13 19:59:46 +02:00
John Shaw
e2917b7f61 feature: websocket connections + chatting 2024-04-13 19:59:46 +02:00
John Shaw
473affa847 refactor: p2p component 2024-04-13 19:59:46 +02:00
John Shaw
ac2bd69f25 feature: creating the p2p ui component 2024-04-13 19:59:45 +02:00
23 changed files with 2093 additions and 133 deletions

View File

@@ -0,0 +1,247 @@
import React, { useCallback, useState } from 'react';
import {
Chat,
requestProof,
RequestProofMessage,
sendChat,
useChatMessages,
useClientId,
usePairId,
} from '../../reducers/p2p';
import { useDispatch } from 'react-redux';
import classNames from 'classnames';
import Icon from '../Icon';
import init, {
Prover,
Verifier,
} from '../../../tlsn/tlsn/tlsn-wasm/pkg/tlsn_wasm';
import { RENDEZVOUS_API } from '../../utils/constants';
import PluginModal from '../PluginModal';
import PluginDisplayBox, { PluginParams } from '../PluginDisplayBox';
import { get, PROXY_API_LS_KEY } from '../../utils/storage';
import { urlify } from '../../utils/misc';
import { useRequests } from '../../reducers/requests';
export default function ChatBox() {
const messages = useChatMessages();
const dispatch = useDispatch();
const clientId = useClientId();
const [text, setText] = useState('');
const pairId = usePairId();
const [showingPluginModal, showPluginModal] = useState(false);
const requests = useRequests();
const onSend = useCallback(() => {
if (text && pairId) {
dispatch(
sendChat({
text,
from: clientId,
to: pairId,
}),
);
}
}, [text, pairId, clientId]);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === 'Enter' && text && pairId) {
onSend();
setText('');
}
},
[text],
);
const onIProve = useCallback(
async (config: {
method: string;
uri: string;
headers: { [key: string]: string };
}) => {
await init();
const websocketProxyUrl = await get(
PROXY_API_LS_KEY,
'wss://notary.pse.dev/proxy',
);
const hostname = urlify(config.uri)?.hostname || '';
const prover = new Prover({
id: 'p2p_proof',
server_dns: hostname,
// max_sent_data: 1024,
// max_received_data: 1024,
});
await prover.setup(`${RENDEZVOUS_API}?clientId=${clientId}:proof`);
await prover.send_request(
`${websocketProxyUrl}?token=${hostname}`,
config,
);
const redact = {
sent: [],
received: [],
};
const resp = await prover.reveal(redact);
console.log(resp, redact);
},
[clientId, pairId],
);
const onNotarize = useCallback(
async (url: string) => {
const reqs = requests.filter((req) => {
return req?.url?.includes(url);
});
const req = reqs[0];
const hostname = urlify(req.url)?.hostname || '';
const headers: { [k: string]: string } = req.requestHeaders.reduce(
(acc: any, h) => {
acc[h.name] = h.value;
return acc;
},
{ Host: hostname },
);
headers['Accept-Encoding'] = 'identity';
headers['Connection'] = 'close';
onIProve({
method: req.method,
uri: req.url,
headers: headers,
});
},
[onIProve],
);
const onRequestProof = useCallback(
async (plugin: PluginParams) => {
await init();
const verifier = new Verifier({
id: 'p2p_proof',
// max_sent_data: 1024,
// max_received_data: 1024,
});
await verifier.connect(`${RENDEZVOUS_API}?clientId=${clientId}:proof`);
dispatch(
requestProof({
plugin,
from: clientId,
to: pairId,
}),
);
showPluginModal(false);
const res = await verifier.verify();
console.log(res);
dispatch(
sendChat({
text: JSON.stringify(res),
from: clientId,
to: pairId,
}),
);
},
[clientId, pairId],
);
const isClient = (msg: any) => {
return msg.from === clientId;
};
return (
<div className="flex flex-col flex-nowrap flex-grow gap-1 p-2 flex-shrink h-0 ">
{showingPluginModal && (
<PluginModal
onClose={() => showPluginModal(false)}
onSelect={onRequestProof}
/>
)}
<div className="flex flex-row gap-1 font-semibold text-xs align-center">
<div>Client ID:</div>
{clientId ? (
<div className="text-green-500">{clientId}</div>
) : (
<Icon
className="animate-spin text-gray-500"
fa="fa-solid fa-spinner"
size={1}
/>
)}
</div>
<div className="flex flex-row gap-1 font-semibold text-xs align-center">
<div>Peer ID:</div>
{pairId ? (
<div className="text-red-500">{pairId}</div>
) : (
<div className="flex flex-row gap-1">
<span className="text-slate-500">Waiting for Peer</span>
<Icon
className="animate-spin text-slate-500 w-fit"
fa="fa-solid fa-spinner"
size={1}
/>
</div>
)}
</div>
<div className="flex flex-col flex-grow flex-shrink h-0 gap-1">
<div className="flex flex-col border gap-1 border-slate-200 flex-grow overflow-y-auto p-2">
{messages.map((msg: Chat | RequestProofMessage) => {
return (
<div
className={classNames(`p-2 max-w-[50%] break-all`, {
'mr-auto bg-blue-600 rounded-t-lg rounded-br-lg':
isClient(msg),
'ml-auto bg-slate-300 rounded-b-lg rounded-tl-lg':
!isClient(msg),
})}
>
{typeof msg.text === 'string' && (
<div
className={`${isClient(msg) ? 'text-white' : 'text-black'}`}
>
{msg.text}
</div>
)}
{typeof msg.plugin !== 'undefined' && (
<div
className={`${isClient(msg) ? 'text-white' : 'text-black'}`}
>
<PluginDisplayBox
{...msg.plugin}
hideAction={isClient(msg)}
onNotarize={() => onNotarize(msg.plugin.url)}
/>
</div>
)}
</div>
);
})}
</div>
<div className="flex flex-row w-full gap-1">
<input
className="input border border-slate-200 focus:border-slate-400 flex-grow p-2"
onChange={(e) => setText(e.target.value)}
onKeyDown={handleKeyDown}
value={text}
autoFocus
/>
<button
className={classNames('button', {
'button--primary': !!pairId,
})}
// disabled={!pairId}
onClick={() => showPluginModal(true)}
>
Request Proof
</button>
<button
className={classNames('button', {
'button--primary': !!text && !!pairId,
})}
disabled={!text || !pairId}
onClick={onSend}
>
Send
</button>
</div>
</div>
</div>
);
}

View File

@@ -50,7 +50,7 @@ export function ModalHeader(props: HeaderProps): ReactElement {
<div
className={classNames(
'flex flex-row items-center justify-center',
'p-2 rounded-full opacity-50',
'rounded-full opacity-50',
'hover:opacity-100 text-black',
)}
>

View File

@@ -0,0 +1,157 @@
import { replayRequest, urlify } from '../../utils/misc';
import { get, NOTARY_API_LS_KEY, PROXY_API_LS_KEY } from '../../utils/storage';
import { notarizeRequest, useRequests } from '../../reducers/requests';
import React, { ReactElement, useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router';
import classNames from 'classnames';
export type PluginParams = {
method: string;
url: string;
targetUrl: string;
type: string;
title: string;
description: string;
responseSelector: string;
valueTransform: string;
};
type Props = PluginParams & {
className?: string;
hideAction?: boolean;
onClick?: () => void;
onNotarize?: () => Promise<void>;
};
export default function PluginDisplayBox(props: Props): ReactElement {
const {
method,
type,
title,
description,
responseSelector,
valueTransform,
targetUrl,
url,
className = '',
hideAction = false,
onClick,
} = props;
const requests = useRequests();
const dispatch = useDispatch();
const navigate = useNavigate();
const reqs = requests.filter((req) => {
return req?.url?.includes(url);
});
const bmHost = urlify(targetUrl)?.host;
const isReady = !!reqs.length;
const onNotarize = useCallback(async () => {
if (!isReady) return;
const req = reqs[0];
const res = await replayRequest(req);
const secretHeaders = req.requestHeaders
.map((h) => {
return `${h.name.toLowerCase()}: ${h.value || ''}` || '';
})
.filter((d) => !!d);
const selectedValue = res.match(new RegExp(responseSelector, 'g'));
if (selectedValue) {
const revealed = valueTransform.replace('%s', selectedValue[0]);
const selectionStart = res.indexOf(revealed);
const selectionEnd = selectionStart + revealed.length - 1;
const secretResps = [
res.substring(0, selectionStart),
res.substring(selectionEnd, res.length),
].filter((d) => !!d);
const hostname = urlify(req.url)?.hostname;
const notaryUrl = await get(
NOTARY_API_LS_KEY,
'https://notary.pse.dev/v0.1.0-alpha.5',
);
const websocketProxyUrl = await get(
PROXY_API_LS_KEY,
'wss://notary.pse.dev/proxy',
);
const headers: { [k: string]: string } = req.requestHeaders.reduce(
(acc: any, h) => {
acc[h.name] = h.value;
return acc;
},
{ Host: hostname },
);
//TODO: for some reason, these needs to be override to work
headers['Accept-Encoding'] = 'identity';
headers['Connection'] = 'close';
dispatch(
// @ts-ignore
notarizeRequest({
url: req.url,
method: req.method,
headers: headers,
body: req.requestBody,
maxTranscriptSize: 16384,
notaryUrl,
websocketProxyUrl,
secretHeaders,
secretResps,
}),
);
navigate(`/history`);
}
}, [
isReady,
reqs[0],
method,
type,
title,
description,
responseSelector,
valueTransform,
targetUrl,
url,
]);
return (
<div
className={classNames('flex flex-col flex-nowrap p-2 gap-1', className)}
onClick={onClick}
>
<div className="flex flex-row items-center text-xs">
<div className="bg-slate-200 text-slate-400 px-1 py-0.5 rounded-sm">
{method}
</div>
<div className="text-slate-400 px-2 py-1 rounded-md">{type}</div>
</div>
<div className="font-bold">{title}</div>
<div className="italic">{description}</div>
{isReady && !hideAction && (
<button
className="button button--primary w-fit self-end mt-2"
onClick={props.onNotarize || onNotarize}
>
Notarize
</button>
)}
{!isReady && !hideAction && (
<button
className="button w-fit self-end mt-2"
onClick={() => chrome.tabs.update({ url: targetUrl })}
>
{`Go to ${bmHost}`}
</button>
)}
</div>
);
}

View File

@@ -0,0 +1,5 @@
.plugin-modal {
height: calc(100% - 2rem);
margin: 1rem;
width: calc(100% - 2rem) !important;
}

View File

@@ -0,0 +1,36 @@
import React, { ReactElement, useCallback } from 'react';
import bookmarks from '../../../utils/bookmark/bookmarks.json';
import Modal, { ModalContent, ModalHeader } from '../Modal/Modal';
import PluginDisplayBox, { type PluginParams } from '../PluginDisplayBox';
import './index.scss';
type Props = {
onClose: () => void;
onSelect?: (plugin: PluginParams) => void;
};
export default function PluginModal(props: Props): ReactElement {
const onClick = useCallback(
(plugin: PluginParams) => {
if (props.onSelect) props.onSelect(plugin);
},
[props.onSelect],
);
return (
<Modal className="plugin-modal" onClose={props.onClose}>
<ModalHeader onClose={props.onClose}>Choose a plugin</ModalHeader>
<ModalContent>
{bookmarks.map((bookmark, i) => (
<PluginDisplayBox
key={i}
className="border-b border-slate-100 hover:bg-slate-50 cursor-pointer"
onClick={() => onClick(bookmark)}
{...bookmark}
hideAction
/>
))}
</ModalContent>
</Modal>
);
}

View File

@@ -18,7 +18,10 @@ import Notarize from '../../pages/Notarize';
import ProofViewer from '../../pages/ProofViewer';
import History from '../../pages/History';
import ProofUploader from '../../pages/ProofUploader';
import Connect from '../../pages/Connect';
import browser from 'webextension-polyfill';
import P2P from '../../pages/P2P';
import CreateSession from '../../pages/CreateSession';
const Popup = () => {
const dispatch = useDispatch();
@@ -79,6 +82,9 @@ const Popup = () => {
<Route path="/custom/*" element={<RequestBuilder />} />
<Route path="/options" element={<Options />} />
<Route path="/home" element={<Home />} />
<Route path="/connect-session" element={<Connect />} />
<Route path="/create-session" element={<CreateSession />} />
<Route path="/p2p" element={<P2P />} />
<Route path="*" element={<Navigate to="/home" />} />
</Routes>
</div>

View File

@@ -0,0 +1,64 @@
import React, { useEffect, useState, useCallback } from 'react';
import { sendPairRequest } from '../../reducers/p2p';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router';
import { usePairId } from '../../reducers/p2p';
import Icon from '../../components/Icon';
export default function Connect() {
const dispatch = useDispatch();
const [peerId, setPeerId] = useState('');
const [loading, setLoading] = useState(false);
const pairId = usePairId();
const navigate = useNavigate();
useEffect(() => {
if (pairId && loading) {
console.log('Connected to peer', pairId);
setLoading(false);
navigate('/create-session');
}
}, [pairId]);
const connect = useCallback(() => {
if (peerId) {
console.log('Connecting to peer', peerId);
dispatch(sendPairRequest(peerId));
setLoading(true);
} else {
console.log('No peer ID provided');
}
}, [peerId]);
return (
<div className="flex flex-col justify-center items-center bg-slate-200 p-4 rounded border-slate-400 m-4 gap-2">
<h1 className="text-base font-semibold">Enter peer ID to connect to</h1>
<input
className="input border border-slate-200 focus:border-slate-400 w-full"
type="text"
value={peerId}
onChange={(e) => setPeerId(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') connect();
}}
autoFocus
/>
<button
className="button button--primary"
disabled={!peerId || loading}
onClick={connect}
>
{loading ? (
<Icon
className="animate-spin text-white"
fa="fa-solid fa-spinner"
size={1}
/>
) : (
'Connect'
)}
</button>
</div>
);
}

View File

@@ -0,0 +1,7 @@
import type {} from 'redux-thunk/extend-redux';
import React, { ReactElement, useEffect } from 'react';
import ChatBox from '../../components/ChatBox';
export default function CreateSession(): ReactElement {
return <ChatBox />;
}

View File

@@ -1,29 +1,17 @@
import React, {
MouseEventHandler,
ReactElement,
ReactNode,
useCallback,
useState,
} from 'react';
import React, { MouseEventHandler, ReactElement, ReactNode } from 'react';
import Icon from '../../components/Icon';
import classNames from 'classnames';
import { useNavigate } from 'react-router';
import {
notarizeRequest,
useActiveTabUrl,
useRequests,
} from '../../reducers/requests';
import { useActiveTabUrl, useRequests } from '../../reducers/requests';
import { Link } from 'react-router-dom';
import bookmarks from '../../../utils/bookmark/bookmarks.json';
import { replayRequest, urlify } from '../../utils/misc';
import { useDispatch } from 'react-redux';
import { get, NOTARY_API_LS_KEY, PROXY_API_LS_KEY } from '../../utils/storage';
import PluginDisplayBox from '../../components/PluginDisplayBox';
export default function Home(): ReactElement {
const requests = useRequests();
const url = useActiveTabUrl();
const navigate = useNavigate();
const dispatch = useDispatch();
return (
<div className="flex flex-col gap-4 py-4 overflow-y-auto">
@@ -32,14 +20,11 @@ export default function Home(): ReactElement {
<span>Requests</span>
<span>{`(${requests.length})`}</span>
</NavButton>
<NavButton
fa="fa-solid fa-magnifying-glass"
onClick={() => navigate('/custom')}
>
<NavButton fa="fa-solid fa-hammer" onClick={() => navigate('/custom')}>
Custom
</NavButton>
<NavButton
fa="fa-solid fa-magnifying-glass"
fa="fa-solid fa-certificate"
onClick={() => navigate('/verify')}
>
Verify
@@ -50,6 +35,12 @@ export default function Home(): ReactElement {
<NavButton fa="fa-solid fa-gear" onClick={() => navigate('/options')}>
Options
</NavButton>
<NavButton
fa="fa-solid fa-people-arrows"
onClick={() => navigate('/p2p')}
>
P2P
</NavButton>
</div>
{!bookmarks.length && (
<div className="flex flex-col flex-nowrap">
@@ -62,115 +53,9 @@ export default function Home(): ReactElement {
</div>
)}
<div className="flex flex-col px-4 gap-4">
{bookmarks.map((bm, i) => {
try {
const reqs = requests.filter((req) => {
return req?.url?.includes(bm.url);
});
const bmHost = urlify(bm.targetUrl)?.host;
const isReady = !!reqs.length;
return (
<div
key={i}
className="flex flex-col flex-nowrap border rounded-md p-2 gap-1 hover:bg-slate-50 cursor-pointer"
>
<div className="flex flex-row items-center text-xs">
<div className="bg-slate-200 text-slate-400 px-1 py-0.5 rounded-sm">
{bm.method}
</div>
<div className="text-slate-400 px-2 py-1 rounded-md">
{bm.type}
</div>
</div>
<div className="font-bold">{bm.title}</div>
<div className="italic">{bm.description}</div>
{isReady && (
<button
className="button button--primary w-fit self-end mt-2"
onClick={async () => {
if (!isReady) return;
const req = reqs[0];
const res = await replayRequest(req);
const secretHeaders = req.requestHeaders
.map((h) => {
return (
`${h.name.toLowerCase()}: ${h.value || ''}` || ''
);
})
.filter((d) => !!d);
const selectedValue = res.match(
new RegExp(bm.responseSelector, 'g'),
);
if (selectedValue) {
const revealed = bm.valueTransform.replace(
'%s',
selectedValue[0],
);
const selectionStart = res.indexOf(revealed);
const selectionEnd =
selectionStart + revealed.length - 1;
const secretResps = [
res.substring(0, selectionStart),
res.substring(selectionEnd, res.length),
].filter((d) => !!d);
const hostname = urlify(req.url)?.hostname;
const notaryUrl = await get(NOTARY_API_LS_KEY);
const websocketProxyUrl = await get(PROXY_API_LS_KEY);
const headers: { [k: string]: string } =
req.requestHeaders.reduce(
(acc: any, h) => {
acc[h.name] = h.value;
return acc;
},
{ Host: hostname },
);
//TODO: for some reason, these needs to be override to work
headers['Accept-Encoding'] = 'identity';
headers['Connection'] = 'close';
dispatch(
// @ts-ignore
notarizeRequest({
url: req.url,
method: req.method,
headers: headers,
body: req.requestBody,
maxTranscriptSize: 16384,
notaryUrl,
websocketProxyUrl,
secretHeaders,
secretResps,
}),
);
navigate(`/history`);
}
}}
>
Notarize
</button>
)}
{!isReady && (
<button
className="button w-fit self-end mt-2"
onClick={() => chrome.tabs.update({ url: bm.targetUrl })}
>
{`Go to ${bmHost}`}
</button>
)}
</div>
);
} catch (e) {
return null;
}
})}
{bookmarks.map((bm, i) => (
<PluginDisplayBox className="border rounded-md" key={i} {...bm} />
))}
</div>
</div>
);

View File

@@ -9,6 +9,9 @@ import {
export default function Options(): ReactElement {
const [notary, setNotary] = useState('https://notary.pse.dev/v0.1.0-alpha.5');
const [proxy, setProxy] = useState('wss://notary.pse.dev/proxy');
const [rendezvous, setRendezvous] = useState(
'wss://notary.pse.dev/rendezvous',
);
const [dirty, setDirty] = useState(false);
useEffect(() => {
@@ -55,6 +58,19 @@ export default function Options(): ReactElement {
value={proxy}
/>
</div>
<div className="flex flex-col flex-nowrap py-1 px-2 gap-2">
<div className="font-semibold">Rendezvous API</div>
<input
type="text"
className="input border"
placeholder="wss://notary.pse.dev/rendezvous"
onChange={(e) => {
setRendezvous(e.target.value);
setDirty(true);
}}
value={rendezvous}
/>
</div>
<div className="flex flex-row flex-nowrap justify-end gap-2 p-2">
<button
className="button !bg-primary/[0.9] hover:bg-primary/[0.8] active:bg-primary !text-white"

30
src/pages/P2P/index.tsx Normal file
View File

@@ -0,0 +1,30 @@
import React, { ReactElement, useEffect } from 'react';
import { useNavigate } from 'react-router';
import { useDispatch } from 'react-redux';
import { connectSession } from '../../reducers/p2p';
export default function P2P(): ReactElement {
const navigate = useNavigate();
const dispatch = useDispatch();
useEffect(() => {
dispatch(connectSession());
}, []);
return (
<div className="flex flex-col flex-nowrap flex-grow gap-2 m-4">
<button
className="button button--primary mx-20 brea"
onClick={() => navigate('/create-session')}
>
Create Session
</button>
<button
className="button button--primary mx-20"
onClick={() => navigate('/connect-session')}
>
Connect to Session
</button>
</div>
);
}

View File

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

316
src/reducers/p2p.tsx Normal file
View File

@@ -0,0 +1,316 @@
import { useSelector } from 'react-redux';
import { AppRootState } from './index';
import deepEqual from 'fast-deep-equal';
import { safeParseJSON } from '../utils/misc';
import { Dispatch } from 'redux';
import { RENDEZVOUS_API } from '../utils/constants';
import { PluginParams } from '../components/PluginDisplayBox';
enum ActionType {
'/p2p/createSession' = '/p2p/createSession',
'/p2p/setConnected' = '/p2p/setConnected',
'/p2p/setClientId' = '/p2p/setClientId',
'/p2p/setSocket' = '/p2p/setSocket',
'/p2p/appendMessage' = '/p2p/appendMessage',
'/p2p/setMessages' = '/p2p/setMessages',
'/p2p/setPairing' = '/p2p/setPairing',
}
type Action<payload> = {
type: ActionType;
payload?: payload;
error?: boolean;
meta?: any;
};
type State = {
clientId: string;
pairing: string;
socket: WebSocket | null;
connected: boolean;
messages: (Chat | RequestProofMessage)[];
};
export type Chat = {
to: string;
from: string;
text: string;
id: number;
plugin?: undefined;
};
export type RequestProofMessage = {
to: string;
from: string;
plugin: PluginParams;
id: number;
text?: undefined;
};
const initialState: State = {
clientId: '',
pairing: '',
socket: null,
connected: false,
messages: [],
};
export const connectSession =
() => async (dispatch: Dispatch, getState: () => AppRootState) => {
const { p2p } = getState();
if (p2p.socket) return;
const socket = new WebSocket(RENDEZVOUS_API);
socket.onopen = () => {
console.log('Connected to websocket');
dispatch(setConnected(true));
dispatch(setSocket(socket));
};
socket.onmessage = async (event) => {
const message: any = safeParseJSON(await event.data.text());
switch (message.method) {
case 'client_connect': {
const { clientId } = message.params;
dispatch(setClientId(clientId));
break;
}
case 'chat': {
const { to, from, text, id } = message.params;
dispatch(
appendMessage({
to,
from,
text,
id,
}),
);
break;
}
case 'request_proof': {
const { to, from, plugin, id } = message.params;
dispatch(
appendMessage({
to,
from,
plugin,
id,
}),
);
break;
}
case 'pair_request': {
const { from } = message.params;
dispatch(confirmPairRequest(from));
break;
}
case 'pair_request_success': {
const { from } = message.params;
dispatch(setPairing(from));
break;
}
default:
console.warn(`Unknown message type "${message.method}"`);
break;
}
};
socket.onerror = () => {
console.error('Error connecting to websocket');
dispatch(setConnected(false));
};
};
export const setConnected = (connected = false) => ({
type: ActionType['/p2p/setConnected'],
payload: connected,
});
export const setClientId = (clientId: string) => ({
type: ActionType['/p2p/setClientId'],
payload: clientId,
});
export const setSocket = (socket: WebSocket) => ({
type: ActionType['/p2p/setSocket'],
payload: socket,
});
export const setMessages = (messages: Chat[]) => ({
type: ActionType['/p2p/setMessages'],
payload: messages,
});
export const appendMessage = (message: Chat | RequestProofMessage) => ({
type: ActionType['/p2p/appendMessage'],
payload: message,
});
export const setPairing = (clientId: string) => ({
type: ActionType['/p2p/setPairing'],
payload: clientId,
});
let id = 1;
export const sendChat =
(message: Omit<Chat, 'id'>) =>
async (dispatch: Dispatch, getState: () => AppRootState) => {
const {
p2p: { socket },
} = getState();
const reqId = id++;
const params = {
...message,
id: reqId,
};
if (socket) {
socket.send(
bufferify({
method: 'chat',
params,
}),
);
dispatch(appendMessage(params));
}
};
export const requestProof =
(message: Omit<RequestProofMessage, 'id'>) =>
async (dispatch: Dispatch, getState: () => AppRootState) => {
const {
p2p: { socket },
} = getState();
const reqId = id++;
const params = {
...message,
id: reqId,
};
if (socket) {
socket.send(
bufferify({
method: 'request_proof',
params,
}),
);
dispatch(appendMessage(params));
}
};
export const sendPairRequest =
(target: string) =>
async (dispatch: Dispatch, getState: () => AppRootState) => {
const {
p2p: { socket, clientId },
} = getState();
const reqId = id++;
if (socket && clientId) {
socket.send(
bufferify({
method: 'pair_request',
params: {
from: clientId,
to: target,
id: reqId,
},
}),
);
}
};
export const confirmPairRequest =
(target: string) => (dispatch: Dispatch, getState: () => AppRootState) => {
const {
p2p: { socket, clientId },
} = getState();
const reqId = id++;
if (socket && clientId) {
socket.send(
bufferify({
method: 'pair_request_success',
params: {
from: clientId,
to: target,
id: reqId,
},
}),
);
dispatch(setPairing(target));
}
};
export default function p2p(state = initialState, action: Action<any>) {
switch (action.type) {
case ActionType['/p2p/setConnected']:
return {
...state,
connected: action.payload,
};
case ActionType['/p2p/setClientId']:
return {
...state,
clientId: action.payload,
};
case ActionType['/p2p/setSocket']:
return {
...state,
socket: action.payload,
};
case ActionType['/p2p/setMessages']:
return {
...state,
messages: action.payload,
};
case ActionType['/p2p/setPairing']:
return {
...state,
pairing: action.payload,
};
case ActionType['/p2p/appendMessage']:
return {
...state,
messages: state.messages.concat(action.payload),
};
default:
return state;
}
}
export function useClientId() {
return useSelector((state: AppRootState) => {
return state.p2p.clientId;
}, deepEqual);
}
export function useSocket() {
return useSelector((state: AppRootState) => {
return state.p2p.socket;
}, deepEqual);
}
export function useConnected() {
return useSelector((state: AppRootState) => {
return state.p2p.connected;
}, deepEqual);
}
export function useChatMessages(): (Chat | RequestProofMessage)[] {
return useSelector((state: AppRootState) => {
return state.p2p.messages;
}, deepEqual);
}
export function usePairId(): string {
return useSelector((state: AppRootState) => {
return state.p2p.pairing;
}, deepEqual);
}
function bufferify(data: any): Buffer {
return Buffer.from(JSON.stringify(data));
}

View File

@@ -1 +1,2 @@
export const EXPLORER_API = 'http://localhost:3000';
export const RENDEZVOUS_API = 'ws://localhost:3000';

View File

@@ -108,3 +108,11 @@ export async function replayRequest(req: RequestLog): Promise<string> {
return resp.blob().then((blob) => blob.text());
}
}
export function safeParseJSON(data: string) {
try {
return JSON.parse(data);
} catch (e) {
return null;
}
}

1
tlsn/tlsn/tlsn-wasm/pkg/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
#*

View File

@@ -0,0 +1,6 @@
onmessage = function (ev) {
let [ia, index, value] = ev.data;
ia = new Int32Array(ia.buffer);
let result = Atomics.wait(ia, index, value);
postMessage(result);
};

107
tlsn/tlsn/tlsn-wasm/pkg/tlsn_wasm.d.ts vendored Normal file
View File

@@ -0,0 +1,107 @@
/* tslint:disable */
/* eslint-disable */
/**
*/
export function setup_tracing_web(): void;
/**
*/
export class Prover {
free(): void;
/**
* @param {any} config
*/
constructor(config: any);
/**
* Set up the prover.
*
* This performs all MPC setup prior to establishing the connection to the
* application server.
* @param {string} verifier_url
* @returns {Promise<void>}
*/
setup(verifier_url: string): Promise<void>;
/**
* Send the HTTP request to the server.
* @param {string} ws_proxy_url
* @param {any} request
* @returns {Promise<any>}
*/
send_request(ws_proxy_url: string, request: any): Promise<any>;
/**
* Reveals data to the verifier, redacting the specified substrings.
* @param {any} redact
* @returns {Promise<void>}
*/
reveal(redact: any): Promise<void>;
}
/**
*/
export class Verifier {
free(): void;
/**
* @param {any} config
*/
constructor(config: any);
/**
* @param {string} prover_url
* @returns {Promise<void>}
*/
connect(prover_url: string): Promise<void>;
/**
* @returns {Promise<any>}
*/
verify(): Promise<any>;
}
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly __wbg_verifier_free: (a: number) => void;
readonly verifier_new: (a: number, b: number) => void;
readonly verifier_connect: (a: number, b: number, c: number) => number;
readonly verifier_verify: (a: number) => number;
readonly setup_tracing_web: () => void;
readonly __wbg_prover_free: (a: number) => void;
readonly prover_new: (a: number, b: number) => void;
readonly prover_setup: (a: number, b: number, c: number) => number;
readonly prover_send_request: (a: number, b: number, c: number, d: number) => number;
readonly prover_reveal: (a: number, b: number) => number;
readonly ring_core_0_17_8_bn_mul_mont: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
readonly memory: WebAssembly.Memory;
readonly __wbindgen_malloc: (a: number, b: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
readonly __wbindgen_export_3: WebAssembly.Table;
readonly wasm_bindgen__convert__closures__invoke0_mut__h866fab451526a01b: (a: number, b: number) => void;
readonly wasm_bindgen__convert__closures__invoke1_mut__hb5831b01f9504b58: (a: number, b: number, c: number) => void;
readonly wasm_bindgen__convert__closures__invoke1_mut__ha34040edb27f2a7b: (a: number, b: number, c: number) => void;
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h04a8fc7e4fd75a48: (a: number, b: number, c: number) => void;
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
readonly __wbindgen_exn_store: (a: number) => void;
readonly wasm_bindgen__convert__closures__invoke2_mut__h921fd5652408deb6: (a: number, b: number, c: number, d: number) => void;
readonly __wbindgen_thread_destroy: (a?: number, b?: number) => void;
readonly __wbindgen_start: () => void;
}
export type SyncInitInput = BufferSource | WebAssembly.Module;
/**
* Instantiates the given `module`, which can either be bytes or
* a precompiled `WebAssembly.Module`.
*
* @param {SyncInitInput} module
* @param {WebAssembly.Memory} maybe_memory
*
* @returns {InitOutput}
*/
export function initSync(module: SyncInitInput, maybe_memory?: WebAssembly.Memory): InitOutput;
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {InitInput | Promise<InitInput>} module_or_path
* @param {WebAssembly.Memory} maybe_memory
*
* @returns {Promise<InitOutput>}
*/
export default function __wbg_init (module_or_path?: InitInput | Promise<InitInput>, maybe_memory?: WebAssembly.Memory): Promise<InitOutput>;

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,27 @@
/* tslint:disable */
/* eslint-disable */
export function __wbg_verifier_free(a: number): void;
export function verifier_new(a: number, b: number): void;
export function verifier_connect(a: number, b: number, c: number): number;
export function verifier_verify(a: number): number;
export function setup_tracing_web(): void;
export function __wbg_prover_free(a: number): void;
export function prover_new(a: number, b: number): void;
export function prover_setup(a: number, b: number, c: number): number;
export function prover_send_request(a: number, b: number, c: number, d: number): number;
export function prover_reveal(a: number, b: number): number;
export function ring_core_0_17_8_bn_mul_mont(a: number, b: number, c: number, d: number, e: number, f: number): void;
export const memory: WebAssembly.Memory;
export function __wbindgen_malloc(a: number, b: number): number;
export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number;
export const __wbindgen_export_3: WebAssembly.Table;
export function wasm_bindgen__convert__closures__invoke0_mut__h866fab451526a01b(a: number, b: number): void;
export function wasm_bindgen__convert__closures__invoke1_mut__hb5831b01f9504b58(a: number, b: number, c: number): void;
export function wasm_bindgen__convert__closures__invoke1_mut__ha34040edb27f2a7b(a: number, b: number, c: number): void;
export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h04a8fc7e4fd75a48(a: number, b: number, c: number): void;
export function __wbindgen_add_to_stack_pointer(a: number): number;
export function __wbindgen_free(a: number, b: number, c: number): void;
export function __wbindgen_exn_store(a: number): void;
export function wasm_bindgen__convert__closures__invoke2_mut__h921fd5652408deb6(a: number, b: number, c: number, d: number): void;
export function __wbindgen_thread_destroy(a: number, b: number): void;
export function __wbindgen_start(): void;

View File

@@ -1,7 +1,7 @@
[
{
"url": "https://api.twitter.com/1.1/account/settings.json",
"targetUrl": "https://www.twitter.com",
"url": "https://api.x.com/1.1/account/settings.json",
"targetUrl": "https://www.x.com",
"method": "GET",
"type": "xmlhttprequest",
"title": "Twitter Profile",

View File

@@ -1,5 +1,5 @@
// tiny wrapper with default env vars
module.exports = {
NODE_ENV: process.env.NODE_ENV || 'development',
PORT: process.env.PORT || 3000,
PORT: process.env.PORT || 3001,
};