Compare commits

...

14 Commits

Author SHA1 Message Date
tsukino
2ecc850382 refactor: turn p2p prover into interactive verifier plugin prover 2025-07-15 22:37:35 +08:00
tsukino
8cea923685 wip: refactoring p2p 2025-07-15 22:00:53 +08:00
Tanner
cc3264f058 Alpha.12 (#193) 2025-06-27 17:58:36 +01:00
Tanner
ea12322686 Developer Mode (#190)
* feat: adding developer mode toggle to add and display plugins

* chore: lint

* chore: cleanup
2025-06-26 17:54:22 +01:00
tsukino
5a9e5bc77d fix: query for headers and cookies (#192) 2025-06-26 17:38:30 +01:00
tsukino
f489800663 fix: refactor cache layer (#189) 2025-06-19 10:54:08 -04:00
yuroitaki
49056dc605 Correct url. (#191) 2025-06-12 15:30:56 +02:00
Tanner
9567590e47 Data: no longer duplicated in meta for downloaded attestations (#188)
* fix: data no longer duplicated in meta in downloads

* fix: cleanup
2025-06-10 08:24:38 -04:00
Tanner
008cb10b30 Display WSS errors in Sidepanel (#187)
* feat: display websocket proxy errors in sidepanel

* chore: cleanup
2025-06-10 08:23:35 -04:00
tsukino
6b8e9a3580 feat: clear db button (#186)
* feat: clear db

* add size calc
2025-06-10 08:23:13 -04:00
Brendan A. Miller
6aab10b7d0 Improve error handling for webpack build (#183) 2025-06-03 16:44:00 +02:00
Hendrik Eeckhaut
7de46bf590 chore: type cleanup (#185) 2025-06-02 21:30:52 +02:00
Hendrik Eeckhaut
c67b794f40 ci: do not unpack the zip file (#184) 2025-06-02 10:51:31 +02:00
Tanner
9872a376c1 Alpha.11 (#182)
Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
2025-06-02 10:32:34 +02:00
22 changed files with 1820 additions and 1115 deletions

View File

@@ -49,7 +49,7 @@ jobs:
uses: actions/download-artifact@v4
with:
name: tlsn-extension-${{ github.ref_name }}.zip
path: ./tlsn-extension-${{ github.ref_name }}.zip
path: .
- name: 📦 Add extension zip file to release
env:

View File

@@ -12,7 +12,7 @@
# Chrome Extension (MV3) for TLSNotary
> [!IMPORTANT]
> ⚠️ When running the extension against a [notary server](https://github.com/tlsnotary/tlsn/tree/dev/notary-server), please ensure that the server's version is the same as the version of this extension
> ⚠️ When running the extension against a [notary server](https://github.com/tlsnotary/tlsn/tree/main/crates/notary/server), please ensure that the server's version is the same as the version of this extension
## License
This repository is licensed under either of

1902
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.1000",
"version": "0.1.0.1200",
"license": "MIT",
"repository": {
"type": "git",
@@ -40,7 +40,7 @@
"redux-logger": "^3.0.6",
"redux-thunk": "^2.4.2",
"tailwindcss": "^3.3.3",
"tlsn-js": "0.1.0-alpha.10.0"
"tlsn-js": "0.1.0-alpha.12.0"
},
"devDependencies": {
"@babel/core": "^7.20.12",
@@ -94,4 +94,4 @@
"webpack-ext-reloader": "^1.1.12",
"zip-webpack-plugin": "^4.0.1"
}
}
}

View File

@@ -72,16 +72,6 @@ export default function Menu(props: {
>
Verify
</MenuRow>
<MenuRow
fa="fa-solid fa-network-wired"
className="border-b border-slate-300"
onClick={() => {
props.setOpen(false);
navigate('/p2p');
}}
>
P2P
</MenuRow>
<MenuRow
className="lg:hidden"
fa="fa-solid fa-up-right-and-down-left-from-center"

View File

@@ -5,7 +5,12 @@ import React, {
useEffect,
useState,
} from 'react';
import { fetchPluginHashes, removePlugin, runPlugin } from '../../utils/rpc';
import {
fetchPluginHashes,
removePlugin,
runPlugin,
addPlugin,
} from '../../utils/rpc';
import { usePluginHashes } from '../../reducers/plugins';
import {
getPluginConfig,
@@ -31,20 +36,78 @@ export function PluginList({
className,
unremovable,
onClick,
showAddButton = false,
}: {
className?: string;
unremovable?: boolean;
onClick?: (hash: string) => void;
showAddButton?: boolean;
}): ReactElement {
const hashes = usePluginHashes();
const [uploading, setUploading] = useState(false);
useEffect(() => {
fetchPluginHashes();
}, []);
const handleFileUpload = useCallback(
async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
if (!file.name.endsWith('.wasm')) {
alert('Please select a .wasm file');
return;
}
setUploading(true);
try {
const arrayBuffer = await file.arrayBuffer();
const hex = Buffer.from(arrayBuffer).toString('hex');
const url = `file://${file.name}`;
await addPlugin(hex, url);
await fetchPluginHashes();
} catch (error: any) {
alert(`Failed to add plugin: ${error.message}`);
} finally {
setUploading(false);
e.target.value = '';
}
},
[],
);
return (
<div className={classNames('flex flex-col flex-nowrap gap-1', className)}>
{!hashes.length && (
{showAddButton && (
<div className="relative">
<input
type="file"
accept=".wasm"
onChange={handleFileUpload}
disabled={uploading}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10"
/>
<button
className="flex flex-row items-center justify-center gap-2 p-3 border-2 border-dashed border-slate-300 rounded-lg text-slate-500 hover:text-slate-700 hover:border-slate-400 transition-colors cursor-pointer w-full"
disabled={uploading}
>
{uploading ? (
<>
<Icon fa="fa-solid fa-spinner" className="animate-spin" />
<span>Adding Plugin...</span>
</>
) : (
<>
<Icon fa="fa-solid fa-plus" />
<span>Add Plugin (.wasm file)</span>
</>
)}
</button>
</div>
)}
{!hashes.length && !showAddButton && (
<div className="flex flex-col items-center justify-center text-slate-400 cursor-default select-none">
<div>No available plugins</div>
</div>

View File

@@ -1,28 +0,0 @@
import NodeCache from 'node-cache';
let RequestsLogs: {
[tabId: string]: NodeCache;
} = {};
export const deleteCacheByTabId = (tabId: number) => {
delete RequestsLogs[tabId];
};
export const getCacheByTabId = (tabId: number): NodeCache => {
RequestsLogs[tabId] =
RequestsLogs[tabId] ||
new NodeCache({
stdTTL: 60 * 5, // default 5m TTL
maxKeys: 1000000,
});
return RequestsLogs[tabId];
};
export const clearRequestCache = () => {
RequestsLogs = {};
};
export const clearCache = () => {
clearRequestCache();
};

View File

@@ -1,6 +1,12 @@
import { Level } from 'level';
import { AbstractSublevel } from 'abstract-level';
import { PluginConfig, PluginMetadata, sha256, urlify } from '../../utils/misc';
import { RequestHistory, RequestProgress } from './rpc';
import {
RequestHistory,
RequestLog,
RequestProgress,
UpsertRequestLog,
} from './rpc';
import mutex from './mutex';
import { minimatch } from 'minimatch';
const charwise = require('charwise');
@@ -23,12 +29,6 @@ const pluginMetadataDb = db.sublevel<string, PluginMetadata>('pluginMetadata', {
const connectionDb = db.sublevel<string, boolean>('connections', {
valueEncoding: 'json',
});
const cookiesDb = db.sublevel<string, boolean>('cookies', {
valueEncoding: 'json',
});
const headersDb = db.sublevel<string, boolean>('headers', {
valueEncoding: 'json',
});
const localStorageDb = db.sublevel<string, any>('sessionStorage', {
valueEncoding: 'json',
});
@@ -38,10 +38,81 @@ const sessionStorageDb = db.sublevel<string, any>('localStorage', {
const appDb = db.sublevel<string, any>('app', {
valueEncoding: 'json',
});
const requestDb = db.sublevel<string, any>('requests', {
valueEncoding: 'json',
});
enum AppDatabaseKey {
DefaultPluginsInstalled = 'DefaultPluginsInstalled',
}
export async function upsertRequestLog(request: UpsertRequestLog) {
const existing = await getRequestLog(request.requestId);
if (existing) {
await requestDb.put(request.requestId, {
...existing,
...request,
});
} else if (request.url) {
const host = urlify(request.url)?.host;
if (host) {
await requestDb.put(request.requestId, request);
await requestDb
.sublevel(request.tabId.toString())
.put(request.requestId, '');
await requestDb.sublevel(host).put(request.requestId, '');
}
}
}
export async function getRequestLog(
requestId: string,
): Promise<RequestLog | null> {
return requestDb.get(requestId).catch(() => null);
}
export async function removeRequestLog(requestId: string) {
const existing = await getRequestLog(requestId);
if (existing) {
await requestDb.del(requestId);
await requestDb.sublevel(existing.tabId.toString()).del(requestId);
const host = urlify(existing.url)?.host;
if (host) {
await requestDb.sublevel(host).del(requestId);
}
}
}
export async function removeRequestLogsByTabId(tabId: number) {
const requests = requestDb.sublevel(tabId.toString());
for await (const [requestId] of requests.iterator()) {
await removeRequestLog(requestId);
}
}
export async function getRequestLogsByTabId(tabId: number) {
const requests = requestDb.sublevel(tabId.toString());
const ret: RequestLog[] = [];
for await (const [requestId] of requests.iterator()) {
ret.push(await requestDb.get(requestId));
}
return ret;
}
export async function getRequestLogsByHost(host: string) {
const requests = requestDb.sublevel(host);
const ret: RequestLog[] = [];
for await (const [requestId] of requests.iterator()) {
ret.push(await requestDb.get(requestId));
}
return ret;
}
export async function clearAllRequestLogs() {
await requestDb.clear();
}
export async function addNotaryRequest(
now = Date.now(),
request: Omit<RequestHistory, 'status' | 'id'>,
@@ -75,6 +146,17 @@ export async function addNotaryRequestProofs(
return newReq;
}
export async function setNotaryRequestSessionId(
id: string,
sessionId: string,
): Promise<RequestHistory | null> {
const existing = await historyDb.get(id);
if (!existing) return null;
const newReq: RequestHistory = { ...existing, sessionId };
await historyDb.put(id, newReq);
return newReq;
}
export async function setNotaryRequestStatus(
id: string,
status: '' | 'pending' | 'success' | 'error',
@@ -339,48 +421,43 @@ export async function setConnection(origin: string) {
return true;
}
export async function setCookies(host: string, name: string, value: string) {
return mutex.runExclusive(async () => {
await cookiesDb.sublevel(host).put(name, value);
return true;
});
}
export async function clearCookies(host: string) {
return mutex.runExclusive(async () => {
await cookiesDb.sublevel(host).clear();
return true;
});
}
export async function getCookies(link: string, name: string) {
try {
const existing = await cookiesDb.sublevel(link).get(name);
return existing;
} catch (e) {
return null;
}
}
export async function getCookiesByHost(link: string) {
export async function getCookiesByHost(linkOrHost: string) {
const ret: { [key: string]: string } = {};
const links: { [k: string]: boolean } = {};
const url = urlify(link);
const url = urlify(linkOrHost);
const isHost = !url;
const host = isHost ? linkOrHost : url.host;
const requests = await getRequestLogsByHost(host);
for await (const sublevel of cookiesDb.keys({ keyEncoding: 'utf8' })) {
const l = sublevel.split('!')[1];
links[l] = true;
let filteredRequest: RequestLog | null = null;
for (const request of requests) {
if (isHost) {
if (!filteredRequest || filteredRequest.updatedAt > request.updatedAt) {
filteredRequest = request;
}
} else {
const { origin, pathname } = urlify(request.url) || {};
const link = [origin, pathname].join('');
if (
minimatch(link, linkOrHost) &&
(!filteredRequest || filteredRequest.updatedAt > request.updatedAt)
) {
filteredRequest = request;
}
}
}
const cookieLink = url
? Object.keys(links).filter((l) => minimatch(l, link))[0]
: Object.keys(links).filter((l) => urlify(l)?.host === link)[0];
if (!filteredRequest) return ret;
if (!cookieLink) return ret;
for await (const [key, value] of cookiesDb.sublevel(cookieLink).iterator()) {
ret[key] = value;
for (const header of filteredRequest.requestHeaders) {
if (header.name.toLowerCase() === 'cookie') {
header.value?.split(';').forEach((cookie) => {
const [name, value] = cookie.split('=');
ret[name.trim()] = value.trim();
});
}
}
return ret;
}
@@ -400,49 +477,40 @@ export async function getConnection(origin: string) {
return null;
}
}
export async function setHeaders(link: string, name: string, value?: string) {
if (!value) return null;
return mutex.runExclusive(async () => {
await headersDb.sublevel(link).put(name, value);
return true;
});
}
export async function clearHeaders(host: string) {
return mutex.runExclusive(async () => {
await headersDb.sublevel(host).clear();
return true;
});
}
export async function getHeaders(host: string, name: string) {
try {
const existing = await headersDb.sublevel(host).get(name);
return existing;
} catch (e) {
return null;
}
}
export async function getHeadersByHost(link: string) {
export async function getHeadersByHost(linkOrHost: string) {
const ret: { [key: string]: string } = {};
const url = urlify(link);
const url = urlify(linkOrHost);
const isHost = !url;
const host = isHost ? linkOrHost : url.host;
const requests = await getRequestLogsByHost(host);
const links: { [k: string]: boolean } = {};
for await (const sublevel of headersDb.keys({ keyEncoding: 'utf8' })) {
const l = sublevel.split('!')[1];
links[l] = true;
let filteredRequest: RequestLog | null = null;
for (const request of requests) {
if (isHost) {
if (!filteredRequest || filteredRequest.updatedAt > request.updatedAt) {
filteredRequest = request;
}
} else {
const { origin, pathname } = urlify(request.url) || {};
const link = [origin, pathname].join('');
if (
minimatch(link, linkOrHost) &&
(!filteredRequest || filteredRequest.updatedAt > request.updatedAt)
) {
filteredRequest = request;
}
}
}
const headerLink = url
? Object.keys(links).filter((l) => minimatch(l, link))[0]
: Object.keys(links).filter((l) => urlify(l)?.host === link)[0];
if (!filteredRequest) return ret;
if (!headerLink) return ret;
for await (const [key, value] of headersDb.sublevel(headerLink).iterator()) {
ret[key] = value;
for (const header of filteredRequest.requestHeaders) {
if (header.name.toLowerCase() !== 'cookie') {
ret[header.name] = header.value || '';
}
}
return ret;
}
@@ -515,3 +583,62 @@ export async function getAppState() {
defaultPluginsInstalled: await getDefaultPluginsInstalled(),
};
}
export async function resetDB() {
return mutex.runExclusive(async () => {
return Promise.all([
localStorageDb.clear(),
sessionStorageDb.clear(),
requestDb.clear(),
]);
});
}
export async function getDBSizeByRoot(
rootDB: AbstractSublevel<Level, any, any, any>,
): Promise<number> {
return new Promise(async (resolve, reject) => {
let size = 0;
for await (const sublevel of rootDB.keys({ keyEncoding: 'utf8' })) {
const link = sublevel.split('!')[1];
const sub = rootDB.sublevel(link);
for await (const [key, value] of sub.iterator()) {
size += key.length + value.length;
}
}
resolve(size);
});
}
export async function getRecursiveDBSize(
db: AbstractSublevel<Level, any, any, any>,
): Promise<number> {
return new Promise(async (resolve, reject) => {
let size = 0;
for await (const sublevel of db.keys({ keyEncoding: 'utf8' })) {
const parts = sublevel.split('!');
if (parts.length === 1) {
const value = await db.get(parts[0]);
size += parts[0].length + (value ? JSON.stringify(value).length : 0);
} else {
const sub = db.sublevel(parts[1]);
size +=
(await getRecursiveDBSize(
sub as unknown as AbstractSublevel<Level, any, any, any>,
)) + parts[1].length;
}
}
resolve(size);
});
}
export async function getDBSize(): Promise<number> {
const sizes = await Promise.all([
getDBSizeByRoot(localStorageDb),
getDBSizeByRoot(sessionStorageDb),
getRecursiveDBSize(requestDb),
]);
return sizes.reduce((a, b) => a + b, 0);
}

View File

@@ -1,10 +1,10 @@
import { getCacheByTabId } from './cache';
import { BackgroundActiontype, RequestLog } from './rpc';
import { BackgroundActiontype } from './rpc';
import mutex from './mutex';
import browser from 'webextension-polyfill';
import { addRequest } from '../../reducers/requests';
import { urlify } from '../../utils/misc';
import { getHeadersByHost, setCookies, setHeaders } from './db';
import { getRequestLog, upsertRequestLog } from './db';
export const onSendHeaders = (
details: browser.WebRequest.OnSendHeadersDetailsType,
) => {
@@ -12,40 +12,22 @@ export const onSendHeaders = (
const { method, tabId, requestId } = details;
if (method !== 'OPTIONS') {
const cache = getCacheByTabId(tabId);
const existing = cache.get<RequestLog>(requestId);
const { origin, pathname } = urlify(details.url) || {};
const link = [origin, pathname].join('');
if (link && details.requestHeaders) {
details.requestHeaders.forEach((header) => {
const { name, value } = header;
if (/^cookie$/i.test(name) && value) {
value.split(';').forEach((cookieStr) => {
const index = cookieStr.indexOf('=');
if (index !== -1) {
const cookieName = cookieStr.slice(0, index).trim();
const cookieValue = cookieStr.slice(index + 1);
setCookies(link, cookieName, cookieValue);
}
});
} else {
setHeaders(link, name, value);
}
upsertRequestLog({
method: details.method as 'GET' | 'POST',
type: details.type,
url: details.url,
initiator: details.initiator || null,
requestHeaders: details.requestHeaders || [],
tabId: tabId,
requestId: requestId,
updatedAt: Date.now(),
});
}
cache.set(requestId, {
...existing,
method: details.method as 'GET' | 'POST',
type: details.type,
url: details.url,
initiator: details.initiator || null,
requestHeaders: details.requestHeaders || [],
tabId: tabId,
requestId: requestId,
});
}
});
};
@@ -59,24 +41,30 @@ export const onBeforeRequest = (
if (method === 'OPTIONS') return;
if (requestBody) {
const cache = getCacheByTabId(tabId);
const existing = cache.get<RequestLog>(requestId);
if (requestBody.raw && requestBody.raw[0]?.bytes) {
try {
cache.set(requestId, {
...existing,
await upsertRequestLog({
requestBody: Buffer.from(requestBody.raw[0].bytes).toString(
'utf-8',
),
requestId: requestId,
tabId: tabId,
updatedAt: Date.now(),
});
} catch (e) {
console.error(e);
}
} else if (requestBody.formData) {
cache.set(requestId, {
...existing,
formData: requestBody.formData,
await upsertRequestLog({
formData: Object.fromEntries(
Object.entries(requestBody.formData).map(([key, value]) => [
key,
Array.isArray(value) ? value : [value],
]),
),
requestId: requestId,
tabId: tabId,
updatedAt: Date.now(),
});
}
}
@@ -91,12 +79,7 @@ export const onResponseStarted = (
if (method === 'OPTIONS') return;
const cache = getCacheByTabId(tabId);
const existing = cache.get<RequestLog>(requestId);
const newLog: RequestLog = {
requestHeaders: [],
...existing,
await upsertRequestLog({
method: details.method,
type: details.type,
url: details.url,
@@ -104,9 +87,15 @@ export const onResponseStarted = (
tabId: tabId,
requestId: requestId,
responseHeaders,
};
updatedAt: Date.now(),
});
cache.set(requestId, newLog);
const newLog = await getRequestLog(requestId);
if (!newLog) {
console.error('Request log not found', requestId);
return;
}
chrome.runtime.sendMessage({
type: BackgroundActiontype.push_action,

View File

@@ -1,7 +1,11 @@
import { onBeforeRequest, onResponseStarted, onSendHeaders } from './handlers';
import { deleteCacheByTabId } from './cache';
import browser from 'webextension-polyfill';
import { getAppState, removePlugin, setDefaultPluginsInstalled } from './db';
import {
getAppState,
removePlugin,
removeRequestLogsByTabId,
setDefaultPluginsInstalled,
} from './db';
import { installPlugin } from './plugins/utils';
(async () => {
@@ -30,7 +34,7 @@ import { installPlugin } from './plugins/utils';
);
browser.tabs.onRemoved.addListener((tabId) => {
deleteCacheByTabId(tabId);
removeRequestLogsByTabId(tabId);
});
const { defaultPluginsInstalled } = await getAppState();

View File

@@ -1,5 +1,4 @@
import browser from 'webextension-polyfill';
import { clearCache, getCacheByTabId } from './cache';
import { addRequestHistory, setRequests } from '../../reducers/history';
import {
addNotaryRequest,
@@ -24,6 +23,9 @@ import {
setLocalStorage,
setSessionStorage,
setNotaryRequestProgress,
getRequestLogsByTabId,
clearAllRequestLogs,
setNotaryRequestSessionId,
} from './db';
import { addOnePlugin, removeOnePlugin } from '../../reducers/plugins';
import {
@@ -54,6 +56,7 @@ import {
sendMessage,
sendPairedMessage,
} from './ws';
import { parseHttpMessage } from '../../utils/parser';
import { mapStringToRange, subtractRanges } from 'tlsn-js';
import { PresentationJSON } from 'tlsn-js/build/types';
@@ -69,6 +72,7 @@ export enum BackgroundActiontype {
get_prove_requests = 'get_prove_requests',
prove_request_start = 'prove_request_start',
process_prove_request = 'process_prove_request',
add_notary_request = 'add_notary_request',
finish_prove_request = 'finish_prove_request',
update_request_progress = 'update_request_progress',
verify_prove_request = 'verify_prove_request',
@@ -141,6 +145,23 @@ export type RequestLog = {
[k: string]: string[];
};
responseHeaders?: browser.WebRequest.HttpHeaders;
updatedAt: number;
};
export type UpsertRequestLog = {
requestId: string;
tabId: number;
method?: string;
type?: string;
url?: string;
initiator?: string | null;
requestHeaders?: browser.WebRequest.HttpHeaders;
requestBody?: string;
formData?: {
[k: string]: string[];
};
responseHeaders?: browser.WebRequest.HttpHeaders;
updatedAt: number;
};
export enum RequestProgress {
@@ -203,6 +224,7 @@ export type RequestHistory = {
metadata?: {
[k: string]: string;
};
sessionId?: string;
};
export const initRPC = () => {
@@ -212,10 +234,12 @@ export const initRPC = () => {
case BackgroundActiontype.get_requests:
return handleGetRequests(request, sendResponse);
case BackgroundActiontype.clear_requests:
clearCache();
clearAllRequestLogs().then(() => pushToRedux(setRequests([])));
return sendResponse();
case BackgroundActiontype.get_prove_requests:
return handleGetProveRequests(request, sendResponse);
case BackgroundActiontype.add_notary_request:
return handleAddNotaryRequest(request, sendResponse);
case BackgroundActiontype.finish_prove_request:
return handleFinishProveRequest(request, sendResponse);
case BackgroundActiontype.update_request_progress:
@@ -337,6 +361,7 @@ export const initRPC = () => {
pluginHash: request.data,
}).then(sendResponse);
return;
case BackgroundActiontype.get_p2p_state:
getP2PState();
return;
@@ -351,10 +376,7 @@ function handleGetRequests(
request: BackgroundAction,
sendResponse: (data?: any) => void,
): boolean {
const cache = getCacheByTabId(request.data);
const keys = cache.keys() || [];
const data = keys.map((key) => cache.get(key));
sendResponse(data);
getRequestLogsByTabId(request.data).then(sendResponse);
return true;
}
@@ -380,8 +402,9 @@ async function handleFinishProveRequest(
request: BackgroundAction,
sendResponse: (data?: any) => void,
) {
const { id, proof, error, verification } = request.data;
const { id, proof, error, verification, sessionId } = request.data;
console.log('handleFinishProveRequest', request.data);
if (proof) {
const newReq = await addNotaryRequestProofs(id, proof);
if (!newReq) return;
@@ -403,6 +426,12 @@ async function handleFinishProveRequest(
await pushToRedux(addRequestHistory(await getNotaryRequest(id)));
}
if (sessionId) {
const newReq = await setNotaryRequestSessionId(id, sessionId);
if (!newReq) return;
await pushToRedux(addRequestHistory(await getNotaryRequest(id)));
}
return sendResponse();
}
@@ -696,24 +725,38 @@ async function runP2PPluginProver(request: BackgroundAction, now = Date.now()) {
websocketProxyUrl: _websocketProxyUrl,
maxSentData: _maxSentData,
maxRecvData: _maxRecvData,
clientId,
verifierPlugin,
notaryUrl,
} = request.data;
const rendezvousApi = await getRendezvousApi();
const proverUrl = `${rendezvousApi}?clientId=${clientId}:proof`;
const websocketProxyUrl = _websocketProxyUrl || (await getProxyApi());
const maxSentData = _maxSentData || (await getMaxSent());
const maxRecvData = _maxRecvData || (await getMaxRecv());
const { id } = await addNotaryRequest(now, {
url,
method,
headers,
body,
notaryUrl,
websocketProxyUrl,
maxRecvData,
maxSentData,
secretHeaders,
secretResps: [],
});
await browser.runtime.sendMessage({
type: OffscreenActionTypes.start_p2p_prover,
data: {
id,
pluginUrl,
pluginHex,
url,
method,
headers,
body,
proverUrl,
proverUrl: notaryUrl,
verifierPlugin,
websocketProxyUrl,
maxRecvData,
maxSentData,
@@ -1051,10 +1094,11 @@ async function handleRunPluginByURLRequest(request: BackgroundAction) {
const onPluginRequest = async (req: any) => {
if (req.type !== SidePanelActionTypes.execute_plugin_response) return;
console.log('onPluginRequest', req.data);
if (req.data.url !== url) return;
if (req.data.error) defer.reject(req.data.error);
if (req.data.proof) defer.resolve(req.data.proof);
if (req.data.sessionId) defer.resolve(req.data.sessionId);
browser.runtime.onMessage.removeListener(onPluginRequest);
};

View File

@@ -69,6 +69,7 @@ export const connectSession = async () => {
if (state.socket) return;
const rendezvousAPI = await getRendezvousApi();
const socket = new WebSocket(rendezvousAPI);
socket.onopen = () => {
@@ -316,9 +317,26 @@ export const connectSession = async () => {
break;
}
};
socket.onerror = () => {
console.error('Error connecting to websocket');
socket.onerror = (error) => {
console.error('Error connecting to websocket:', error);
pushToRedux(setConnected(false));
pushToRedux(
setP2PError(
'Failed to connect to rendezvous server. Please check your connection and server URL.',
),
);
};
socket.onclose = (event) => {
console.log('WebSocket connection closed:', event.code, event.reason);
pushToRedux(setConnected(false));
if (event.code !== 1000 && event.code !== 1001) {
pushToRedux(
setP2PError(
`WebSocket connection lost: ${event.reason || 'Unknown error'}`,
),
);
}
};
};
@@ -436,8 +454,8 @@ export const endProofRequest = async (data: {
proof: VerifierOutput;
}) => {
const transcript = new Transcript({
sent: data.proof.transcript.sent,
recv: data.proof.transcript.recv,
sent: data.proof.transcript?.sent || [],
recv: data.proof.transcript?.recv || [],
});
state.presentation = {

View File

@@ -1,5 +1,5 @@
import { ContentScriptTypes, RPCClient } from './rpc';
import { PresentationJSON } from '../../utils/types';
import { PresentationJSON } from 'tlsn-js/build/types';
const client = new RPCClient();

View File

@@ -16,9 +16,8 @@ import {
} from 'tlsn-js';
import { convertNotaryWsToHttp, devlog, urlify } from '../../utils/misc';
import * as Comlink from 'comlink';
import { PresentationJSON as PresentationJSONa7 } from 'tlsn-js/build/types';
import { OffscreenActionTypes } from './types';
import { PresentationJSON } from '../../utils/types';
import { PresentationJSON } from 'tlsn-js/build/types';
import { waitForEvent } from '../utils';
import {
setNotaryRequestError,
@@ -125,7 +124,7 @@ export const onCreatePresentationRequest = async (request: any) => {
secretsHex: notarizationOutputs.secrets,
notaryUrl: notarizationOutputs.notaryUrl,
websocketProxyUrl: notarizationOutputs.websocketProxyUrl,
reveal: commit,
reveal: { ...commit, server_identity: false },
})) as TPresentation;
const json = await presentation.json();
browser.runtime.sendMessage({
@@ -134,11 +133,6 @@ export const onCreatePresentationRequest = async (request: any) => {
id,
proof: {
...json,
meta: {
...json.meta,
notaryUrl,
websocketProxyUrl,
},
},
},
});
@@ -251,6 +245,7 @@ export const startP2PVerifier = async (request: any) => {
export const startP2PProver = async (request: any) => {
const {
id,
pluginUrl,
pluginHex,
url,
@@ -263,52 +258,56 @@ export const startP2PProver = async (request: any) => {
maxSentData,
secretHeaders,
getSecretResponse,
verifierPlugin,
} = request.data;
const hostname = urlify(url)?.hostname || '';
updateRequestProgress(id, RequestProgress.CreatingProver);
const prover: TProver = await new Prover({
id: pluginUrl,
id,
serverDns: hostname,
maxSentData,
maxRecvData,
serverIdentity: true,
});
browser.runtime.sendMessage({
type: BackgroundActiontype.prover_instantiated,
data: {
pluginUrl,
updateRequestProgress(id, RequestProgress.GettingSession);
const resp = await fetch(`${proverUrl}/session`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
clientType: 'Websocket',
maxRecvData,
maxSentData,
plugin: 'plugin-js',
}),
});
const { sessionId } = await resp.json();
const _url = new URL(proverUrl);
const protocol = _url.protocol === 'https:' ? 'wss' : 'ws';
const pathname = _url.pathname;
const sessionUrl = `${protocol}://${_url.host}${pathname === '/' ? '' : pathname}/notarize?sessionId=${sessionId!}`;
const proofRequestStart = waitForEvent(
OffscreenActionTypes.start_p2p_proof_request,
updateRequestProgress(id, RequestProgress.SettingUpProver);
await prover.setup(sessionUrl);
await handleProgress(
id,
RequestProgress.SendingRequest,
() =>
prover.sendRequest(websocketProxyUrl + `?token=${hostname}`, {
url,
method,
headers,
body,
}),
`Error connecting to websocket proxy: ${websocketProxyUrl}. Please check the proxy URL and ensure it's accessible.`,
);
const proverSetup = prover.setup(proverUrl);
await new Promise((r) => setTimeout(r, 5000));
browser.runtime.sendMessage({
type: BackgroundActiontype.prover_setup,
data: {
pluginUrl,
},
});
await proverSetup;
browser.runtime.sendMessage({
type: BackgroundActiontype.prover_started,
data: {
pluginUrl,
},
});
await proofRequestStart;
await prover.sendRequest(websocketProxyUrl + `?token=${hostname}`, {
url,
method,
headers,
body,
});
updateRequestProgress(id, RequestProgress.ReadingTranscript);
const transcript = await prover.transcript();
let secretResps: string[] = [];
@@ -349,9 +348,15 @@ export const startP2PProver = async (request: any) => {
),
};
const endRequest = waitForEvent(OffscreenActionTypes.end_p2p_proof_request);
await prover.reveal(commit);
await endRequest;
await prover.reveal({ ...commit, server_identity: true });
updateRequestProgress(id, RequestProgress.FinalizingOutputs);
browser.runtime.sendMessage({
type: BackgroundActiontype.finish_prove_request,
data: {
id,
sessionId: sessionId,
},
});
};
async function createProof(options: {
@@ -368,7 +373,7 @@ async function createProof(options: {
id: string;
secretHeaders: string[];
secretResps: string[];
}): Promise<PresentationJSONa7> {
}): Promise<PresentationJSON> {
const {
url,
method = 'GET',
@@ -400,13 +405,18 @@ async function createProof(options: {
updateRequestProgress(id, RequestProgress.SettingUpProver);
await prover.setup(sessionUrl);
updateRequestProgress(id, RequestProgress.SendingRequest);
await prover.sendRequest(websocketProxyUrl + `?token=${hostname}`, {
url,
method,
headers,
body,
});
await handleProgress(
id,
RequestProgress.SendingRequest,
() =>
prover.sendRequest(websocketProxyUrl + `?token=${hostname}`, {
url,
method,
headers,
body,
}),
`Error connecting to websocket proxy: ${websocketProxyUrl}. Please check the proxy URL and ensure it's accessible.`,
);
updateRequestProgress(id, RequestProgress.ReadingTranscript);
const transcript = await prover.transcript();
@@ -436,17 +446,12 @@ async function createProof(options: {
secretsHex: notarizationOutputs.secrets,
notaryUrl: notarizationOutputs.notaryUrl,
websocketProxyUrl: notarizationOutputs.websocketProxyUrl,
reveal: commit,
reveal: { ...commit, server_identity: false },
})) as TPresentation;
const json = await presentation.json();
return {
...json,
meta: {
...json,
notaryUrl: notaryUrl,
websocketProxyUrl: websocketProxyUrl,
},
};
}
@@ -515,7 +520,7 @@ async function createProver(options: {
headers,
body,
}),
'Error sending request',
`Error connecting to websocket proxy: ${websocketProxyUrl}. Please check the proxy URL and ensure it's accessible.`,
);
return prover;
@@ -538,32 +543,29 @@ async function verifyProof(proof: PresentationJSON): Promise<{
};
switch (proof.version) {
case undefined:
case '0.1.0-alpha.7':
case '0.1.0-alpha.8':
case '0.1.0-alpha.9':
case '0.1.0-alpha.12':
result = await verify(proof);
break;
default:
result = {
sent: 'version not supported',
recv: 'version not supported',
};
break;
case '0.1.0-alpha.10':
result = await verify(proof);
break;
}
return result!;
}
async function verify(proof: PresentationJSON) {
if (proof.version !== '0.1.0-alpha.10') {
if (proof.version !== '0.1.0-alpha.12') {
throw new Error('wrong version');
}
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,
sent: verifierOutput.transcript?.sent || [],
recv: verifierOutput.transcript?.recv || [],
});
const vk = await presentation.verifyingKey();
const verifyingKey = Buffer.from(vk.data).toString('hex');
@@ -602,6 +604,75 @@ function updateRequestProgress(
});
}
function getWebsocketErrorMessage(
lowerError: string,
fallbackMessage: string,
): string {
const isWebsocketError =
lowerError.includes('websocket') ||
lowerError.includes('proxy') ||
lowerError.includes('connection') ||
lowerError.includes('network') ||
lowerError.includes('prover error') ||
lowerError.includes('io error') ||
lowerError.includes('certificate') ||
lowerError.includes('cert') ||
lowerError.includes('ssl') ||
lowerError.includes('tls');
if (!isWebsocketError) {
return fallbackMessage;
}
const errorPatterns = [
{
patterns: ['protocol', 'must use ws://', 'must use wss://'],
message:
'Invalid websocket proxy URL protocol. Please use ws:// or wss:// protocol in your websocket proxy URL settings.',
},
{
patterns: [
'not allowed',
'not whitelisted',
'forbidden',
'unauthorized',
'permission denied',
'access denied',
],
message:
'Target domain not allowed by websocket proxy. Please check if the website domain is supported by your proxy service.',
},
{
patterns: ['dns', 'resolve'],
message:
'Cannot resolve websocket proxy domain. Please check your websocket proxy URL in settings.',
},
{
patterns: ['timeout'],
message:
'Websocket proxy connection timeout. Please check your websocket proxy URL in settings and ensure the server is accessible.',
},
{
patterns: ['refused', 'unreachable'],
message:
'Cannot reach websocket proxy server. Please check your websocket proxy URL in settings and ensure the server is accessible.',
},
{
patterns: ['cert', 'certificate', 'certnotvalidforname'],
message:
'Cannot connect to websocket proxy server. Please check your websocket proxy URL in settings and ensure it points to a valid websocket proxy service.',
},
];
for (const { patterns, message } of errorPatterns) {
if (patterns.some((pattern) => lowerError.includes(pattern))) {
return message;
}
}
return 'Websocket proxy connection failed. Please check your websocket proxy URL in settings and ensure the server is accessible.';
}
async function handleProgress<T>(
id: string,
progress: RequestProgress,
@@ -612,12 +683,17 @@ async function handleProgress<T>(
updateRequestProgress(id, progress);
return await action();
} catch (error: any) {
updateRequestProgress(id, RequestProgress.Error, errorMessage);
await setNotaryRequestStatus(id, 'error');
await setNotaryRequestError(
id,
errorMessage || error.message || 'Unknown error',
const specificError = error?.message || '';
const lowerError = specificError.toLowerCase();
const finalErrorMessage = getWebsocketErrorMessage(
lowerError,
errorMessage,
);
updateRequestProgress(id, RequestProgress.Error, finalErrorMessage);
await setNotaryRequestStatus(id, 'error');
await setNotaryRequestError(id, finalErrorMessage);
throw error;
}
}

View File

@@ -44,8 +44,9 @@ export default function SidePanel(): ReactElement {
switch (type) {
case SidePanelActionTypes.execute_plugin_request: {
setConfig(await getPluginConfigByUrl(data.pluginUrl));
setUrl(data.pluginUrl);
const pluginIdentifier = data.pluginUrl || data.pluginHash;
setConfig(await getPluginConfigByUrl(pluginIdentifier));
setUrl(pluginIdentifier);
setParams(data.pluginParams);
setStarted(true);
break;
@@ -92,6 +93,7 @@ export default function SidePanel(): ReactElement {
</button>
</div>
{/*{!config && <PluginList />}*/}
{started && config && (
<PluginBody
url={url}
@@ -147,16 +149,27 @@ function PluginBody({
proof: notaryRequest.proof,
},
});
} else if (notaryRequest?.sessionId) {
browser.runtime.sendMessage({
type: SidePanelActionTypes.execute_plugin_response,
data: {
url,
sessionId: notaryRequest.sessionId,
},
});
} else if (notaryRequest?.status === 'error') {
browser.runtime.sendMessage({
type: SidePanelActionTypes.execute_plugin_response,
data: {
url,
error: notaryRequest.error,
error:
notaryRequest.errorMessage ||
notaryRequest.error ||
'Notarization failed',
},
});
}
}, [url, notaryRequest?.status]);
}, [url, notaryRequest?.status, notaryRequest?.sessionId]);
return (
<div className="flex flex-col p-4">
@@ -248,7 +261,7 @@ function StepContent(
? JSON.stringify(lastResponse)
: JSON.stringify(parameterValues),
);
const val = JSON.parse(out.string());
const val = JSON.parse(out!.string());
if (val && prover) {
setNotarizationId(val);
} else {
@@ -257,7 +270,7 @@ function StepContent(
setResponse(val, index);
} catch (e: any) {
console.error(e);
setError(e?.message || 'Unkonwn error');
setError(e?.message || 'Unknown error');
} finally {
setPending(false);
}
@@ -311,6 +324,8 @@ function StepContent(
let btnContent = null;
console.log('notaryRequest', notaryRequest);
console.log('notarizationId', notarizationId);
if (prover && p2p) {
btnContent = (
<button
@@ -322,7 +337,7 @@ function StepContent(
<span className="text-sm">View in P2P</span>
</button>
);
} else if (completed) {
} else if (completed || notaryRequest?.sessionId) {
btnContent = (
<button
className={classNames(
@@ -349,12 +364,18 @@ function StepContent(
btnContent = (
<div className="flex flex-col gap-2">
{notaryRequest?.progress === RequestProgress.Error && (
<div className="flex flex-row items-center gap-2 text-red-600">
<Icon fa="fa-solid fa-triangle-exclamation" size={1} />
<span className="text-sm">
{notaryRequest?.errorMessage ||
progressText(notaryRequest.progress)}
</span>
<div className="flex flex-col gap-1">
<div className="flex flex-row items-start gap-2 text-red-600">
<Icon
fa="fa-solid fa-triangle-exclamation"
size={1}
className="mt-0.5"
/>
<span className="text-sm">
{notaryRequest?.errorMessage ||
progressText(notaryRequest.progress)}
</span>
</div>
</div>
)}
{notaryRequest?.progress !== RequestProgress.Error && (
@@ -397,7 +418,18 @@ function StepContent(
{!!description && (
<div className="text-slate-500 text-sm">{description}</div>
)}
{!!error && <div className="text-red-500 text-sm">{error}</div>}
{!!error && (
<div className="flex flex-col gap-1">
<div className="flex flex-row items-start gap-2 text-red-600">
<Icon
fa="fa-solid fa-triangle-exclamation"
size={1}
className="mt-0.5"
/>
<div className="text-red-500 text-sm">{error}</div>
</div>
</div>
)}
{btnContent}
</div>
</div>

View File

@@ -11,17 +11,23 @@ import History from '../History';
import './index.scss';
import Requests from '../Requests';
import { fetchPluginHashes } from '../../utils/rpc';
import { PluginList } from '../../components/PluginList';
import { getDeveloperMode } from '../../utils/storage';
export default function Home(props: {
tab?: 'history' | 'network';
}): ReactElement {
const [error, showError] = useState('');
const [tab, setTab] = useState<'history' | 'network'>(props.tab || 'history');
const [tab, setTab] = useState<'history' | 'network' | 'plugins'>(
props.tab || 'history',
);
const scrollableContent = useRef<HTMLDivElement | null>(null);
const [shouldFix, setFix] = useState(false);
const [developerMode, setDeveloperMode] = useState(false);
useEffect(() => {
fetchPluginHashes();
getDeveloperMode().then(setDeveloperMode);
}, []);
useEffect(() => {
@@ -71,10 +77,24 @@ export default function Home(props: {
>
History
</TabSelector>
{developerMode && (
<TabSelector
onClick={() => setTab('plugins')}
selected={tab === 'plugins'}
>
Plugins
</TabSelector>
)}
</div>
<div className="flex-grow">
{tab === 'history' && <History />}
{tab === 'network' && <Requests shouldFix={shouldFix} />}
{tab === 'plugins' && (
<PluginList
className="p-2 overflow-y-auto"
showAddButton={developerMode}
/>
)}
</div>
</div>
);

View File

@@ -19,6 +19,8 @@ import {
LOGGING_FILTER_KEY,
getRendezvousApi,
RENDEZVOUS_API_LS_KEY,
getDeveloperMode,
DEVELOPER_MODE_LS_KEY,
} from '../../utils/storage';
import {
EXPLORER_API,
@@ -32,6 +34,7 @@ import Modal, { ModalContent } from '../../components/Modal/Modal';
import browser from 'webextension-polyfill';
import { LoggingLevel } from 'tlsn-js';
import { version } from '../../../package.json';
import { getDBSize, resetDB } from '../../entries/Background/db';
export default function Options(): ReactElement {
const [notary, setNotary] = useState(NOTARY_API);
@@ -40,16 +43,28 @@ export default function Options(): ReactElement {
const [maxReceived, setMaxReceived] = useState(MAX_RECV);
const [loggingLevel, setLoggingLevel] = useState<LoggingLevel>('Info');
const [rendezvous, setRendezvous] = useState(RENDEZVOUS_API);
const [developerMode, setDeveloperMode] = useState(false);
const [dirty, setDirty] = useState(false);
const [shouldReload, setShouldReload] = useState(false);
const [advanced, setAdvanced] = useState(false);
const [showReloadModal, setShowReloadModal] = useState(false);
const [dbSize, setDbSize] = useState(0);
const [isCalculatingDbSize, setIsCalculatingDbSize] = useState(false);
useEffect(() => {
(async () => {
setIsCalculatingDbSize(true);
setDbSize(await getDBSize());
setIsCalculatingDbSize(false);
})();
}, []);
useEffect(() => {
(async () => {
setNotary(await getNotaryApi());
setProxy(await getProxyApi());
setDeveloperMode(await getDeveloperMode());
})();
}, []);
@@ -74,6 +89,7 @@ export default function Options(): ReactElement {
await set(MAX_RECEIVED_LS_KEY, maxReceived.toString());
await set(LOGGING_FILTER_KEY, loggingLevel);
await set(RENDEZVOUS_API_LS_KEY, rendezvous);
await set(DEVELOPER_MODE_LS_KEY, developerMode.toString());
setDirty(false);
},
[
@@ -83,6 +99,7 @@ export default function Options(): ReactElement {
maxReceived,
loggingLevel,
rendezvous,
developerMode,
shouldReload,
],
);
@@ -103,6 +120,13 @@ export default function Options(): ReactElement {
browser.tabs.create({ url });
}, []);
const onCleanCache = useCallback(async () => {
setIsCalculatingDbSize(true);
await resetDB();
setDbSize(await getDBSize());
setIsCalculatingDbSize(false);
}, []);
return (
<div className="flex flex-col flex-nowrap flex-grow overflow-y-auto">
{showReloadModal && (
@@ -139,6 +163,8 @@ export default function Options(): ReactElement {
proxy={proxy}
setProxy={setProxy}
setDirty={setDirty}
developerMode={developerMode}
setDeveloperMode={setDeveloperMode}
/>
<div className="justify-left px-2 pt-3 gap-2">
<button className="font-bold" onClick={onAdvanced}>
@@ -192,6 +218,15 @@ export default function Options(): ReactElement {
>
Join our Discord
</button>
<button className="button" onClick={onCleanCache}>
<span>Clean Cache (</span>
{isCalculatingDbSize ? (
<i className="fa-solid fa-spinner fa-spin"></i>
) : (
<span>{(dbSize / 1024 / 1024).toFixed(2)} MB</span>
)}
<span>)</span>
</button>
</div>
</div>
);
@@ -228,8 +263,18 @@ function NormalOptions(props: {
proxy: string;
setProxy: (value: string) => void;
setDirty: (value: boolean) => void;
developerMode: boolean;
setDeveloperMode: (value: boolean) => void;
}) {
const { notary, setNotary, proxy, setProxy, setDirty } = props;
const {
notary,
setNotary,
proxy,
setProxy,
setDirty,
developerMode,
setDeveloperMode,
} = props;
return (
<div>
@@ -261,6 +306,33 @@ function NormalOptions(props: {
<div className="font-semibold">Explorer URL</div>
<div className="input border bg-slate-100">{EXPLORER_API}</div>
</div>
<div className="flex flex-row items-center py-3 px-2 gap-2">
<div className="font-semibold">Developer Mode</div>
<div className="relative inline-block w-9 h-5">
<input
type="checkbox"
id="developer-mode"
checked={developerMode}
onChange={(e) => {
setDeveloperMode(e.target.checked);
setDirty(true);
}}
className="sr-only"
/>
<label
htmlFor="developer-mode"
className={`block h-5 rounded-full cursor-pointer transition-all duration-300 ease-in-out ${
developerMode ? 'bg-blue-500' : 'bg-gray-300'
}`}
>
<span
className={`absolute top-0.5 left-0.5 w-4 h-4 bg-white rounded-full shadow-sm transform transition-all duration-300 ease-in-out ${
developerMode ? 'translate-x-4' : 'translate-x-0'
}`}
/>
</label>
</div>
</div>
</div>
);
}
@@ -312,15 +384,6 @@ function AdvancedOptions(props: {
setDirty(true);
}}
/>
<InputField
label="Rendezvous API (for P2P)"
value={rendezvous}
type="text"
onChange={(e) => {
setRendezvous(e.target.value);
setDirty(true);
}}
/>
<div className="flex flex-col flex-nowrap py-1 px-2 gap-2">
<div className="font-semibold">Logging Level</div>
<select

View File

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

View File

@@ -221,11 +221,20 @@ export const makePlugin = async (
}
(async () => {
const { getSecretResponse, body: reqBody } = params;
const {
getSecretResponse,
body: reqBody,
interactive,
verifierPlugin,
} = params;
if (meta?.p2p) {
console.log('interactive', interactive);
console.log('verifierPlugin', verifierPlugin);
console.log('params', params);
if (interactive) {
const pluginHex = Buffer.from(arrayBuffer).toString('hex');
const pluginUrl = await sha256(pluginHex);
handleExecP2PPluginProver({
type: BackgroundActiontype.execute_p2p_plugin_prover,
data: {
@@ -234,7 +243,7 @@ export const makePlugin = async (
pluginHex,
body: reqBody,
now,
clientId: meta.clientId,
verifierPlugin,
},
});
} else {

View File

@@ -8,6 +8,7 @@ export const MAX_SENT_LS_KEY = 'max-sent';
export const MAX_RECEIVED_LS_KEY = 'max-received';
export const LOGGING_FILTER_KEY = 'logging-filter-2';
export const RENDEZVOUS_API_LS_KEY = 'rendezvous-api';
export const DEVELOPER_MODE_LS_KEY = 'developer-mode';
export async function set(key: string, value: string) {
return chrome.storage.sync.set({ [key]: value });
@@ -43,3 +44,8 @@ export async function getLoggingFilter(): Promise<LoggingLevel> {
export async function getRendezvousApi(): Promise<string> {
return await get(RENDEZVOUS_API_LS_KEY, RENDEZVOUS_API);
}
export async function getDeveloperMode(): Promise<boolean> {
const value = await get(DEVELOPER_MODE_LS_KEY, 'false');
return value === 'true';
}

View File

@@ -1,10 +0,0 @@
import { PresentationJSON as PresentationJSONa7 } from 'tlsn-js/build/types';
export type PresentationJSON = PresentationJSONa5 | PresentationJSONa7;
export type PresentationJSONa5 = {
version?: undefined;
session: any;
substrings: any;
notaryUrl: string;
};

View File

@@ -22,6 +22,28 @@ config.plugins = (config.plugins || []).concat(
}),
);
webpack(config, function (err) {
if (err) throw err;
webpack(config, function (err, stats) {
if (err) {
console.error('Webpack error:', err);
process.exit(1);
}
if (stats.hasErrors()) {
console.error('Build failed with errors:');
const info = stats.toJson();
console.error(info.errors.map((e) => e.message).join('\n\n'));
process.exit(1);
}
if (stats.hasWarnings()) {
console.warn('Build completed with warnings:');
const info = stats.toJson();
console.warn(info.warnings.map((w) => w.message).join('\n\n'));
}
console.log('Build completed successfully!');
console.log(`Output: ${path.join(__dirname, '../', 'build')}`);
console.log(
`Zip: ${path.join(__dirname, '../', 'zip', `${packageInfo.name}-${packageInfo.version}.zip`)}`,
);
});