Compare commits

..

9 Commits

Author SHA1 Message Date
mac
727e147690 Update manifest.json 2024-10-10 21:24:16 +02:00
Tanner
2db735e04f Multiple Redactions during Notarization process (#81) 2024-10-09 05:33:47 -04:00
Hendrik Eeckhaut
f6e582016b feat: add button to open extension in a browser page and various UI updates (#99) 2024-10-09 04:56:04 -04:00
Hendrik Eeckhaut
21ebcd1a11 Added documentation (#96)
* Added documentation

* formatting
2024-10-08 10:24:05 -04:00
Hendrik Eeckhaut
5063d6cb45 chore: refactor to use constants (#104) 2024-10-08 10:22:13 -04:00
Hendrik Eeckhaut
3d5e3ce4ac chore: remove Max transcript size and refactor to use constants (#106)
* chore: refactor to use constants

* chore: remove stale max_transcript_size

closes #105
2024-10-08 10:21:41 -04:00
tsukino
d47cf0d8ea feat: integrate with alpha.7 (#102) 2024-10-04 05:37:41 -04:00
Tanner
25f35d0051 feat: moving requestbuilder.tsx from utils to it's own component (#92) 2024-08-28 04:10:28 -04:00
tsukino
5ccdd9b06a feat: add default plugins to extension bundle (#93) 2024-08-28 04:05:56 -04:00
40 changed files with 3910 additions and 3290 deletions

6180
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "tlsn-extension",
"version": "0.1.0.6",
"version": "0.1.0.700",
"license": "MIT",
"repository": {
"type": "git",
@@ -38,8 +38,8 @@
"redux-logger": "^3.0.6",
"redux-thunk": "^2.4.2",
"tailwindcss": "^3.3.3",
"tlsn-js": "0.1.0-alpha.6.2",
"tlsn-jsV5.3": "npm:tlsn-js@0.1.0-alpha.5.3"
"tlsn-js": "0.1.0-alpha.7.1",
"tlsn-js-v5": "npm:tlsn-js@0.1.0-alpha.5.4"
},
"devDependencies": {
"@babel/core": "^7.20.12",

View File

@@ -1 +0,0 @@
You can find example plugins at https://github.com/tlsnotary/tlsn-plugin-boilerplate/tree/main/examples

24
pnpm-lock.yaml generated
View File

@@ -72,11 +72,11 @@ dependencies:
specifier: ^3.3.3
version: 3.4.3
tlsn-js:
specifier: 0.1.0-alpha.6.2
version: 0.1.0-alpha.6.2
tlsn-jsV5.3:
specifier: npm:tlsn-js@0.1.0-alpha.5.3
version: /tlsn-js@0.1.0-alpha.5.3
specifier: 0.1.0-alpha.7.1
version: 0.1.0-alpha.7.1
tlsn-js-v5:
specifier: npm:tlsn-js@0.1.0-alpha.5.4
version: /tlsn-js@0.1.0-alpha.5.4
devDependencies:
'@babel/core':
@@ -7974,16 +7974,22 @@ packages:
resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==}
dev: true
/tlsn-js@0.1.0-alpha.5.3:
resolution: {integrity: sha512-eTSCQ6MaH8mN2oCfLsQDe/mdfTlIq74MbKVesV6M01C2SRE0FJcVlTWMsnT3L+wOpvOlvsiBeCWV7T9m/YMzew==}
/tlsn-js@0.1.0-alpha.5.4:
resolution: {integrity: sha512-qbqaDjApXarohn/XMJrxMsNHYTCzy+pw0Fc8gtPPN17PLE+1DwwLTXAAhnxYqYQyo3+Hmy+ODd4C02+KCwRWmg==}
engines: {node: '>= 16.20.2'}
dependencies:
comlink: 4.4.1
dev: false
/tlsn-js@0.1.0-alpha.6.2:
resolution: {integrity: sha512-6PANWQEFw48VFNfDgA017zcNRae2OutzNmE5Xcc/h6lwkZZ8MBZIFAFfz/WHZJ3fcFJumaKrG80gpomP6blZqw==}
/tlsn-js@0.1.0-alpha.7.1:
resolution: {integrity: sha512-EWdRp1VQBfdre8jehJgmDjtDvt01ZL1JWbcscctnFTLIIwMYS7IBxU07UYG0NMZFXeTE8PlrUDEVwEl1+vla+g==}
engines: {node: '>= 16.20.2'}
dependencies:
tlsn-wasm: 0.1.0-alpha.7.2
dev: false
/tlsn-wasm@0.1.0-alpha.7.2:
resolution: {integrity: sha512-NzrDfOxmFtMHDb4lmMsx6RaS6F+IVXEHxK0zow8jpnx+NryuJ+qnp4380Lq0uj61w/Yuq+yzOhzFe6Bpeo59dA==}
dev: false
/tmp@0.0.33:

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,115 @@
import React, {
MouseEventHandler,
ReactElement,
ReactNode,
useCallback,
useState,
} from 'react';
import Icon from '../Icon';
import browser from 'webextension-polyfill';
import classNames from 'classnames';
import { useNavigate } from 'react-router';
import PluginUploadInfo from '../PluginInfo';
export function MenuIcon(): ReactElement {
const [opened, setOpen] = useState(false);
const toggleMenu = useCallback(() => {
setOpen(!opened);
}, [opened]);
return (
<div className="relative">
{opened && (
<>
<div
className="fixed top-0 left-0 w-screen h-screen z-10"
onClick={toggleMenu}
/>
<Menu opened={opened} setOpen={setOpen} />
</>
)}
<Icon
fa="fa-solid fa-bars"
className="text-slate-500 hover:text-slate-700 active:text-slate-900 cursor-pointer z-20"
onClick={toggleMenu}
/>
</div>
);
}
export default function Menu(props: {
opened: boolean;
setOpen: (opened: boolean) => void;
}): ReactElement {
const navigate = useNavigate();
const openExtensionInPage = () => {
props.setOpen(false);
browser.tabs.create({
url: `chrome-extension://${chrome.runtime.id}/popup.html`,
});
};
return (
<div className="absolute top-[100%] right-0 rounded-md z-20">
<div className="flex flex-col bg-slate-200 w-40 shadow rounded-md py">
<MenuRow
fa="fa-solid fa-plus"
className="relative"
onClick={() => {
props.setOpen(false);
}}
>
<PluginUploadInfo />
<span>Install Plugin</span>
</MenuRow>
<MenuRow
fa="fa-solid fa-toolbox"
className="border-b border-slate-300"
onClick={() => {
props.setOpen(false);
navigate('/plugins');
}}
>
Plugins
</MenuRow>
<MenuRow
className="lg:hidden"
fa="fa-solid fa-up-right-and-down-left-from-center"
onClick={openExtensionInPage}
>
Expand
</MenuRow>
<MenuRow
fa="fa-solid fa-gear"
onClick={() => {
props.setOpen(false);
navigate('/options');
}}
>
Options
</MenuRow>
</div>
</div>
);
}
function MenuRow(props: {
fa: string;
children?: ReactNode;
onClick?: MouseEventHandler;
className?: string;
}): ReactElement {
return (
<div
className={classNames(
'flex flex-row items-center py-3 px-4 gap-2 hover:bg-slate-300 cursor-pointer text-slate-800 hover:text-slate-900 font-semibold',
props.className,
)}
onClick={props.onClick}
>
<Icon size={0.875} fa={props.fa} />
{props.children}
</div>
);
}

View File

@@ -24,6 +24,7 @@ import {
} from '../../utils/plugins';
import { ErrorModal } from '../ErrorModal';
import classNames from 'classnames';
import DefaultPluginIcon from '../../assets/img/default-plugin-icon.png';
export default function PluginUploadInfo(): ReactElement {
const [error, showError] = useState('');
@@ -122,7 +123,7 @@ export function PluginInfoModal(props: {
<ModalHeader className="w-full p-2 border-gray-200 text-gray-500">
{header || (
<div className="flex flex-row items-end justify-start gap-2">
<img className="h-5" src={logo} alt="logo" />
<img className="h-5" src={logo || DefaultPluginIcon} alt="logo" />
<span className="font-semibold">{`Installing ${pluginContent.title}`}</span>
</div>
)}
@@ -132,7 +133,7 @@ export function PluginInfoModal(props: {
<>
<img
className="w-12 h-12"
src={pluginContent.icon}
src={pluginContent.icon || DefaultPluginIcon}
alt="Plugin Icon"
/>
<span className="text-3xl text-center">

View File

@@ -5,12 +5,7 @@ import React, {
useEffect,
useState,
} from 'react';
import {
fetchPluginHashes,
removePlugin,
fetchPluginConfigByHash,
runPlugin,
} from '../../utils/rpc';
import { fetchPluginHashes, removePlugin, runPlugin } from '../../utils/rpc';
import { usePluginHashes } from '../../reducers/plugins';
import { PluginConfig } from '../../utils/misc';
import DefaultPluginIcon from '../../assets/img/default-plugin-icon.png';
@@ -169,7 +164,7 @@ export function Plugin(props: {
<PluginInfoModalContent className="flex flex-col items-center cursor-default">
<img
className="w-12 h-12 mb-2"
src={config.icon}
src={config.icon || DefaultPluginIcon}
alt="Plugin Icon"
/>
<span className="text-3xl text-blue-600 font-semibold">

View File

@@ -26,7 +26,6 @@ import {
getMaxSent,
} from '../../utils/storage';
import { MAX_RECV, MAX_SENT } from '../../utils/constants';
import { urlify } from '../../utils/misc';
type Props = {
requestId: string;

View File

@@ -1,13 +1,21 @@
import React, { ReactElement, useCallback, useState } from 'react';
import React, {
ReactElement,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { BackgroundActiontype, RequestLog } from '../../entries/Background/rpc';
import { useNavigate } from 'react-router';
import Fuse from 'fuse.js';
import Icon from '../Icon';
import { useDispatch } from 'react-redux';
import { setRequests } from '../../reducers/requests';
import classNames from 'classnames';
type Props = {
requests: RequestLog[];
shouldFix?: boolean;
};
export default function RequestTable(props: Props): ReactElement {
@@ -47,7 +55,14 @@ export default function RequestTable(props: Props): ReactElement {
return (
<div className="flex flex-col flex-nowrap flex-grow">
<div className="flex flex-row flex-nowrap bg-slate-300 py-1 px-2 gap-2">
<div
className={classNames(
'flex flex-row flex-nowrap bg-slate-300 py-1 px-2 gap-2',
{
'fixed top-[4.5rem] w-full shadow': props.shouldFix,
},
)}
>
<input
className="input w-full"
type="text"
@@ -61,7 +76,7 @@ export default function RequestTable(props: Props): ReactElement {
onClick={reset}
/>
</div>
<div className="flex-grow overflow-y-auto h-0">
<div className="flex-grow">
<table className="border border-slate-300 border-collapse table-fixed w-full">
<thead className="bg-slate-200">
<tr>

View File

@@ -28,6 +28,12 @@ const cookiesDb = db.sublevel<string, boolean>('cookies', {
const headersDb = db.sublevel<string, boolean>('headers', {
valueEncoding: 'json',
});
const appDb = db.sublevel<string, any>('app', {
valueEncoding: 'json',
});
enum AppDatabaseKey {
DefaultPluginsInstalled = 'DefaultPluginsInstalled',
}
export async function addNotaryRequest(
now = Date.now(),
@@ -372,3 +378,19 @@ export async function getHeadersByHost(host: string) {
}
return ret;
}
async function getDefaultPluginsInstalled(): Promise<boolean> {
return appDb.get(AppDatabaseKey.DefaultPluginsInstalled).catch(() => false);
}
export async function setDefaultPluginsInstalled(installed = false) {
return mutex.runExclusive(async () => {
await appDb.put(AppDatabaseKey.DefaultPluginsInstalled, installed);
});
}
export async function getAppState() {
return {
defaultPluginsInstalled: await getDefaultPluginsInstalled(),
};
}

View File

@@ -1,6 +1,8 @@
import { onBeforeRequest, onResponseStarted, onSendHeaders } from './handlers';
import { deleteCacheByTabId } from './cache';
import browser from 'webextension-polyfill';
import { getAppState, setDefaultPluginsInstalled } from './db';
import { installPlugin } from './plugins/utils';
(async () => {
browser.webRequest.onSendHeaders.addListener(
@@ -31,6 +33,19 @@ import browser from 'webextension-polyfill';
deleteCacheByTabId(tabId);
});
const { defaultPluginsInstalled } = await getAppState();
if (!defaultPluginsInstalled) {
try {
const twitterProfileUrl = browser.runtime.getURL('twitter_profile.wasm');
const discordDmUrl = browser.runtime.getURL('discord_dm.wasm');
await installPlugin(twitterProfileUrl);
await installPlugin(discordDmUrl);
} finally {
await setDefaultPluginsInstalled(true);
}
}
const { initRPC } = await import('./rpc');
await createOffscreenDocument();
initRPC();

View File

@@ -0,0 +1,29 @@
import { addPlugin, addPluginConfig, addPluginMetadata } from '../db';
import { getPluginConfig } from '../../../utils/misc';
export async function installPlugin(
urlOrBuffer: ArrayBuffer | string,
origin = '',
filePath = '',
metadata: {[key: string]: string} = {},
) {
let arrayBuffer;
if (typeof urlOrBuffer === 'string') {
const resp = await fetch(urlOrBuffer);
arrayBuffer = await resp.arrayBuffer();
} else {
arrayBuffer = urlOrBuffer;
}
const config = await getPluginConfig(arrayBuffer);
const hex = Buffer.from(arrayBuffer).toString('hex');
const hash = await addPlugin(hex);
await addPluginConfig(hash!, config);
await addPluginMetadata(hash!, {
...metadata,
origin,
filePath,
});
return hash;
}

View File

@@ -1,6 +1,6 @@
import browser from 'webextension-polyfill';
import { clearCache, getCacheByTabId } from './cache';
import { addRequestHistory } from '../../reducers/history';
import { addRequestHistory, setRequests } from '../../reducers/history';
import {
addNotaryRequest,
addNotaryRequestProofs,
@@ -24,6 +24,8 @@ import {
getPlugins,
getCookiesByHost,
getHeadersByHost,
getAppState,
setDefaultPluginsInstalled,
} from './db';
import { addOnePlugin, removeOnePlugin } from '../../reducers/plugins';
import {
@@ -85,6 +87,8 @@ export enum BackgroundActiontype {
run_plugin_request = 'run_plugin_request',
run_plugin_response = 'run_plugin_response',
get_logging_level = 'get_logging_level',
get_app_state = 'get_app_state',
set_default_plugins_installed = 'set_default_plugins_installed',
}
export type BackgroundAction = {
@@ -115,7 +119,6 @@ export type RequestHistory = {
method: string;
headers: { [key: string]: string };
body?: string;
maxTranscriptSize: number;
maxSentData: number;
maxRecvData: number;
notaryUrl: string;
@@ -192,6 +195,12 @@ export const initRPC = () => {
case BackgroundActiontype.get_logging_level:
getLoggingFilter().then(sendResponse);
return true;
case BackgroundActiontype.get_app_state:
getAppState().then(sendResponse);
return true;
case BackgroundActiontype.set_default_plugins_installed:
setDefaultPluginsInstalled(request.data).then(sendResponse);
return true;
default:
break;
}
@@ -215,15 +224,13 @@ function handleGetProveRequests(
sendResponse: (data?: any) => void,
): 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 browser.runtime.sendMessage({
type: BackgroundActiontype.push_action,
data: {
tabId: 'background',
},
action: setRequests(reqs),
});
sendResponse(reqs);
});
@@ -318,7 +325,6 @@ async function handleProveRequestStart(
method,
headers,
body,
maxTranscriptSize,
maxSentData,
maxRecvData,
notaryUrl,
@@ -334,7 +340,6 @@ async function handleProveRequestStart(
body,
maxSentData,
maxRecvData,
maxTranscriptSize,
notaryUrl,
websocketProxyUrl,
secretHeaders,
@@ -359,7 +364,6 @@ async function handleProveRequestStart(
method,
headers,
body,
maxTranscriptSize,
maxSentData,
maxRecvData,
notaryUrl,
@@ -389,14 +393,12 @@ async function runPluginProver(request: BackgroundAction, now = Date.now()) {
const websocketProxyUrl = _websocketProxyUrl || (await getProxyApi());
const maxSentData = _maxSentData || (await getMaxSent());
const maxRecvData = _maxRecvData || (await getMaxRecv());
const maxTranscriptSize = 16384;
const { id } = await addNotaryRequest(now, {
url,
method,
headers,
body,
maxTranscriptSize,
notaryUrl,
websocketProxyUrl,
maxRecvData,
@@ -423,7 +425,6 @@ async function runPluginProver(request: BackgroundAction, now = Date.now()) {
method,
headers,
body,
maxTranscriptSize,
notaryUrl,
websocketProxyUrl,
maxRecvData,
@@ -798,7 +799,6 @@ async function handleNotarizeRequest(request: BackgroundAction) {
body,
maxSentData = await getMaxSent(),
maxRecvData = await getMaxRecv(),
maxTranscriptSize,
notaryUrl = await getNotaryApi(),
websocketProxyUrl = await getProxyApi(),
origin,
@@ -813,7 +813,6 @@ async function handleNotarizeRequest(request: BackgroundAction) {
body,
maxSentData,
maxRecvData,
maxTranscriptSize,
notaryUrl,
websocketProxyUrl,
metadata,
@@ -856,7 +855,6 @@ async function handleNotarizeRequest(request: BackgroundAction) {
method,
headers,
body,
maxTranscriptSize,
maxSentData,
maxRecvData,
notaryUrl,

View File

@@ -1,7 +1,7 @@
import { ContentScriptTypes, RPCClient } from './rpc';
import { RequestHistory } from '../Background/rpc';
import { PluginConfig, PluginMetadata } from '../../utils/misc';
import { Proof } from '../../utils/types';
import { PresentationJSON } from '../../utils/types';
const client = new RPCClient();
@@ -27,7 +27,7 @@ class TLSN {
return resp || [];
}
async getProof(id: string): Promise<Proof | null> {
async getProof(id: string): Promise<PresentationJSON | null> {
const resp = await client.call(ContentScriptTypes.get_proof, {
id,
});
@@ -47,12 +47,11 @@ class TLSN {
websocketProxyUrl?: string;
maxSentData?: number;
maxRecvData?: number;
maxTranscriptSize?: number;
metadata?: {
[k: string]: string;
};
},
): Promise<Proof> {
): Promise<PresentationJSON> {
const resp = await client.call(ContentScriptTypes.notarize, {
url,
method: requestOptions?.method,
@@ -60,7 +59,6 @@ class TLSN {
body: requestOptions?.body,
maxSentData: proofOptions?.maxSentData,
maxRecvData: proofOptions?.maxRecvData,
maxTranscriptSize: proofOptions?.maxTranscriptSize,
notaryUrl: proofOptions?.notaryUrl,
websocketProxyUrl: proofOptions?.websocketProxyUrl,
metadata: proofOptions?.metadata,

View File

@@ -84,7 +84,6 @@ import { urlify } from '../../utils/misc';
websocketProxyUrl?: string;
maxSentData?: number;
maxRecvData?: number;
maxTranscriptSize?: number;
}>,
) => {
const {
@@ -94,7 +93,6 @@ import { urlify } from '../../utils/misc';
body,
maxSentData,
maxRecvData,
maxTranscriptSize,
notaryUrl,
websocketProxyUrl,
metadata,
@@ -112,7 +110,6 @@ import { urlify } from '../../utils/misc';
body,
maxSentData,
maxRecvData,
maxTranscriptSize,
notaryUrl,
websocketProxyUrl,
metadata,

View File

@@ -3,19 +3,20 @@ import * as Comlink from 'comlink';
import { OffscreenActionTypes } from './types';
import {
NotaryServer,
Prover as _Prover,
NotarizedSession as _NotarizedSession,
TlsProof as _TlsProof,
Prover as TProver,
Presentation as TPresentation,
Transcript,
} from 'tlsn-js';
import { verify } from 'tlsn-jsV5.3';
import { verify } from 'tlsn-js-v5';
import { urlify } from '../../utils/misc';
import { BackgroundActiontype } from '../Background/rpc';
import browser from 'webextension-polyfill';
import { Proof, ProofV1 } from '../../utils/types';
import { PresentationJSON } from '../../utils/types';
import { PresentationJSON as PresentationJSONa7 } from 'tlsn-js/build/types';
import { Method } from 'tlsn-js/wasm/pkg';
const { init, Prover, NotarizedSession, TlsProof }: any = Comlink.wrap(
const { init, Prover, Presentation }: any = Comlink.wrap(
new Worker(new URL('./worker.ts', import.meta.url)),
);
@@ -111,7 +112,7 @@ const Offscreen = () => {
}
case BackgroundActiontype.verify_prove_request: {
(async () => {
const proof: Proof = request.data.proof;
const proof: PresentationJSON = request.data.proof;
const result: { sent: string; recv: string } =
await verifyProof(proof);
@@ -194,7 +195,7 @@ async function createProof(options: {
id: string;
secretHeaders: string[];
secretResps: string[];
}): Promise<ProofV1> {
}): Promise<PresentationJSONa7> {
const {
url,
method = 'GET',
@@ -211,7 +212,7 @@ async function createProof(options: {
const hostname = urlify(url)?.hostname || '';
const notary = NotaryServer.from(notaryUrl);
const prover: _Prover = await new Prover({
const prover: TProver = await new Prover({
id,
serverDns: hostname,
maxSentData,
@@ -260,24 +261,21 @@ async function createProof(options: {
),
};
const session: _NotarizedSession = await new NotarizedSession(
await prover.notarize(commit),
);
const notarizationOutputs = await prover.notarize(commit);
const proofHex = await session.proof(commit);
const proof: ProofV1 = {
version: '1.0',
meta: {
notaryUrl,
websocketProxyUrl,
},
data: proofHex,
};
return proof;
const presentation = (await new Presentation({
attestationHex: notarizationOutputs.attestation,
secretsHex: notarizationOutputs.secrets,
notaryUrl: notarizationOutputs.notaryUrl,
websocketProxyUrl: notarizationOutputs.websocketProxyUrl,
reveal: commit,
})) as TPresentation;
const presentationJSON = await presentation.json();
return presentationJSON;
}
async function verifyProof(
proof: Proof,
proof: PresentationJSON,
): Promise<{ sent: string; recv: string }> {
let result: { sent: string; recv: string };
@@ -286,12 +284,17 @@ async function verifyProof(
result = await verify(proof);
break;
}
case '1.0': {
const tlsProof: _TlsProof = await new TlsProof(proof.data);
result = await tlsProof.verify({
typ: 'P256',
key: await NotaryServer.from(proof.meta.notaryUrl).publicKey(),
case '0.1.0-alpha.7': {
const presentation: TPresentation = await new Presentation(proof.data);
const verifierOutput = await presentation.verify();
const transcript = new Transcript({
sent: verifierOutput.transcript.sent,
recv: verifierOutput.transcript.recv,
});
result = {
sent: transcript.sent(),
recv: transcript.recv(),
};
break;
}
}

View File

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

View File

@@ -33,6 +33,8 @@ import Icon from '../../components/Icon';
import classNames from 'classnames';
import { getConnection } from '../Background/db';
import { useIsConnected, setConnection } from '../../reducers/requests';
import { MenuIcon } from '../../components/Menu';
import Plugins from '../../pages/Plugins';
const Popup = () => {
const dispatch = useDispatch();
@@ -84,7 +86,7 @@ const Popup = () => {
}, []);
return (
<div className="flex flex-col w-full h-full overflow-hidden">
<div className="flex flex-col w-full h-full overflow-hidden lg:w-[600px] lg:h-[800px] lg:border lg:m-auto lg:mt-40 lg:bg-white lg:shadow">
<div className="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">
<img
className="absolute left-2 h-5 cursor-pointer"
@@ -92,17 +94,21 @@ const Popup = () => {
alt="logo"
onClick={() => navigate('/')}
/>
<AppConnectionLogo />
<div className="flex flex-row flex-grow items-center justify-end gap-4">
<AppConnectionLogo />
<MenuIcon />
</div>
</div>
<Routes>
<Route path="/requests/:requestId/*" element={<Request />} />
<Route path="/notary/:requestId" element={<Notarize />} />
<Route path="/verify/:requestId/*" element={<ProofViewer />} />
<Route path="/verify" element={<ProofUploader />} />
<Route path="/history" element={<History />} />
<Route path="/requests" element={<Requests />} />
<Route path="/history" element={<Home tab="history" />} />
<Route path="/requests" element={<Home tab="network" />} />
<Route path="/custom/*" element={<RequestBuilder />} />
<Route path="/options" element={<Options />} />
<Route path="/plugins" element={<Plugins />} />
<Route path="/home" element={<Home />} />
<Route path="/plugininfo" element={<PluginUploadInfo />} />
<Route path="/connection-approval" element={<ConnectionApproval />} />
@@ -141,7 +147,7 @@ function AppConnectionLogo() {
return (
<div
className="absolute right-2 flex flex-nowrap flex-row items-center gap-1 justify-center w-fit cursor-pointer"
className="flex flex-nowrap flex-row items-center gap-1 justify-center w-fit cursor-pointer"
onClick={() => setShowConnectionDetails(true)}
>
<div className="flex flex-row relative bg-black border-[1px] border-black rounded-full">

View File

@@ -30,6 +30,10 @@ code {
width: 100vw;
height: 100vh;
overflow: hidden;
@media (min-width: 1024px) {
@apply bg-slate-400;
}
}
.button {

View File

@@ -235,7 +235,7 @@ function StepContent(
)}
onClick={viewProofInPopup}
>
<span className="text-sm">View Proof</span>
<span className="text-sm">View</span>
</button>
);
} else if (notaryRequest?.status === 'pending' || pending || notarizationId) {

View File

@@ -28,12 +28,13 @@
],
"web_accessible_resources": [
{
"resources": ["content.styles.css", "icon-128.png", "icon-34.png", "content.bundle.js"],
"resources": ["content.styles.css", "icon-128.png", "icon-34.png", "content.bundle.js", "discord_dm.wasm", "twitter_profile.wasm"],
"matches": ["http://*/*", "https://*/*", "<all_urls>"]
}
],
"host_permissions": ["<all_urls>"],
"permissions": [
"contextMenus".
"offscreen",
"storage",
"webRequest",

View File

@@ -25,9 +25,11 @@ export default function History(): ReactElement {
return (
<div className="flex flex-col flex-nowrap overflow-y-auto">
{history.map((id) => {
return <OneRequestHistory key={id} requestId={id} />;
})}
{history
.map((id) => {
return <OneRequestHistory key={id} requestId={id} />;
})
.reverse()}
</div>
);
}
@@ -160,7 +162,7 @@ export function OneRequestHistory(props: {
className="bg-slate-600 text-slate-200 hover:bg-slate-500 hover:text-slate-100"
onClick={onView}
fa="fa-solid fa-receipt"
ctaText="View Proof"
ctaText="View"
hidden={hideActions.includes('view')}
/>
<ActionButton

View File

@@ -0,0 +1,5 @@
#home {
&::-webkit-scrollbar {
display: none;
}
}

View File

@@ -2,78 +2,284 @@ import React, {
MouseEventHandler,
ReactElement,
ReactNode,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import Icon from '../../components/Icon';
import classNames from 'classnames';
import { useNavigate } from 'react-router';
import { useRequests } from '../../reducers/requests';
import { PluginList } from '../../components/PluginList';
import PluginUploadInfo from '../../components/PluginInfo';
import { ErrorModal } from '../../components/ErrorModal';
import History from '../History';
import './index.scss';
import Requests from '../Requests';
import PluginUploadInfo from '../../components/PluginInfo';
import {
useOnPluginClick,
usePluginConfig,
usePluginHashes,
} from '../../reducers/plugins';
import { PluginConfig } from '../../utils/misc';
import { getPluginConfigByHash } from '../../entries/Background/db';
import { fetchPluginHashes } from '../../utils/rpc';
import DefaultPluginIcon from '../../assets/img/default-plugin-icon.png';
export default function Home(): ReactElement {
const requests = useRequests();
const navigate = useNavigate();
export default function Home(props: {
tab?: 'history' | 'network';
}): ReactElement {
const [error, showError] = useState('');
const [tab, setTab] = useState<'history' | 'network'>(props.tab || 'history');
const scrollableContent = useRef<HTMLDivElement | null>(null);
const [shouldFix, setFix] = useState(false);
const [actionPanelElement, setActionPanelElement] =
useState<HTMLDivElement | null>(null);
const [scrollTop, setScrollTop] = useState(0);
useEffect(() => {
fetchPluginHashes();
}, []);
useEffect(() => {
const element = scrollableContent.current;
if (!element) return;
if (!actionPanelElement) return;
let timer = Date.now();
const onScroll = () => {
const now = Date.now();
if (now - timer > 20) {
timer = now;
setScrollTop(element.scrollTop);
if (element.scrollTop >= actionPanelElement.clientHeight) {
setFix(true);
} else {
setFix(false);
}
}
};
element.addEventListener('scroll', onScroll);
return () => {
element.removeEventListener('scroll', onScroll);
};
}, [scrollableContent, actionPanelElement]);
return (
<div className="flex flex-col gap-4 py-4 overflow-y-auto">
<div
id="home"
ref={scrollableContent}
className="flex flex-col flex-grow overflow-y-auto"
>
{error && <ErrorModal onClose={() => showError('')} message={error} />}
<div className="flex flex-col flex-nowrap justify-center gap-2 mx-4">
<NavButton fa="fa-solid fa-table" onClick={() => navigate('/requests')}>
<span>Requests</span>
<span>{`(${requests.length})`}</span>
</NavButton>
<NavButton fa="fa-solid fa-hammer" onClick={() => navigate('/custom')}>
Custom
</NavButton>
<NavButton
fa="fa-solid fa-certificate"
onClick={() => navigate('/verify')}
<ActionPanel
setActionPanelElement={setActionPanelElement}
scrollTop={scrollTop}
/>
<div
className={classNames('flex flex-row justify-center items-center', {
'fixed top-9 w-full bg-white shadow lg:w-[598px] lg:mt-40': shouldFix,
})}
>
<TabSelector
onClick={() => setTab('network')}
selected={tab === 'network'}
>
Network
</TabSelector>
<TabSelector
onClick={() => setTab('history')}
selected={tab === 'history'}
>
Verify
</NavButton>
<NavButton fa="fa-solid fa-list" onClick={() => navigate('/history')}>
History
</NavButton>
<NavButton className="relative" fa="fa-solid fa-plus">
<PluginUploadInfo />
Add a plugin
</NavButton>
<NavButton fa="fa-solid fa-gear" onClick={() => navigate('/options')}>
Options
</NavButton>
</TabSelector>
</div>
<div className="flex-grow">
{tab === 'history' && <History />}
{tab === 'network' && <Requests shouldFix={shouldFix} />}
</div>
<PluginList className="mx-4" />
</div>
);
}
function ActionPanel({
setActionPanelElement,
scrollTop,
}: {
scrollTop: number;
setActionPanelElement: (el: HTMLDivElement) => void;
}) {
const pluginHashes = usePluginHashes();
const navigate = useNavigate();
const container = useRef<HTMLDivElement | null>(null);
const [isOverflow, setOverflow] = useState(false);
const [expanded, setExpand] = useState(false);
useEffect(() => {
const element = container.current;
if (!element) return;
setActionPanelElement(element);
const onCheckSize = () => {
if (element.scrollWidth > element.clientWidth) {
setOverflow(true);
} else {
setOverflow(false);
}
};
onCheckSize();
window.addEventListener('resize', onCheckSize);
return () => {
window.removeEventListener('resize', onCheckSize);
};
}, [container, pluginHashes]);
useEffect(() => {
const element = container.current;
if (!element) return;
if (scrollTop >= element.clientHeight) {
setExpand(false);
}
}, [container, scrollTop]);
return (
<div
ref={container}
className={classNames(
'flex flex-row justify-start items-center gap-4 p-4 border-b relative',
{
'flex-wrap': expanded,
'flex-nowrap': !expanded,
},
)}
>
<NavButton
fa="fa-solid fa-hammer"
onClick={() => navigate('/custom')}
title="Build a custom request"
>
Custom
</NavButton>
<NavButton
fa="fa-solid fa-certificate"
onClick={() => navigate('/verify')}
title="Visualize an attestation"
>
Verify
</NavButton>
{pluginHashes.map((hash) => (
<PluginIcon hash={hash} />
))}
<button
className={
'flex flex-row items-center justify-center self-start rounded relative border-2 border-dashed border-slate-300 hover:border-slate-400 text-slate-300 hover:text-slate-400 h-16 w-16 mx-1'
}
title="Install a plugin"
>
<PluginUploadInfo />
<Icon fa="fa-solid fa-plus" />
</button>
<button
className={classNames(
'absolute right-0 top-0 w-6 h-full bg-slate-100 hover:bg-slate-200 font-semibold',
'flex flex-row items-center justify-center gap-2 text-slate-500 hover:text-slate-700',
{
hidden: !isOverflow || expanded,
},
)}
onClick={() => setExpand(true)}
>
<Icon fa="fa-solid fa-caret-down" size={0.875} />
</button>
</div>
);
}
function PluginIcon({ hash }: { hash: string }) {
const config = usePluginConfig(hash);
const onPluginClick = useOnPluginClick(hash);
const onClick = useCallback(() => {
if (!config) return;
onPluginClick();
}, [onPluginClick, config]);
return (
<button
className={classNames(
'flex flex-col flex-nowrap items-center justify-center',
'text-white px-2 py-1 gap-1 opacity-90 hover:opacity-100',
)}
onClick={onClick}
>
<Icon
className="rounded-full flex flex-row items-center justify-center flex-grow-0 flex-shrink-0"
url={config?.icon || DefaultPluginIcon}
size={2}
/>
<span className="font-bold text-primary h-10 w-14 overflow-hidden text-ellipsis">
{config?.title}
</span>
</button>
);
}
function TabSelector(props: {
children: string;
className?: string;
selected?: boolean;
onClick: MouseEventHandler;
}): ReactElement {
return (
<button
onClick={props.onClick}
className={classNames(
'flex flex-grow items-center justify-center p-2 font-semibold hover:text-slate-700 border-b-2 ',
{
'font-semibold text-slate-400 border-white': !props.selected,
'font-bold text-primary border-primary': props.selected,
},
props.className,
)}
>
{props.children}
</button>
);
}
function NavButton(props: {
fa: string;
children?: ReactNode;
onClick?: MouseEventHandler;
className?: string;
title?: string;
disabled?: boolean;
}): ReactElement {
return (
<button
className={classNames(
'flex flex-row flex-nowrap items-center justify-center',
'text-white rounded px-2 py-1 gap-1',
{
'bg-primary/[.8] hover:bg-primary/[.7] active:bg-primary':
!props.disabled,
'bg-primary/[.5]': props.disabled,
},
'flex flex-col flex-nowrap items-center justify-center',
'text-white px-2 py-1 gap-1 opacity-90 hover:opacity-100',
props.className,
)}
onClick={props.onClick}
disabled={props.disabled}
title={props.title}
>
<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="w-8 h-8 rounded-full bg-primary flex flex-row items-center justify-center flex-grow-0 flex-shrink-0"
fa={props.fa}
size={0.875}
/>
<span className="font-bold text-primary h-10 w-14 overflow-hidden text-ellipsis">
{props.children}
</span>
</button>

View File

@@ -6,6 +6,7 @@ import React, {
ReactEventHandler,
useEffect,
useRef,
useMemo,
} from 'react';
import { useNavigate, useParams } from 'react-router';
import { notarizeRequest, useRequest } from '../../reducers/requests';
@@ -19,8 +20,6 @@ import {
} from '../../utils/storage';
import { useDispatch } from 'react-redux';
const maxTranscriptSize = 16384;
export default function Notarize(): ReactElement {
const params = useParams<{ requestId: string }>();
const req = useRequest(params.requestId);
@@ -58,7 +57,6 @@ export default function Notarize(): ReactElement {
body: req.requestBody,
maxSentData,
maxRecvData,
maxTranscriptSize,
notaryUrl,
websocketProxyUrl,
secretHeaders,
@@ -281,34 +279,79 @@ export function RevealHeaderTable(props: {
);
}
function HideResponseStep(props: {
export function HideResponseStep(props: {
onNext: () => void;
onCancel: () => void;
setSecretResps: (secrets: string[]) => void;
}): ReactElement {
}): React.ReactElement {
const params = useParams<{ requestId: string }>();
const req = useRequest(params.requestId);
const [responseText, setResponseText] = useState('');
const [start, setStart] = useState(0);
const [end, setEnd] = useState(0);
const [redactedRanges, setRedactedRanges] = useState<
{ start: number; end: number }[]
>([]);
const [isRedactMode, setIsRedactMode] = useState(true);
const taRef = useRef<HTMLTextAreaElement | null>(null);
const onSelectionChange: ReactEventHandler<HTMLTextAreaElement> = useCallback(
(e) => {
const ta = e.currentTarget;
if (ta.selectionEnd > ta.selectionStart) {
setStart(ta.selectionStart);
setEnd(ta.selectionEnd);
props.setSecretResps(
[
responseText.substring(0, ta.selectionStart),
responseText.substring(ta.selectionEnd, responseText.length),
].filter((d) => !!d),
);
const onSelectionChange: React.MouseEventHandler<HTMLTextAreaElement> =
useCallback(
(e) => {
const ta = e.currentTarget;
if (isRedactMode && ta.selectionEnd > ta.selectionStart) {
const newRange: { start: number; end: number } = {
start: ta.selectionStart,
end: ta.selectionEnd,
};
setRedactedRanges((prevRanges) => {
let updatedRanges = [...prevRanges, newRange].sort(
(a, b) => a.start - b.start,
);
updatedRanges = mergeRanges(updatedRanges);
const secretResps = updatedRanges
.map(({ start, end }) => responseText.substring(start, end))
.filter((d) => !!d);
props.setSecretResps(secretResps);
return updatedRanges;
});
} else if (!isRedactMode) {
const clickPosition = ta.selectionStart;
setRedactedRanges((prevRanges) => {
const updatedRanges = prevRanges.filter(
({ start, end }) => clickPosition < start || clickPosition > end,
);
const secretResps = updatedRanges
.map(({ start, end }) => responseText.substring(start, end))
.filter((d) => !!d);
props.setSecretResps(secretResps);
return updatedRanges;
});
}
},
[responseText, props, isRedactMode],
);
const mergeRanges = (
ranges: { start: number; end: number }[],
): { start: number; end: number }[] => {
if (ranges.length === 0) return [];
const mergedRanges: { start: number; end: number }[] = [ranges[0]];
for (let i = 1; i < ranges.length; i++) {
const lastRange = mergedRanges[mergedRanges.length - 1];
if (ranges[i].start <= lastRange.end) {
lastRange.end = Math.max(lastRange.end, ranges[i].end);
} else {
mergedRanges.push(ranges[i]);
}
},
[responseText],
);
}
return mergedRanges;
};
useEffect(() => {
if (!req) return;
@@ -344,37 +387,46 @@ function HideResponseStep(props: {
if (current) {
current.focus();
current.setSelectionRange(start, end);
}
}, [taRef, start, end]);
}, [taRef]);
if (!req) return <></>;
let shieldedText = '';
if (end > start) {
shieldedText = Array(start)
.fill('*')
.join('')
.concat(responseText.substring(start, end))
.concat(
Array(responseText.length - end)
.fill('*')
.join(''),
);
}
const shieldedText = responseText.split('');
redactedRanges.forEach(({ start, end }) => {
for (let i = start; i < end; i++) {
shieldedText[i] = '*';
}
});
return (
<div className="flex flex-col flex-nowrap flex-shrink flex-grow h-0">
<div className="border bg-primary/[0.9] text-white border-slate-300 py-1 px-2 font-semibold">
Step 2 of 2: Highlight text to show only selected text from response
Step 2 of 2:{' '}
{isRedactMode
? 'Highlight text to redact selected portions'
: 'Click redacted text to unredact'}
</div>
<div className="flex flex-row justify-end p-0.5 gap-2 border-t">
<button
className={`bg-${isRedactMode ? 'red-500' : 'green-500'} text-white font-bold hover:bg-${isRedactMode ? 'red-400' : 'green-400'} px-2 py-0.5 active:bg-${isRedactMode ? 'red-600' : 'green-600'}`}
onClick={() => setIsRedactMode(!isRedactMode)}
>
{isRedactMode ? 'Unredact Text' : 'Redact Text'}
</button>
<button
className="bg-gray-500 text-white font-bold hover:bg-gray-400 px-2 py-0.5 active:bg-gray-600"
onClick={() => setRedactedRanges([])}
>
Unredact All
</button>
</div>
<div className="flex flex-col flex-grow flex-shrink h-0 overflow-y-auto p-2">
<textarea
ref={taRef}
className="flex-grow textarea bg-slate-100 font-mono"
value={shieldedText || responseText}
onSelect={onSelectionChange}
value={shieldedText.join('')}
onMouseUp={onSelectionChange}
/>
</div>
<div className="flex flex-row justify-end p-2 gap-2 border-t">

View File

@@ -80,6 +80,10 @@ export default function Options(): ReactElement {
setAdvanced(!advanced);
}, [advanced]);
const openInTab = useCallback((url: string) => {
browser.tabs.create({ url });
}, []);
return (
<div className="flex flex-col flex-nowrap flex-grow">
{showReloadModal && (
@@ -152,6 +156,22 @@ export default function Options(): ReactElement {
Save
</button>
</div>
<div className="flex flex-col w-full items-end gap-2 p-2">
<button
className="button"
onClick={() =>
openInTab('https://github.com/tlsnotary/tlsn-extension/issues/new')
}
>
File an issue
</button>
<button
className="button"
onClick={() => openInTab('https://discord.gg/9XwESXtcN7')}
>
Join our Discord
</button>
</div>
</div>
);
}

View File

@@ -0,0 +1,10 @@
import React, { ReactElement } from "react";
import { PluginList } from "../../components/PluginList";
export default function Plugins(): ReactElement {
return (
<div className="flex flex-col flex-nowrap flex-grow">
<PluginList className="p-2 overflow-y-auto" />
</div>
)
}

View File

@@ -24,7 +24,7 @@ import {
InputBody,
FormBodyTable,
parseResponse,
} from '../../utils/requestbuilder';
} from '../../components/RequestBuilder';
enum TabType {
Params = 'Params',
@@ -56,15 +56,7 @@ export default function RequestBuilder(props?: {
const [method, setMethod] = useState<string>(props?.method || 'GET');
const [type, setType] = useState<string>('text/plain');
const [headers, setHeaders] = useState<[string, string, boolean?][]>(
props?.headers || [
['Content-Type', type, true],
['Accept', '*/*', false],
['Host', '', false],
['Connection', 'keep-alive', false],
['Accept-Encoding', 'gzip, deflate, br', false],
['Accept-Language', 'en-US,en;q=0.9', false],
['User-Agent', window.navigator.userAgent, false],
],
props?.headers || [['Content-Type', type, true]],
);
const [responseData, setResponseData] = useState<{
@@ -193,9 +185,6 @@ export default function RequestBuilder(props?: {
if (k !== 'Cookie') {
map[k] = v;
}
if (k === 'Host') {
map[k] ? map[k] : (map[k] = url?.host || '');
}
return map;
}, {}),
body: body ? formatForRequest(body, type) : undefined,
@@ -203,7 +192,6 @@ export default function RequestBuilder(props?: {
maxRecvData,
secretHeaders: [],
secretResps: [],
maxTranscriptSize: 0,
notaryUrl,
websocketProxyUrl,
},
@@ -217,7 +205,7 @@ export default function RequestBuilder(props?: {
(e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value;
if (value === 'GET' || value === 'HEAD') {
// setType(''); Leaving this here for now - I feel like I did this for a specific reason but I can't remember why
setType('');
setMethod(value);
} else {
setMethod(value);
@@ -423,7 +411,7 @@ function HeaderTable(props: {
}): ReactElement {
const headers: [string, string, boolean?][] = [
...props.headers,
['', ' ', true],
['', '', true],
];
const last = props.headers.length;
@@ -461,7 +449,7 @@ function HeaderTable(props: {
<input
className="input py-1 px-2 w-full py-1 px-2"
type="text"
value={value ? value : 'Calculated when request is sent'}
value={value}
placeholder="Value"
onChange={(e) => {
props.setHeader(i, key, e.target.value);

View File

@@ -2,11 +2,11 @@ import React, { ReactElement } from 'react';
import RequestTable from '../../components/RequestTable';
import { useRequests } from '../../reducers/requests';
export default function Requests(): ReactElement {
export default function Requests(props: { shouldFix?: boolean }): ReactElement {
const requests = useRequests();
return (
<>
<RequestTable requests={requests} />
<RequestTable shouldFix={props.shouldFix} requests={requests} />
</>
);
}

View File

@@ -8,6 +8,7 @@ import deepEqual from 'fast-deep-equal';
enum ActionType {
'/history/addRequest' = '/history/addRequest',
'/history/setRequests' = '/history/setRequests',
'/history/deleteRequest' = '/history/deleteRequest',
}
@@ -37,6 +38,13 @@ export const addRequestHistory = (request?: RequestHistory | null) => {
};
};
export const setRequests = (requests: RequestHistory[]) => {
return {
type: ActionType['/history/setRequests'],
payload: requests,
};
};
export const deleteRequestHistory = (id: string) => {
chrome.runtime.sendMessage<any, string>({
type: BackgroundActiontype.delete_prove_request,
@@ -72,6 +80,18 @@ export default function history(
order: newOrder,
};
}
case ActionType['/history/setRequests']: {
const payload: RequestHistory[] = action.payload;
return {
...state,
map: payload.reduce((map: { [id: string]: RequestHistory }, req) => {
map[req.id] = req;
return map;
}, {}),
order: payload.map(({ id }) => id),
};
}
case ActionType['/history/deleteRequest']: {
const reqId: string = action.payload;
const newMap = { ...state.map };

View File

@@ -1,6 +1,11 @@
import { useSelector } from 'react-redux';
import { AppRootState } from './index';
import deepEqual from 'fast-deep-equal';
import { useCallback, useEffect, useState } from 'react';
import { getPluginConfigByHash } from '../entries/Background/db';
import { PluginConfig } from '../utils/misc';
import { runPlugin } from '../utils/rpc';
import browser from 'webextension-polyfill';
enum ActionType {
'/plugin/addPlugin' = '/plugin/addPlugin',
@@ -52,3 +57,31 @@ export const usePluginHashes = (): string[] => {
return state.plugins.order;
}, deepEqual);
};
export const usePluginConfig = (hash: string) => {
const [config, setConfig] = useState<PluginConfig | null>(null);
useEffect(() => {
(async function () {
setConfig(await getPluginConfigByHash(hash));
})();
}, [hash]);
return config;
};
export const useOnPluginClick = (hash: string) => {
return useCallback(async () => {
await runPlugin(hash, 'start');
const [tab] = await browser.tabs.query({
active: true,
currentWindow: true,
});
await browser.storage.local.set({ plugin_hash: hash });
// @ts-ignore
if (chrome.sidePanel) await chrome.sidePanel.open({ tabId: tab.id });
window.close();
}, [hash]);
};

View File

@@ -69,7 +69,6 @@ export const notarizeRequest = (options: RequestHistory) => async () => {
method: options.method,
headers: options.headers,
body: options.body,
maxTranscriptSize: options.maxTranscriptSize,
maxSentData,
maxRecvData,
secretHeaders: options.secretHeaders,

View File

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

View File

@@ -284,24 +284,24 @@ export const makePlugin = async (
};
export type StepConfig = {
title: string;
description?: string;
cta: string;
action: string;
prover?: boolean;
title: string; // Text for the step's title
description?: string; // Text for the step's description (optional)
cta: string; // Text for the step's call-to-action button
action: string; // The function name that this step will execute
prover?: boolean; // Boolean indicating if this step outputs a notarization (optional)
};
export type PluginConfig = {
title: string;
description: string;
icon?: string;
steps?: StepConfig[];
hostFunctions?: string[];
cookies?: string[];
headers?: string[];
requests: { method: string; url: string }[];
notaryUrls?: string[];
proxyUrls?: string[];
title: string; // The name of the plugin
description: string; // A description of the plugin's purpose
icon?: string; // A base64-encoded image string representing the plugin's icon (optional)
steps?: StepConfig[]; // An array describing the UI steps and behavior (see Step UI below) (optional)
hostFunctions?: string[]; // Host functions that the plugin will have access to
cookies?: string[]; // Cookies the plugin will have access to, cached by the extension from specified hosts (optional)
headers?: string[]; // Headers the plugin will have access to, cached by the extension from specified hosts (optional)
requests: { method: string; url: string }[]; // List of requests that the plugin is allowed to make
notaryUrls?: string[]; // List of notary services that the plugin is allowed to use (optional)
proxyUrls?: string[]; // List of websocket proxies that the plugin is allowed to use (optional)
};
export type PluginMetadata = {

View File

@@ -1,4 +1,5 @@
import { LoggingLevel } from 'tlsn-js';
import { MAX_RECV, MAX_SENT, NOTARY_API, NOTARY_PROXY } from './constants';
export const NOTARY_API_LS_KEY = 'notary-api';
export const PROXY_API_LS_KEY = 'proxy-api';
@@ -18,19 +19,19 @@ export async function get(key: string, defaultValue?: string) {
}
export async function getMaxSent() {
return parseInt(await get(MAX_SENT_LS_KEY, '4096'));
return parseInt(await get(MAX_SENT_LS_KEY, MAX_SENT.toString()));
}
export async function getMaxRecv() {
return parseInt(await get(MAX_RECEIVED_LS_KEY, '16384'));
return parseInt(await get(MAX_RECEIVED_LS_KEY, MAX_RECV.toString()));
}
export async function getNotaryApi() {
return await get(NOTARY_API_LS_KEY, 'https://notary.pse.dev/v0.1.0-alpha.6');
return await get(NOTARY_API_LS_KEY, NOTARY_API);
}
export async function getProxyApi() {
return await get(PROXY_API_LS_KEY, 'wss://notary.pse.dev/proxy');
return await get(PROXY_API_LS_KEY, NOTARY_PROXY);
}
export async function getLoggingFilter(): Promise<LoggingLevel> {

View File

@@ -1,18 +1,10 @@
export type Proof = ProofV0 | ProofV1;
import { PresentationJSON as PresentationJSONa7 } from 'tlsn-js/build/types';
export type ProofV0 = {
export type PresentationJSON = PresentationJSONa5 | PresentationJSONa7;
export type PresentationJSONa5 = {
version?: undefined;
session: any;
substrings: any;
notaryUrl: string;
};
export type ProofV1 = {
version: '1.0';
data: string;
meta: {
notaryUrl: string;
websocketProxyUrl: string;
pluginUrl?: string;
};
};

View File

@@ -40,7 +40,8 @@ var options = {
mode: process.env.NODE_ENV || "development",
ignoreWarnings: [
/Circular dependency between chunks with runtime/,
/ResizeObserver loop completed with undelivered notifications/
/ResizeObserver loop completed with undelivered notifications/,
/Should not import the named export/,
],
entry: {
@@ -82,6 +83,9 @@ var options = {
loader: "sass-loader",
options: {
sourceMap: true,
sassOptions: {
silenceDeprecations: ["legacy-js-api"],
}
},
},
],
@@ -199,31 +203,21 @@ var options = {
}),
new CopyWebpackPlugin({
patterns: [
// {
// from: "node_modules/tlsn-js/build/7.js",
// to: path.join(__dirname, "build"),
// force: true,
// },
// {
// from: "node_modules/tlsn-js/build/250.js",
// to: path.join(__dirname, "build"),
// force: true,
// },
// {
// from: "node_modules/tlsn-js/build/278.js",
// to: path.join(__dirname, "build"),
// force: true,
// },
// {
// from: "node_modules/tlsn-js/build/349.js",
// to: path.join(__dirname, "build"),
// force: true,
// },
{
from: "node_modules/tlsn-js/build",
to: path.join(__dirname, "build"),
force: true,
},
{
from: "src/assets/plugins/discord_dm.wasm",
to: path.join(__dirname, "build"),
force: true,
},
{
from: "src/assets/plugins/twitter_profile.wasm",
to: path.join(__dirname, "build"),
force: true,
},
],
}),
new HtmlWebpackPlugin({