mirror of
https://github.com/tlsnotary/tlsn-extension.git
synced 2026-01-09 13:08:04 -05:00
Compare commits
20 Commits
contextMen
...
hackathon
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da2ec501f3 | ||
|
|
871c765963 | ||
|
|
31ca65ccd2 | ||
|
|
8ad4c0b4f8 | ||
|
|
3d8aec4083 | ||
|
|
e63d21e394 | ||
|
|
fca19846b4 | ||
|
|
12e6806fa8 | ||
|
|
b6d801a45e | ||
|
|
b8f67d0adf | ||
|
|
b3009023b3 | ||
|
|
02398eb253 | ||
|
|
f2e298943f | ||
|
|
743b073fbe | ||
|
|
78fc4bc452 | ||
|
|
c0fb13d5c8 | ||
|
|
6ffe529144 | ||
|
|
82d5b77969 | ||
|
|
f58431cfb2 | ||
|
|
86f21ac8a1 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@ bin/
|
||||
build
|
||||
tlsn/
|
||||
zip
|
||||
yarn.lock
|
||||
|
||||
114
src/components/ChatBox/index.tsx
Normal file
114
src/components/ChatBox/index.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
sendChat,
|
||||
useChatMessages,
|
||||
useClientId,
|
||||
usePairId,
|
||||
} from '../../reducers/p2p';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import Icon from '../Icon';
|
||||
|
||||
export default function ChatBox() {
|
||||
const messages = useChatMessages();
|
||||
const dispatch = useDispatch();
|
||||
const clientId = useClientId();
|
||||
const [text, setText] = useState('');
|
||||
const pairId = usePairId();
|
||||
|
||||
const onSend = useCallback(() => {
|
||||
if (text && pairId) {
|
||||
dispatch(
|
||||
sendChat({
|
||||
text,
|
||||
from: clientId,
|
||||
to: pairId,
|
||||
}),
|
||||
);
|
||||
|
||||
setText('');
|
||||
|
||||
console.log('after sending');
|
||||
}
|
||||
}, [text, pairId]);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && text && pairId) {
|
||||
onSend();
|
||||
setText('');
|
||||
}
|
||||
},
|
||||
[text],
|
||||
);
|
||||
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 ">
|
||||
<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">
|
||||
{messages.map((msg) => {
|
||||
return (
|
||||
<div
|
||||
className={`rounded-lg p-2 max-w-[50%] break-all ${isClient(msg) ? 'mr-auto bg-blue-600' : 'ml-auto bg-slate-300'}`}
|
||||
>
|
||||
<div
|
||||
className={`${isClient(msg) ? 'text-white' : 'text-black'}`}
|
||||
>
|
||||
{msg.text}
|
||||
</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': !!text && !!pairId,
|
||||
})}
|
||||
disabled={!text || !pairId}
|
||||
onClick={onSend}
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
64
src/pages/Connect/index.tsx
Normal file
64
src/pages/Connect/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
7
src/pages/CreateSession/index.tsx
Normal file
7
src/pages/CreateSession/index.tsx
Normal 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 />;
|
||||
}
|
||||
@@ -50,6 +50,9 @@ export default function Home(): ReactElement {
|
||||
<NavButton fa="fa-solid fa-gear" onClick={() => navigate('/options')}>
|
||||
Options
|
||||
</NavButton>
|
||||
<NavButton fa="fa-solid fa-wifi" onClick={() => navigate('/p2p')}>
|
||||
Prove to peer
|
||||
</NavButton>
|
||||
</div>
|
||||
{!bookmarks.length && (
|
||||
<div className="flex flex-col flex-nowrap">
|
||||
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
export default function Options(): ReactElement {
|
||||
const [notary, setNotary] = useState('https://notary.pse.dev');
|
||||
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
30
src/pages/P2P/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>;
|
||||
|
||||
264
src/reducers/p2p.tsx
Normal file
264
src/reducers/p2p.tsx
Normal file
@@ -0,0 +1,264 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppRootState } from './index';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { safeParseJSON } from '../utils/misc';
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
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[];
|
||||
};
|
||||
|
||||
type Chat = {
|
||||
to: string;
|
||||
from: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
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('ws://0.tcp.ngrok.io:14339?clientId=hi');
|
||||
|
||||
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());
|
||||
|
||||
console.log(message);
|
||||
switch (message.method) {
|
||||
case 'client_connect': {
|
||||
const { clientId } = message.params;
|
||||
dispatch(setClientId(clientId));
|
||||
break;
|
||||
}
|
||||
case 'chat': {
|
||||
const { to, from, text } = message.params;
|
||||
dispatch(
|
||||
appendMessage({
|
||||
to,
|
||||
from,
|
||||
text,
|
||||
}),
|
||||
);
|
||||
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) => ({
|
||||
type: ActionType['/p2p/appendMessage'],
|
||||
payload: message,
|
||||
});
|
||||
|
||||
export const setPairing = (clientId: string) => ({
|
||||
type: ActionType['/p2p/setPairing'],
|
||||
payload: clientId,
|
||||
});
|
||||
|
||||
let id = 1;
|
||||
export const sendChat =
|
||||
(message: Chat) =>
|
||||
async (dispatch: Dispatch, getState: () => AppRootState) => {
|
||||
const {
|
||||
p2p: { socket },
|
||||
} = getState();
|
||||
if (socket) {
|
||||
socket.send(
|
||||
Buffer.from(
|
||||
JSON.stringify({
|
||||
method: 'chat',
|
||||
params: {
|
||||
...message,
|
||||
id: id++,
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
dispatch(appendMessage(message));
|
||||
}
|
||||
};
|
||||
export const sendPairRequest =
|
||||
(target: string) =>
|
||||
async (dispatch: Dispatch, getState: () => AppRootState) => {
|
||||
const {
|
||||
p2p: { socket, clientId },
|
||||
} = getState();
|
||||
if (socket && clientId) {
|
||||
socket.send(
|
||||
Buffer.from(
|
||||
JSON.stringify({
|
||||
method: 'pair_request',
|
||||
params: {
|
||||
from: clientId,
|
||||
to: target,
|
||||
id: id++,
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
export const confirmPairRequest =
|
||||
(target: string) => (dispatch: Dispatch, getState: () => AppRootState) => {
|
||||
const {
|
||||
p2p: { socket, clientId },
|
||||
} = getState();
|
||||
|
||||
console.log({ target, clientId });
|
||||
if (socket && clientId) {
|
||||
socket.send(
|
||||
Buffer.from(
|
||||
JSON.stringify({
|
||||
method: 'pair_request_success',
|
||||
params: {
|
||||
from: clientId,
|
||||
to: target,
|
||||
id: id++,
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
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[] {
|
||||
return useSelector((state: AppRootState) => {
|
||||
return state.p2p.messages;
|
||||
}, deepEqual);
|
||||
}
|
||||
|
||||
export function usePairId(): string {
|
||||
return useSelector((state: AppRootState) => {
|
||||
return state.p2p.pairing;
|
||||
}, deepEqual);
|
||||
}
|
||||
@@ -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,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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user