mirror of
https://github.com/tlsnotary/tlsn-extension.git
synced 2026-01-10 13:38:08 -05:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
303448a3c1 | ||
|
|
46674491c6 | ||
|
|
0ba4a71bba | ||
|
|
ade6d7e575 | ||
|
|
3f37c1aee8 | ||
|
|
5545d7abed | ||
|
|
b674f5c579 | ||
|
|
49a94e1bbc | ||
|
|
64d572d663 | ||
|
|
2f35278235 | ||
|
|
5af783e48c | ||
|
|
1ea69d0574 | ||
|
|
8bfcf8c8d5 | ||
|
|
fe825d7ebb | ||
|
|
ca4986c3bb | ||
|
|
da756721d4 | ||
|
|
38852620d2 | ||
|
|
1c9f340add |
46
.github/workflows/build.yaml
vendored
46
.github/workflows/build.yaml
vendored
@@ -1,46 +0,0 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
70
.github/workflows/ci.yaml
vendored
Normal file
70
.github/workflows/ci.yaml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: ci
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build-lint-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Test Webpack Build
|
||||
run: npm run build:webpack
|
||||
|
||||
- name: Save extension zip file for releases
|
||||
if: github.event_name == 'release'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tlsn-extension-${{ github.ref_name }}.zip
|
||||
path: ./zip/tlsn-extension-${{ github.ref_name }}.zip
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
if: github.event_name == 'release'
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-lint-test
|
||||
steps:
|
||||
- name: Download extension from build-lint-test job
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: tlsn-extension-${{ github.ref_name }}.zip
|
||||
path: ./tlsn-extension-${{ github.ref_name }}.zip
|
||||
|
||||
- name: 📦 Add extension zip file to release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release upload "${{ github.event.release.tag_name }}" \
|
||||
./tlsn-extension-${{ github.ref_name }}.zip \
|
||||
--clobber
|
||||
|
||||
# Get tokens as documented on
|
||||
# * https://developer.chrome.com/docs/webstore/using-api#beforeyoubegin
|
||||
# * https://github.com/fregante/chrome-webstore-upload-keys?tab=readme-ov-file
|
||||
- name: 💨 Publish to chrome store
|
||||
uses: browser-actions/release-chrome-extension@latest # https://github.com/browser-actions/release-chrome-extension/tree/latest/
|
||||
with:
|
||||
extension-id: "gcfkkledipjbgdbimfpijgbkhajiaaph"
|
||||
extension-path: tlsn-extension-${{ github.ref_name }}.zip
|
||||
oauth-client-id: ${{ secrets.OAUTH_CLIENT_ID }}
|
||||
oauth-client-secret: ${{ secrets.OAUTH_CLIENT_SECRET }}
|
||||
oauth-refresh-token: ${{ secrets.OAUTH_REFRESH_TOKEN }}
|
||||
46
.github/workflows/lint.yaml
vendored
46
.github/workflows/lint.yaml
vendored
@@ -1,46 +0,0 @@
|
||||
name: lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@ bin/
|
||||
build
|
||||
tlsn/
|
||||
zip
|
||||
.vscode
|
||||
@@ -25,7 +25,11 @@ at your option.
|
||||
|
||||
## Installing and Running
|
||||
|
||||
### Procedures:
|
||||
The easiest way to install the TLSN browser extension is to use the [Chrome Web Store](https://chromewebstore.google.com/detail/tlsn-extension/gcfkkledipjbgdbimfpijgbkhajiaaph).
|
||||
|
||||
You can also build and run it locally as explained in the following steps.
|
||||
|
||||
### Procedure:
|
||||
|
||||
1. Check if your [Node.js](https://nodejs.org/) version is >= **18**.
|
||||
2. Clone this repository.
|
||||
|
||||
15351
package-lock.json
generated
15351
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tlsn-extension",
|
||||
"version": "0.1.0.704",
|
||||
"version": "0.1.0.1000",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -16,7 +16,7 @@
|
||||
"lint:fix": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@extism/extism": "^1.0.2",
|
||||
"@extism/extism": "^1.0.3",
|
||||
"@fortawesome/fontawesome-free": "^6.4.2",
|
||||
"async-mutex": "^0.4.0",
|
||||
"buffer": "^6.0.3",
|
||||
@@ -27,6 +27,7 @@
|
||||
"dayjs": "^1.11.13",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fuse.js": "^6.6.2",
|
||||
"http-parser-js": "^0.5.9",
|
||||
"level": "^8.0.0",
|
||||
"minimatch": "^9.0.4",
|
||||
"node-cache": "^5.1.2",
|
||||
@@ -39,8 +40,7 @@
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-thunk": "^2.4.2",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tlsn-js": "0.1.0-alpha.7.1",
|
||||
"tlsn-js-v5": "npm:tlsn-js@0.1.0-alpha.5.4"
|
||||
"tlsn-js": "0.1.0-alpha.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.12",
|
||||
@@ -94,4 +94,4 @@
|
||||
"webpack-ext-reloader": "^1.1.12",
|
||||
"zip-webpack-plugin": "^4.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
8652
pnpm-lock.yaml
generated
8652
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -115,14 +115,15 @@ export async function setNotaryRequestError(
|
||||
export async function setNotaryRequestProgress(
|
||||
id: string,
|
||||
progress: RequestProgress,
|
||||
errorMessage?: string,
|
||||
): Promise<RequestHistory | null> {
|
||||
const existing = await historyDb.get(id);
|
||||
|
||||
if (!existing) return null;
|
||||
|
||||
const newReq: RequestHistory = {
|
||||
...existing,
|
||||
progress,
|
||||
errorMessage,
|
||||
};
|
||||
|
||||
await historyDb.put(id, newReq);
|
||||
@@ -132,7 +133,12 @@ export async function setNotaryRequestProgress(
|
||||
|
||||
export async function setNotaryRequestVerification(
|
||||
id: string,
|
||||
verification: { sent: string; recv: string },
|
||||
verification: {
|
||||
sent: string;
|
||||
recv: string;
|
||||
verifierKey: string;
|
||||
notaryKey?: string;
|
||||
},
|
||||
): Promise<RequestHistory | null> {
|
||||
const existing = await historyDb.get(id);
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ import {
|
||||
hexToArrayBuffer,
|
||||
makePlugin,
|
||||
PluginConfig,
|
||||
safeParseJSON,
|
||||
} from '../../utils/misc';
|
||||
import {
|
||||
getLoggingFilter,
|
||||
@@ -51,8 +50,6 @@ import { deferredPromise } from '../../utils/promise';
|
||||
import { minimatch } from 'minimatch';
|
||||
import { OffscreenActionTypes } from '../Offscreen/types';
|
||||
import { SidePanelActionTypes } from '../SidePanel/types';
|
||||
import { subtractRanges } from '../Offscreen/utils';
|
||||
import { mapSecretsToRange } from './plugins/utils';
|
||||
import { pushToRedux } from '../utils';
|
||||
import {
|
||||
connectSession,
|
||||
@@ -64,6 +61,9 @@ import {
|
||||
sendMessage,
|
||||
sendPairedMessage,
|
||||
} from './ws';
|
||||
import { parseHttpMessage } from '../../utils/parser';
|
||||
import { mapStringToRange, subtractRanges } from 'tlsn-js';
|
||||
import { PresentationJSON } from 'tlsn-js/build/types';
|
||||
|
||||
const charwise = require('charwise');
|
||||
|
||||
@@ -167,9 +167,13 @@ export enum RequestProgress {
|
||||
SendingRequest,
|
||||
ReadingTranscript,
|
||||
FinalizingOutputs,
|
||||
Error,
|
||||
}
|
||||
|
||||
export function progressText(progress: RequestProgress): string {
|
||||
export function progressText(
|
||||
progress: RequestProgress,
|
||||
errorMessage?: string,
|
||||
): string {
|
||||
switch (progress) {
|
||||
case RequestProgress.CreatingProver:
|
||||
return 'Creating prover...';
|
||||
@@ -183,6 +187,8 @@ export function progressText(progress: RequestProgress): string {
|
||||
return 'Reading request transcript...';
|
||||
case RequestProgress.FinalizingOutputs:
|
||||
return 'Finalizing notarization outputs...';
|
||||
case RequestProgress.Error:
|
||||
return errorMessage ? errorMessage : 'Error: Notarization Failed';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,15 +205,18 @@ export type RequestHistory = {
|
||||
status: '' | 'pending' | 'success' | 'error';
|
||||
progress?: RequestProgress;
|
||||
error?: any;
|
||||
proof?: { session: any; substrings: any };
|
||||
proof?: { session: any; substrings: any } | PresentationJSON;
|
||||
requestBody?: any;
|
||||
verification?: {
|
||||
sent: string;
|
||||
recv: string;
|
||||
verifierKey: string;
|
||||
notaryKey?: string;
|
||||
};
|
||||
secretHeaders?: string[];
|
||||
secretResps?: string[];
|
||||
cid?: string;
|
||||
errorMessage?: string;
|
||||
metadata?: {
|
||||
[k: string]: string;
|
||||
};
|
||||
@@ -428,9 +437,9 @@ async function handleUpdateRequestProgress(
|
||||
request: BackgroundAction,
|
||||
sendResponse: (data?: any) => void,
|
||||
) {
|
||||
const { id, progress } = request.data;
|
||||
const { id, progress, errorMessage } = request.data;
|
||||
|
||||
const newReq = await setNotaryRequestProgress(id, progress);
|
||||
const newReq = await setNotaryRequestProgress(id, progress, errorMessage);
|
||||
if (!newReq) return;
|
||||
await pushToRedux(addRequestHistory(await getNotaryRequest(id)));
|
||||
|
||||
@@ -551,74 +560,82 @@ async function runPluginProver(request: BackgroundAction, now = Date.now()) {
|
||||
});
|
||||
|
||||
await setNotaryRequestStatus(id, 'pending');
|
||||
|
||||
await pushToRedux(addRequestHistory(await getNotaryRequest(id)));
|
||||
|
||||
const onProverResponse = async (request: any) => {
|
||||
const { data, type } = request;
|
||||
let listenerActive = true;
|
||||
let responseListener: (request: any) => void;
|
||||
|
||||
if (type !== OffscreenActionTypes.create_prover_response) {
|
||||
return;
|
||||
}
|
||||
const proverPromise = new Promise<void>((resolve, reject) => {
|
||||
responseListener = async (request: any) => {
|
||||
if (!listenerActive) return;
|
||||
|
||||
if (data.error) {
|
||||
console.error(data.error);
|
||||
return;
|
||||
}
|
||||
const { data, type } = request;
|
||||
|
||||
if (data.id !== id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getSecretResponse) {
|
||||
const body = data.transcript.recv.split('\r\n').reduce(
|
||||
(state: { headerEnd: boolean; body: string[] }, line: string) => {
|
||||
if (state.headerEnd) {
|
||||
state.body.push(line);
|
||||
} else if (!line) {
|
||||
state.headerEnd = true;
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
{ headerEnd: false, body: [] },
|
||||
).body;
|
||||
|
||||
if (body.length == 1) {
|
||||
secretResps = await getSecretResponseFn(body[0]);
|
||||
} else {
|
||||
secretResps = await getSecretResponseFn(
|
||||
body.filter((txt: string) => {
|
||||
const json = safeParseJSON(txt);
|
||||
return typeof json === 'object';
|
||||
})[0],
|
||||
);
|
||||
if (type !== OffscreenActionTypes.create_prover_response) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const commit = {
|
||||
sent: subtractRanges(
|
||||
data.transcript.ranges.sent.all,
|
||||
mapSecretsToRange(secretHeaders, data.transcript.sent),
|
||||
),
|
||||
recv: subtractRanges(
|
||||
data.transcript.ranges.recv.all,
|
||||
mapSecretsToRange(secretResps, data.transcript.recv),
|
||||
),
|
||||
if (data.id !== id) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
|
||||
const transcript: { recv: number[]; sent: number[] } = data.transcript;
|
||||
|
||||
const { body: recvBody } = parseHttpMessage(
|
||||
Buffer.from(transcript.recv),
|
||||
'response',
|
||||
);
|
||||
|
||||
if (getSecretResponse) {
|
||||
secretResps = await getSecretResponseFn(
|
||||
...recvBody.map((body) => body.toString('utf-8')),
|
||||
);
|
||||
}
|
||||
|
||||
const commit = {
|
||||
sent: subtractRanges(
|
||||
{ start: 0, end: transcript.sent.length },
|
||||
mapStringToRange(
|
||||
secretHeaders,
|
||||
Buffer.from(transcript.sent).toString('utf-8'),
|
||||
),
|
||||
),
|
||||
recv: subtractRanges(
|
||||
{ start: 0, end: transcript.recv.length },
|
||||
mapStringToRange(
|
||||
secretResps,
|
||||
Buffer.from(transcript.recv).toString('utf-8'),
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
browser.runtime.sendMessage({
|
||||
type: OffscreenActionTypes.create_presentation_request,
|
||||
data: {
|
||||
id,
|
||||
commit,
|
||||
notaryUrl,
|
||||
websocketProxyUrl,
|
||||
},
|
||||
});
|
||||
|
||||
resolve();
|
||||
} catch (error) {
|
||||
console.error('Prover response error:', error);
|
||||
reject(error);
|
||||
} finally {
|
||||
listenerActive = false;
|
||||
browser.runtime.onMessage.removeListener(responseListener);
|
||||
}
|
||||
};
|
||||
|
||||
browser.runtime.sendMessage({
|
||||
type: OffscreenActionTypes.create_presentation_request,
|
||||
data: {
|
||||
id,
|
||||
commit,
|
||||
},
|
||||
});
|
||||
|
||||
browser.runtime.onMessage.removeListener(onProverResponse);
|
||||
};
|
||||
|
||||
browser.runtime.onMessage.addListener(onProverResponse);
|
||||
browser.runtime.onMessage.addListener(responseListener);
|
||||
});
|
||||
|
||||
browser.runtime.sendMessage({
|
||||
type: OffscreenActionTypes.create_prover_request,
|
||||
@@ -634,6 +651,34 @@ async function runPluginProver(request: BackgroundAction, now = Date.now()) {
|
||||
maxSentData,
|
||||
},
|
||||
});
|
||||
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
if (listenerActive) {
|
||||
listenerActive = false;
|
||||
browser.runtime.onMessage.removeListener(responseListener);
|
||||
reject(new Error('Notarization Timed Out'));
|
||||
}
|
||||
// 3 minute timeout
|
||||
}, 180000);
|
||||
});
|
||||
|
||||
try {
|
||||
await Promise.race([proverPromise, timeoutPromise]);
|
||||
} catch (error: any) {
|
||||
await setNotaryRequestStatus(id, 'error');
|
||||
await setNotaryRequestError(id, error.message);
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.update_request_progress,
|
||||
data: {
|
||||
id,
|
||||
progress: RequestProgress.Error,
|
||||
error: error.message,
|
||||
},
|
||||
});
|
||||
await pushToRedux(addRequestHistory(await getNotaryRequest(id)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleGetSecretsFromTranscript(
|
||||
@@ -646,32 +691,15 @@ async function handleGetSecretsFromTranscript(
|
||||
const config = await getPluginConfig(arrayBuffer);
|
||||
const plugin = await makePlugin(arrayBuffer, config, p2p);
|
||||
|
||||
const body = transcript.recv.split('\r\n').reduce(
|
||||
(state: { headerEnd: boolean; body: string[] }, line: string) => {
|
||||
if (state.headerEnd) {
|
||||
state.body.push(line);
|
||||
} else if (!line) {
|
||||
state.headerEnd = true;
|
||||
}
|
||||
const { body: recvBody } = parseHttpMessage(
|
||||
Buffer.from(transcript.recv),
|
||||
'response',
|
||||
);
|
||||
|
||||
return state;
|
||||
},
|
||||
{ headerEnd: false, body: [] },
|
||||
).body;
|
||||
|
||||
let out;
|
||||
|
||||
if (body.length == 1) {
|
||||
out = await plugin.call(method, body[0]);
|
||||
} else {
|
||||
out = await plugin.call(
|
||||
method,
|
||||
body.filter((txt: string) => {
|
||||
const json = safeParseJSON(txt);
|
||||
return typeof json === 'object';
|
||||
})[0],
|
||||
);
|
||||
}
|
||||
const out = await plugin.call(
|
||||
method,
|
||||
...recvBody.map((body) => body.toString('utf-8')),
|
||||
);
|
||||
|
||||
const secretResps = JSON.parse(out.string());
|
||||
await browser.runtime.sendMessage({
|
||||
@@ -1343,7 +1371,7 @@ async function handleRunPluginCSRequest(request: BackgroundAction) {
|
||||
});
|
||||
|
||||
const defer = deferredPromise();
|
||||
const { origin, position, hash } = request.data;
|
||||
const { origin, position, hash, params } = request.data;
|
||||
|
||||
const plugin = await getPluginByHash(hash);
|
||||
const config = await getPluginConfigByHash(hash);
|
||||
@@ -1355,7 +1383,7 @@ async function handleRunPluginCSRequest(request: BackgroundAction) {
|
||||
}
|
||||
|
||||
const { popup, tab } = await openPopup(
|
||||
`run-plugin-approval?hash=${hash}&origin=${encodeURIComponent(origin)}&favIconUrl=${encodeURIComponent(currentTab?.favIconUrl || '')}`,
|
||||
`run-plugin-approval?hash=${hash}&origin=${encodeURIComponent(origin)}&favIconUrl=${encodeURIComponent(currentTab?.favIconUrl || '')}¶ms=${encodeURIComponent(JSON.stringify(params) || '')}`,
|
||||
position.left,
|
||||
position.top,
|
||||
);
|
||||
|
||||
@@ -22,8 +22,7 @@ import browser from 'webextension-polyfill';
|
||||
import { OffscreenActionTypes } from '../Offscreen/types';
|
||||
import { getMaxRecv, getMaxSent, getRendezvousApi } from '../../utils/storage';
|
||||
import { SidePanelActionTypes } from '../SidePanel/types';
|
||||
import { Transcript } from 'tlsn-js';
|
||||
import { VerifierOutput } from 'tlsn-wasm';
|
||||
import { Transcript, VerifierOutput } from 'tlsn-js';
|
||||
|
||||
const state: {
|
||||
clientId: string;
|
||||
|
||||
@@ -95,9 +95,10 @@ class TLSN {
|
||||
return resp;
|
||||
}
|
||||
|
||||
async runPlugin(hash: string) {
|
||||
async runPlugin(hash: string, params?: Record<string, string>) {
|
||||
const resp = await client.call(ContentScriptTypes.run_plugin, {
|
||||
hash,
|
||||
params,
|
||||
});
|
||||
|
||||
return resp;
|
||||
|
||||
@@ -196,8 +196,13 @@ import { urlify } from '../../utils/misc';
|
||||
|
||||
server.on(
|
||||
ContentScriptTypes.run_plugin,
|
||||
async (request: ContentScriptRequest<{ hash: string }>) => {
|
||||
const { hash } = request.params || {};
|
||||
async (
|
||||
request: ContentScriptRequest<{
|
||||
hash: string;
|
||||
params?: Record<string, string>;
|
||||
}>,
|
||||
) => {
|
||||
const { hash, params } = request.params || {};
|
||||
|
||||
if (!hash) throw new Error('params must include hash');
|
||||
|
||||
@@ -206,6 +211,7 @@ import { urlify } from '../../utils/misc';
|
||||
data: {
|
||||
...getPopupData(),
|
||||
hash,
|
||||
params,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -4,23 +4,26 @@ import {
|
||||
progressText,
|
||||
RequestProgress,
|
||||
} from '../Background/rpc';
|
||||
import { Method } from 'tlsn-wasm';
|
||||
import {
|
||||
mapStringToRange,
|
||||
NotaryServer,
|
||||
Method,
|
||||
Presentation as TPresentation,
|
||||
Prover as TProver,
|
||||
subtractRanges,
|
||||
Transcript,
|
||||
Verifier as TVerifier,
|
||||
} from 'tlsn-js';
|
||||
import { devlog, urlify } from '../../utils/misc';
|
||||
import { convertNotaryWsToHttp, devlog, urlify } from '../../utils/misc';
|
||||
import * as Comlink from 'comlink';
|
||||
import { PresentationJSON as PresentationJSONa7 } from 'tlsn-js/build/types';
|
||||
import { subtractRanges } from './utils';
|
||||
import { mapSecretsToRange } from '../Background/plugins/utils';
|
||||
import { OffscreenActionTypes } from './types';
|
||||
import { PresentationJSON } from '../../utils/types';
|
||||
import { verify } from 'tlsn-js-v5';
|
||||
import { waitForEvent } from '../utils';
|
||||
import {
|
||||
setNotaryRequestError,
|
||||
setNotaryRequestStatus,
|
||||
} from '../Background/db';
|
||||
|
||||
const { init, Prover, Presentation, Verifier }: any = Comlink.wrap(
|
||||
new Worker(new URL('./worker.ts', import.meta.url)),
|
||||
@@ -33,7 +36,10 @@ export const initThreads = async () => {
|
||||
type: BackgroundActiontype.get_logging_level,
|
||||
hardwareConcurrency: navigator.hardwareConcurrency,
|
||||
});
|
||||
await init({ loggingLevel });
|
||||
await init({
|
||||
loggingLevel,
|
||||
hardwareConcurrency: navigator.hardwareConcurrency,
|
||||
});
|
||||
};
|
||||
export const onNotarizationRequest = async (request: any) => {
|
||||
const { id } = request.data;
|
||||
@@ -105,7 +111,7 @@ export const onCreateProverRequest = async (request: any) => {
|
||||
};
|
||||
|
||||
export const onCreatePresentationRequest = async (request: any) => {
|
||||
const { id, commit } = request.data;
|
||||
const { id, commit, notaryUrl, websocketProxyUrl } = request.data;
|
||||
const prover = provers[id];
|
||||
|
||||
try {
|
||||
@@ -121,13 +127,19 @@ export const onCreatePresentationRequest = async (request: any) => {
|
||||
websocketProxyUrl: notarizationOutputs.websocketProxyUrl,
|
||||
reveal: commit,
|
||||
})) as TPresentation;
|
||||
const presentationJSON = await presentation.json();
|
||||
|
||||
const json = await presentation.json();
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.finish_prove_request,
|
||||
data: {
|
||||
id,
|
||||
proof: presentationJSON,
|
||||
proof: {
|
||||
...json,
|
||||
meta: {
|
||||
...json.meta,
|
||||
notaryUrl,
|
||||
websocketProxyUrl,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -176,7 +188,12 @@ export const onVerifyProof = async (request: any, sendResponse: any) => {
|
||||
|
||||
export const onVerifyProofRequest = async (request: any) => {
|
||||
const proof: PresentationJSON = request.data.proof;
|
||||
const result: { sent: string; recv: string } = await verifyProof(proof);
|
||||
const result: {
|
||||
sent: string;
|
||||
recv: string;
|
||||
verifierKey?: string;
|
||||
notaryKey?: string;
|
||||
} = await verifyProof(proof);
|
||||
|
||||
chrome.runtime.sendMessage<any, string>({
|
||||
type: BackgroundActiontype.finish_prove_request,
|
||||
@@ -185,6 +202,8 @@ export const onVerifyProofRequest = async (request: any) => {
|
||||
verification: {
|
||||
sent: result.sent,
|
||||
recv: result.recv,
|
||||
verifierKey: result.verifierKey,
|
||||
notaryKey: result.notaryKey,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -315,25 +334,18 @@ export const startP2PProver = async (request: any) => {
|
||||
|
||||
const commit = {
|
||||
sent: subtractRanges(
|
||||
transcript.ranges.sent.all,
|
||||
mapSecretsToRange(secretHeaders, transcript.sent),
|
||||
{ start: 0, end: transcript.sent.length },
|
||||
mapStringToRange(
|
||||
secretHeaders,
|
||||
Buffer.from(transcript.sent).toString('utf-8'),
|
||||
),
|
||||
),
|
||||
recv: subtractRanges(
|
||||
transcript.ranges.recv.all,
|
||||
secretResps
|
||||
.map((secret: string) => {
|
||||
const index = transcript.recv.indexOf(secret);
|
||||
return index > -1
|
||||
? {
|
||||
start: index,
|
||||
end: index + secret.length,
|
||||
}
|
||||
: null;
|
||||
})
|
||||
.filter((data: any) => !!data) as {
|
||||
start: number;
|
||||
end: number;
|
||||
}[],
|
||||
{ start: 0, end: transcript.recv.length },
|
||||
mapStringToRange(
|
||||
secretResps,
|
||||
Buffer.from(transcript.recv).toString('utf-8'),
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
@@ -401,12 +413,18 @@ async function createProof(options: {
|
||||
|
||||
const commit = {
|
||||
sent: subtractRanges(
|
||||
transcript.ranges.sent.all,
|
||||
mapSecretsToRange(secretHeaders, transcript.sent),
|
||||
{ start: 0, end: transcript.sent.length },
|
||||
mapStringToRange(
|
||||
secretHeaders,
|
||||
Buffer.from(transcript.sent).toString('utf-8'),
|
||||
),
|
||||
),
|
||||
recv: subtractRanges(
|
||||
transcript.ranges.recv.all,
|
||||
mapSecretsToRange(secretResps, transcript.recv),
|
||||
{ start: 0, end: transcript.recv.length },
|
||||
mapStringToRange(
|
||||
secretResps,
|
||||
Buffer.from(transcript.recv).toString('utf-8'),
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
@@ -421,7 +439,15 @@ async function createProof(options: {
|
||||
reveal: commit,
|
||||
})) as TPresentation;
|
||||
|
||||
return presentation.json();
|
||||
const json = await presentation.json();
|
||||
return {
|
||||
...json,
|
||||
meta: {
|
||||
...json,
|
||||
notaryUrl: notaryUrl,
|
||||
websocketProxyUrl: websocketProxyUrl,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function createProver(options: {
|
||||
@@ -451,67 +477,147 @@ async function createProver(options: {
|
||||
|
||||
const hostname = urlify(url)?.hostname || '';
|
||||
const notary = NotaryServer.from(notaryUrl);
|
||||
try {
|
||||
const prover: TProver = await handleProgress(
|
||||
id,
|
||||
RequestProgress.CreatingProver,
|
||||
() =>
|
||||
new Prover({
|
||||
id,
|
||||
serverDns: hostname,
|
||||
maxSentData,
|
||||
maxRecvData,
|
||||
}),
|
||||
'Error creating prover',
|
||||
);
|
||||
|
||||
updateRequestProgress(id, RequestProgress.CreatingProver);
|
||||
const prover: TProver = await new Prover({
|
||||
id,
|
||||
serverDns: hostname,
|
||||
maxSentData,
|
||||
maxRecvData,
|
||||
});
|
||||
const sessionUrl = await handleProgress(
|
||||
id,
|
||||
RequestProgress.GettingSession,
|
||||
() => notary.sessionUrl(maxSentData, maxRecvData),
|
||||
'Error getting session from Notary',
|
||||
);
|
||||
|
||||
updateRequestProgress(id, RequestProgress.GettingSession);
|
||||
const sessionUrl = await notary.sessionUrl(maxSentData, maxRecvData);
|
||||
await handleProgress(
|
||||
id,
|
||||
RequestProgress.SettingUpProver,
|
||||
() => prover.setup(sessionUrl),
|
||||
'Error setting up prover',
|
||||
);
|
||||
|
||||
updateRequestProgress(id, RequestProgress.SettingUpProver);
|
||||
await prover.setup(sessionUrl);
|
||||
await handleProgress(
|
||||
id,
|
||||
RequestProgress.SendingRequest,
|
||||
() =>
|
||||
prover.sendRequest(websocketProxyUrl + `?token=${hostname}`, {
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
}),
|
||||
'Error sending request',
|
||||
);
|
||||
|
||||
updateRequestProgress(id, RequestProgress.SendingRequest);
|
||||
await prover.sendRequest(websocketProxyUrl + `?token=${hostname}`, {
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
|
||||
return prover;
|
||||
return prover;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyProof(
|
||||
proof: PresentationJSON,
|
||||
): Promise<{ sent: string; recv: string }> {
|
||||
let result: { sent: string; recv: string };
|
||||
async function verifyProof(proof: PresentationJSON): Promise<{
|
||||
sent: string;
|
||||
recv: string;
|
||||
verifierKey?: string;
|
||||
notaryKey?: string;
|
||||
}> {
|
||||
let result: {
|
||||
sent: string;
|
||||
recv: string;
|
||||
verifierKey?: string;
|
||||
notaryKey?: string;
|
||||
};
|
||||
|
||||
switch (proof.version) {
|
||||
case undefined: {
|
||||
result = await verify(proof);
|
||||
break;
|
||||
}
|
||||
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,
|
||||
});
|
||||
case undefined:
|
||||
case '0.1.0-alpha.7':
|
||||
case '0.1.0-alpha.8':
|
||||
case '0.1.0-alpha.9':
|
||||
result = {
|
||||
sent: transcript.sent(),
|
||||
recv: transcript.recv(),
|
||||
sent: 'version not supported',
|
||||
recv: 'version not supported',
|
||||
};
|
||||
break;
|
||||
}
|
||||
case '0.1.0-alpha.10':
|
||||
result = await verify(proof);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
return result!;
|
||||
}
|
||||
|
||||
function updateRequestProgress(id: string, progress: RequestProgress) {
|
||||
devlog(`Request ${id}: ${progressText(progress)}`);
|
||||
async function verify(proof: PresentationJSON) {
|
||||
if (proof.version !== '0.1.0-alpha.10') {
|
||||
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,
|
||||
});
|
||||
const vk = await presentation.verifyingKey();
|
||||
const verifyingKey = Buffer.from(vk.data).toString('hex');
|
||||
const notaryUrl = proof.meta.notaryUrl
|
||||
? convertNotaryWsToHttp(proof.meta.notaryUrl)
|
||||
: '';
|
||||
const publicKey = await new NotaryServer(notaryUrl)
|
||||
.publicKey()
|
||||
.catch(() => '');
|
||||
return {
|
||||
sent: transcript.sent(),
|
||||
recv: transcript.recv(),
|
||||
verifierKey: verifyingKey,
|
||||
notaryKey: publicKey,
|
||||
};
|
||||
}
|
||||
|
||||
function updateRequestProgress(
|
||||
id: string,
|
||||
progress: RequestProgress,
|
||||
errorMessage?: string,
|
||||
) {
|
||||
const progressMessage =
|
||||
progress === RequestProgress.Error
|
||||
? `${errorMessage || 'Notarization Failed'}`
|
||||
: progressText(progress);
|
||||
devlog(`Request ${id}: ${progressMessage}`);
|
||||
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.update_request_progress,
|
||||
data: {
|
||||
id,
|
||||
progress: progress,
|
||||
progress,
|
||||
errorMessage,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function handleProgress<T>(
|
||||
id: string,
|
||||
progress: RequestProgress,
|
||||
action: () => Promise<T>,
|
||||
errorMessage: string,
|
||||
): Promise<T> {
|
||||
try {
|
||||
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',
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
9
src/entries/Offscreen/workers-v9.ts
Normal file
9
src/entries/Offscreen/workers-v9.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as Comlink from 'comlink';
|
||||
import init, { Prover, Presentation, Verifier } from 'tlsn-js-v9';
|
||||
|
||||
Comlink.expose({
|
||||
init,
|
||||
Prover,
|
||||
Presentation,
|
||||
Verifier,
|
||||
});
|
||||
@@ -20,6 +20,7 @@ import History from '../../pages/History';
|
||||
import ProofUploader from '../../pages/ProofUploader';
|
||||
import browser from 'webextension-polyfill';
|
||||
import store from '../../utils/store';
|
||||
import { isPopupWindow } from '../../utils/misc';
|
||||
import PluginUploadInfo from '../../components/PluginInfo';
|
||||
import ConnectionDetailsModal from '../../components/ConnectionDetailsModal';
|
||||
import { ConnectionApproval } from '../../pages/ConnectionApproval';
|
||||
@@ -41,7 +42,7 @@ import { fetchP2PState } from '../../reducers/p2p';
|
||||
const Popup = () => {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [isPopup, setIsPopup] = useState(isPopupWindow());
|
||||
useEffect(() => {
|
||||
fetchP2PState();
|
||||
}, []);
|
||||
@@ -101,8 +102,12 @@ const Popup = () => {
|
||||
onClick={() => navigate('/')}
|
||||
/>
|
||||
<div className="flex flex-row flex-grow items-center justify-end gap-4">
|
||||
<AppConnectionLogo />
|
||||
<MenuIcon />
|
||||
{!isPopup && (
|
||||
<>
|
||||
<AppConnectionLogo />
|
||||
<MenuIcon />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Routes>
|
||||
|
||||
@@ -13,7 +13,11 @@ import logo from '../../assets/img/icon-128.png';
|
||||
import classNames from 'classnames';
|
||||
import Icon from '../../components/Icon';
|
||||
import { useRequestHistory } from '../../reducers/history';
|
||||
import { BackgroundActiontype, progressText } from '../Background/rpc';
|
||||
import {
|
||||
BackgroundActiontype,
|
||||
progressText,
|
||||
RequestProgress,
|
||||
} from '../Background/rpc';
|
||||
import { getPluginByHash, getPluginConfigByHash } from '../Background/db';
|
||||
import { SidePanelActionTypes } from './types';
|
||||
import { fetchP2PState, useClientId } from '../../reducers/p2p';
|
||||
@@ -23,6 +27,7 @@ export default function SidePanel(): ReactElement {
|
||||
const [hash, setHash] = useState('');
|
||||
const [hex, setHex] = useState('');
|
||||
const [p2p, setP2P] = useState(false);
|
||||
const [params, setParams] = useState<Record<string, string> | undefined>();
|
||||
const [started, setStarted] = useState(false);
|
||||
const clientId = useClientId();
|
||||
|
||||
@@ -41,6 +46,7 @@ export default function SidePanel(): ReactElement {
|
||||
case SidePanelActionTypes.execute_plugin_request: {
|
||||
setConfig(await getPluginConfigByHash(data.pluginHash));
|
||||
setHash(data.pluginHash);
|
||||
setParams(data.pluginParams);
|
||||
setStarted(true);
|
||||
break;
|
||||
}
|
||||
@@ -60,6 +66,16 @@ export default function SidePanel(): ReactElement {
|
||||
setStarted(true);
|
||||
break;
|
||||
}
|
||||
case SidePanelActionTypes.is_panel_open: {
|
||||
return { isOpen: true };
|
||||
}
|
||||
case SidePanelActionTypes.reset_panel: {
|
||||
setConfig(null);
|
||||
setHash('');
|
||||
setHex('');
|
||||
setStarted(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
@@ -83,6 +99,7 @@ export default function SidePanel(): ReactElement {
|
||||
config={config}
|
||||
p2p={p2p}
|
||||
clientId={clientId}
|
||||
presetParameterValues={params}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -95,8 +112,9 @@ function PluginBody(props: {
|
||||
hex?: string;
|
||||
clientId?: string;
|
||||
p2p?: boolean;
|
||||
presetParameterValues?: Record<string, string>;
|
||||
}): ReactElement {
|
||||
const { hash, hex, config, p2p, clientId } = props;
|
||||
const { hash, hex, config, p2p, clientId, presetParameterValues } = props;
|
||||
const { title, description, icon, steps } = config;
|
||||
const [responses, setResponses] = useState<any[]>([]);
|
||||
const [notarizationId, setNotarizationId] = useState('');
|
||||
@@ -158,6 +176,7 @@ function PluginBody(props: {
|
||||
responses={responses}
|
||||
p2p={p2p}
|
||||
clientId={clientId}
|
||||
parameterValues={presetParameterValues}
|
||||
{...step}
|
||||
/>
|
||||
))}
|
||||
@@ -177,6 +196,7 @@ function StepContent(
|
||||
lastResponse?: any;
|
||||
config: PluginConfig;
|
||||
p2p?: boolean;
|
||||
parameterValues?: Record<string, string>;
|
||||
},
|
||||
): ReactElement {
|
||||
const {
|
||||
@@ -193,6 +213,7 @@ function StepContent(
|
||||
config,
|
||||
p2p = false,
|
||||
clientId = '',
|
||||
parameterValues,
|
||||
} = props;
|
||||
const [completed, setCompleted] = useState(false);
|
||||
const [pending, setPending] = useState(false);
|
||||
@@ -215,7 +236,13 @@ function StepContent(
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const out = await plugin.call(action, JSON.stringify(lastResponse));
|
||||
const out = await plugin.call(
|
||||
action,
|
||||
index > 0
|
||||
? JSON.stringify(lastResponse)
|
||||
: JSON.stringify(parameterValues),
|
||||
);
|
||||
console.log(out);
|
||||
const val = JSON.parse(out.string());
|
||||
if (val && prover) {
|
||||
setNotarizationId(val);
|
||||
@@ -315,17 +342,27 @@ function StepContent(
|
||||
);
|
||||
} else if (notaryRequest?.status === 'pending' || pending || notarizationId) {
|
||||
btnContent = (
|
||||
<button className="button mt-2 w-fit flex flex-row flex-nowrap items-center gap-2 cursor-default">
|
||||
<Icon className="animate-spin" fa="fa-solid fa-spinner" size={1} />
|
||||
<span className="text-sm">
|
||||
{notaryRequest?.progress
|
||||
? `(${(
|
||||
((notaryRequest.progress + 1) / 6.06) *
|
||||
100
|
||||
).toFixed()}%) ${progressText(notaryRequest.progress)}`
|
||||
: 'Pending...'}
|
||||
</span>
|
||||
</button>
|
||||
<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>
|
||||
)}
|
||||
{notaryRequest?.progress !== RequestProgress.Error && (
|
||||
<button className="button mt-2 w-fit flex flex-row flex-nowrap items-center gap-2 cursor-default">
|
||||
<Icon className="animate-spin" fa="fa-solid fa-spinner" size={1} />
|
||||
<span className="text-sm">
|
||||
{notaryRequest?.progress !== undefined
|
||||
? `(${(((notaryRequest.progress + 1) / 6.06) * 100).toFixed()}%) ${progressText(notaryRequest.progress)}`
|
||||
: 'Pending...'}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
btnContent = (
|
||||
|
||||
@@ -5,4 +5,6 @@ export enum SidePanelActionTypes {
|
||||
run_p2p_plugin_request = 'sidePanel/run_p2p_plugin_request',
|
||||
run_p2p_plugin_response = 'sidePanel/run_p2p_plugin_response',
|
||||
start_p2p_plugin = 'sidePanel/start_p2p_plugin',
|
||||
is_panel_open = 'sidePanel/is_panel_open',
|
||||
reset_panel = 'sidePanel/reset_panel',
|
||||
}
|
||||
|
||||
@@ -23,6 +23,18 @@ export const openSidePanel = async () => {
|
||||
const { promise, resolve, reject } = deferredPromise();
|
||||
|
||||
try {
|
||||
const response = await browser.runtime.sendMessage({
|
||||
type: SidePanelActionTypes.is_panel_open,
|
||||
});
|
||||
|
||||
if (response?.isOpen) {
|
||||
await browser.runtime.sendMessage({
|
||||
type: SidePanelActionTypes.reset_panel,
|
||||
});
|
||||
resolve();
|
||||
return promise;
|
||||
}
|
||||
|
||||
const [tab] = await browser.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import React, { ReactElement, useState, useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useNavigate } from 'react-router';
|
||||
import {
|
||||
useHistoryOrder,
|
||||
useRequestHistory,
|
||||
deleteRequestHistory,
|
||||
} from '../../reducers/history';
|
||||
import { useHistoryOrder, useRequestHistory } from '../../reducers/history';
|
||||
import Icon from '../../components/Icon';
|
||||
import NotarizeIcon from '../../assets/img/notarize.png';
|
||||
import { getNotaryApi, getProxyApi } from '../../utils/storage';
|
||||
import { urlify } from '../../utils/misc';
|
||||
import {
|
||||
BackgroundActiontype,
|
||||
progressText,
|
||||
RequestProgress,
|
||||
} from '../../entries/Background/rpc';
|
||||
import Modal, { ModalContent } from '../../components/Modal/Modal';
|
||||
import classNames from 'classnames';
|
||||
import dayjs from 'dayjs';
|
||||
import RequestMenu from './request-menu';
|
||||
|
||||
const charwise = require('charwise');
|
||||
|
||||
export default function History(): ReactElement {
|
||||
@@ -110,12 +107,14 @@ export function OneRequestHistory(props: {
|
||||
size={1}
|
||||
/>
|
||||
<span className="">
|
||||
{request?.progress
|
||||
? `(${(
|
||||
((request.progress + 1) / 6.06) *
|
||||
100
|
||||
).toFixed()}%) ${progressText(request.progress)}`
|
||||
: 'Pending...'}
|
||||
{request?.progress === RequestProgress.Error
|
||||
? `${progressText(request.progress, request.errorMessage)}`
|
||||
: request?.progress
|
||||
? `(${(
|
||||
((request.progress + 1) / 6.06) *
|
||||
100
|
||||
).toFixed()}%) ${progressText(request.progress)}`
|
||||
: 'Pending...'}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -155,7 +154,7 @@ export function OneRequestHistory(props: {
|
||||
onClose={closeAllModal}
|
||||
>
|
||||
<ModalContent className="flex justify-center items-center text-slate-500">
|
||||
{msg || 'Something went wrong :('}
|
||||
{msg || request?.errorMessage}
|
||||
</ModalContent>
|
||||
<button
|
||||
className="m-0 w-24 bg-red-100 text-red-300 hover:bg-red-200 hover:text-red-500"
|
||||
|
||||
@@ -33,6 +33,7 @@ export default function RequestMenu({
|
||||
const request = useRequestHistory(requestId);
|
||||
const [showingShareConfirmation, setShowingShareConfirmation] =
|
||||
useState(false);
|
||||
const [showRemoveModal, setShowRemoveModal] = useState(false);
|
||||
|
||||
const onRetry = useCallback(async () => {
|
||||
const notaryUrl = await getNotaryApi();
|
||||
@@ -64,6 +65,12 @@ export default function RequestMenu({
|
||||
showMenu={showMenu}
|
||||
/>
|
||||
)}
|
||||
<RemoveHistory
|
||||
onRemove={onDelete}
|
||||
showRemovalModal={showRemoveModal}
|
||||
setShowRemoveModal={setShowRemoveModal}
|
||||
onCancel={() => setShowRemoveModal(false)}
|
||||
/>
|
||||
<div
|
||||
className="fixed top-0 left-0 w-screen h-screen z-10 cursor-default"
|
||||
onClick={(e) => {
|
||||
@@ -116,8 +123,7 @@ export default function RequestMenu({
|
||||
className="border-b border-slate-300 !text-red-500"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete();
|
||||
showMenu(false);
|
||||
setShowRemoveModal(true);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
@@ -254,3 +260,48 @@ function ShareConfirmationModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export function RemoveHistory(props: {
|
||||
onRemove: () => void;
|
||||
showRemovalModal: boolean;
|
||||
setShowRemoveModal: (show: boolean) => void;
|
||||
onCancel: () => void;
|
||||
}): ReactElement {
|
||||
const { onRemove, setShowRemoveModal, showRemovalModal } = props;
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
setShowRemoveModal(false);
|
||||
}, [showRemovalModal]);
|
||||
return !showRemovalModal ? (
|
||||
<></>
|
||||
) : (
|
||||
<Modal
|
||||
onClose={onCancel}
|
||||
className="flex flex-col items-center text-base cursor-default justify-center !w-auto mx-4 my-[50%] p-4 gap-4"
|
||||
>
|
||||
<ModalContent className="flex flex-col w-full gap-4 items-center text-base justify-center">
|
||||
<div className="text-base">
|
||||
Are you sure you want to delete this attestation?
|
||||
</div>
|
||||
<div className="mb-1">
|
||||
<span className="text-red-500 font-bold">Warning:</span> this cannot
|
||||
be undone.
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 justify-end">
|
||||
<button
|
||||
className="m-0 w-24 bg-slate-100 text-slate-300 hover:bg-slate-200 hover:text-slate-500"
|
||||
onClick={onCancel}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="m-0 w-24 bg-red-100 text-red-300 hover:bg-red-200 hover:text-red-500"
|
||||
onClick={onRemove}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,14 +7,17 @@ import React, {
|
||||
import Icon from '../../components/Icon';
|
||||
import { BackgroundActiontype } from '../../entries/Background/rpc';
|
||||
import ProofViewer from '../ProofViewer';
|
||||
import { convertNotaryWsToHttp } from '../../utils/misc';
|
||||
|
||||
export default function ProofUploader(): ReactElement {
|
||||
const [proof, setProof] = useState<{
|
||||
recv: string;
|
||||
sent: string;
|
||||
verifierKey?: string;
|
||||
notaryKey?: string;
|
||||
} | null>(null);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
const [metadata, setMetaData] = useState<any>({ meta: '', version: '' });
|
||||
const onFileUpload: ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
async (e) => {
|
||||
// @ts-ignore
|
||||
@@ -26,8 +29,19 @@ export default function ProofUploader(): ReactElement {
|
||||
const result = event.target?.result;
|
||||
if (result) {
|
||||
const proof = JSON.parse(result as string);
|
||||
const notaryUrl = convertNotaryWsToHttp(proof.meta.notaryUrl);
|
||||
proof.meta.notaryUrl = notaryUrl;
|
||||
setMetaData({ meta: proof.meta, version: proof.version });
|
||||
const res = await chrome.runtime
|
||||
.sendMessage<any, { recv: string; sent: string }>({
|
||||
.sendMessage<
|
||||
any,
|
||||
{
|
||||
recv: string;
|
||||
sent: string;
|
||||
verifierKey?: string;
|
||||
notaryKey?: string;
|
||||
}
|
||||
>({
|
||||
type: BackgroundActiontype.verify_proof,
|
||||
data: proof,
|
||||
})
|
||||
@@ -48,7 +62,15 @@ export default function ProofUploader(): ReactElement {
|
||||
);
|
||||
|
||||
if (proof) {
|
||||
return <ProofViewer recv={proof.recv} sent={proof.sent} />;
|
||||
return (
|
||||
<ProofViewer
|
||||
recv={proof.recv}
|
||||
sent={proof.sent}
|
||||
verifierKey={proof.verifierKey}
|
||||
notaryKey={proof.notaryKey}
|
||||
info={metadata}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, {
|
||||
ReactNode,
|
||||
ReactElement,
|
||||
useState,
|
||||
useEffect,
|
||||
MouseEventHandler,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
@@ -12,28 +13,51 @@ import {
|
||||
useRequestHistory,
|
||||
} from '../../reducers/history';
|
||||
import Icon from '../../components/Icon';
|
||||
import { download } from '../../utils/misc';
|
||||
import {
|
||||
convertNotaryWsToHttp,
|
||||
download,
|
||||
isPopupWindow,
|
||||
} from '../../utils/misc';
|
||||
import classNames from 'classnames';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { RemoveHistory } from '../History/request-menu';
|
||||
import { PresentationJSON } from 'tlsn-js/build/types';
|
||||
import { RequestHistory } from '../../entries/Background/rpc';
|
||||
|
||||
export default function ProofViewer(props?: {
|
||||
className?: string;
|
||||
recv?: string;
|
||||
sent?: string;
|
||||
verifierKey?: string;
|
||||
notaryKey?: string;
|
||||
info?: {
|
||||
meta: { notaryUrl: string; websocketProxyUrl: string };
|
||||
version: string;
|
||||
};
|
||||
}): ReactElement {
|
||||
const dispatch = useDispatch();
|
||||
const { requestId } = useParams<{ requestId: string }>();
|
||||
const request = useRequestHistory(requestId);
|
||||
const navigate = useNavigate();
|
||||
const [tab, setTab] = useState('sent');
|
||||
const [isPopup, setIsPopup] = useState(isPopupWindow());
|
||||
const [showRemoveModal, setShowRemoveModal] = useState(false);
|
||||
|
||||
const onDelete = useCallback(async () => {
|
||||
if (requestId) {
|
||||
dispatch(deleteRequestHistory(requestId));
|
||||
if (isPopup) window.close();
|
||||
navigate(-1);
|
||||
}
|
||||
}, [requestId]);
|
||||
|
||||
const notaryUrl = extractFromProps('notaryUrl', props, request);
|
||||
const websocketProxyUrl = extractFromProps(
|
||||
'websocketProxyUrl',
|
||||
props,
|
||||
request,
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
@@ -41,23 +65,37 @@ export default function ProofViewer(props?: {
|
||||
props?.className,
|
||||
)}
|
||||
>
|
||||
<RemoveHistory
|
||||
onRemove={onDelete}
|
||||
showRemovalModal={showRemoveModal}
|
||||
setShowRemoveModal={setShowRemoveModal}
|
||||
onCancel={() => setShowRemoveModal(false)}
|
||||
/>
|
||||
<div className="flex flex-col px-2">
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<Icon
|
||||
className={c(
|
||||
'px-1 select-none cursor-pointer',
|
||||
'text-slate-400 border-b-2 border-transparent hover:text-slate-500 active:text-slate-800',
|
||||
)}
|
||||
onClick={() => navigate(-1)}
|
||||
fa="fa-solid fa-xmark"
|
||||
/>
|
||||
{!isPopup && (
|
||||
<Icon
|
||||
className={c(
|
||||
'px-1 select-none cursor-pointer',
|
||||
'text-slate-400 border-b-2 border-transparent hover:text-slate-500 active:text-slate-800',
|
||||
)}
|
||||
onClick={() => navigate(-1)}
|
||||
fa="fa-solid fa-xmark"
|
||||
/>
|
||||
)}
|
||||
<TabLabel onClick={() => setTab('sent')} active={tab === 'sent'}>
|
||||
Sent
|
||||
</TabLabel>
|
||||
<TabLabel onClick={() => setTab('recv')} active={tab === 'recv'}>
|
||||
Recv
|
||||
</TabLabel>
|
||||
<div className="flex flex-row flex-grow items-center justify-end gap-2">
|
||||
<TabLabel
|
||||
onClick={() => setTab('metadata')}
|
||||
active={tab === 'metadata'}
|
||||
>
|
||||
Metadata
|
||||
</TabLabel>
|
||||
<div className="flex flex-row flex-grow items-center justify-end">
|
||||
{!props?.recv && (
|
||||
<button
|
||||
className="button"
|
||||
@@ -69,7 +107,10 @@ export default function ProofViewer(props?: {
|
||||
Download
|
||||
</button>
|
||||
)}
|
||||
<button className="button !text-red-500" onClick={onDelete}>
|
||||
<button
|
||||
className="button !text-red-500"
|
||||
onClick={() => setShowRemoveModal(true)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
@@ -90,11 +131,61 @@ export default function ProofViewer(props?: {
|
||||
readOnly
|
||||
></textarea>
|
||||
)}
|
||||
{tab === 'metadata' && (
|
||||
<div className="w-full resize-none bg-slate-100 text-slate-800 border p-2 text-[10px] break-all h-full outline-none font-mono">
|
||||
<MetadataRow
|
||||
label="Version"
|
||||
//@ts-ignore
|
||||
value={props?.info?.version || request?.proof?.version}
|
||||
/>
|
||||
<MetadataRow label="Notary URL" value={notaryUrl} />
|
||||
<MetadataRow
|
||||
label="Websocket Proxy URL"
|
||||
value={websocketProxyUrl}
|
||||
/>
|
||||
<MetadataRow
|
||||
label="Verifying Key"
|
||||
value={props?.verifierKey || request?.verification?.verifierKey}
|
||||
/>
|
||||
<MetadataRow
|
||||
label="Notary Key"
|
||||
value={props?.notaryKey || request?.verification?.notaryKey}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function extractFromProps(
|
||||
key: 'notaryUrl' | 'websocketProxyUrl',
|
||||
props?: {
|
||||
className?: string;
|
||||
recv?: string;
|
||||
sent?: string;
|
||||
verifierKey?: string;
|
||||
notaryKey?: string;
|
||||
info?: {
|
||||
meta: { notaryUrl: string; websocketProxyUrl: string };
|
||||
version: string;
|
||||
};
|
||||
},
|
||||
request?: RequestHistory,
|
||||
) {
|
||||
let value;
|
||||
|
||||
if (props?.info?.meta) {
|
||||
value = props.info.meta[key];
|
||||
} else if (request && (request?.proof as PresentationJSON)?.meta) {
|
||||
value = (request.proof as PresentationJSON).meta[key];
|
||||
} else {
|
||||
value = '';
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function TabLabel(props: {
|
||||
children: ReactNode;
|
||||
onClick: MouseEventHandler;
|
||||
@@ -113,3 +204,20 @@ function TabLabel(props: {
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function MetadataRow({
|
||||
label,
|
||||
value,
|
||||
}: {
|
||||
label: string;
|
||||
value: string | undefined;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<div>{label}:</div>
|
||||
<div className="text-sm font-semibold whitespace-pre-wrap">
|
||||
{value || 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ export function RunPluginApproval(): ReactElement {
|
||||
const origin = params.get('origin');
|
||||
const favIconUrl = params.get('favIconUrl');
|
||||
const hash = params.get('hash');
|
||||
const pluginParams = params.get('params');
|
||||
const hostname = urlify(origin || '')?.hostname;
|
||||
const [error, showError] = useState('');
|
||||
const [metadata, setPluginMetadata] = useState<PluginMetadata | null>(null);
|
||||
@@ -60,6 +61,7 @@ export function RunPluginApproval(): ReactElement {
|
||||
type: SidePanelActionTypes.execute_plugin_request,
|
||||
data: {
|
||||
pluginHash: hash,
|
||||
pluginParams: pluginParams ? JSON.parse(pluginParams) : undefined,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
BackgroundActiontype,
|
||||
RequestHistory,
|
||||
RequestProgress,
|
||||
} from '../entries/Background/rpc';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppRootState } from './index';
|
||||
@@ -76,6 +77,9 @@ export default function history(
|
||||
if (!payload) return state;
|
||||
|
||||
const existing = state.map[payload.id];
|
||||
if (existing?.progress === RequestProgress.Error) {
|
||||
return state;
|
||||
}
|
||||
const newMap = {
|
||||
...state.map,
|
||||
[payload.id]: payload,
|
||||
@@ -90,13 +94,20 @@ export default function history(
|
||||
}
|
||||
case ActionType['/history/setRequests']: {
|
||||
const payload: RequestHistory[] = action.payload;
|
||||
|
||||
const newMap = payload.reduce(
|
||||
(map: { [id: string]: RequestHistory }, req) => {
|
||||
if (state.map[req.id]?.progress === RequestProgress.Error) {
|
||||
map[req.id] = state.map[req.id];
|
||||
} else {
|
||||
map[req.id] = req;
|
||||
}
|
||||
return map;
|
||||
},
|
||||
{},
|
||||
);
|
||||
return {
|
||||
...state,
|
||||
map: payload.reduce((map: { [id: string]: RequestHistory }, req) => {
|
||||
map[req.id] = req;
|
||||
return map;
|
||||
}, {}),
|
||||
map: newMap,
|
||||
order: payload.map(({ id }) => id),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const EXPLORER_API = 'https://explorer.tlsnotary.org';
|
||||
export const NOTARY_API = 'https://notary.pse.dev/v0.1.0-alpha.7';
|
||||
export const NOTARY_API = 'https://notary.pse.dev/v0.1.0-alpha.10';
|
||||
export const RENDEZVOUS_API = 'wss://explorer.tlsnotary.org';
|
||||
export const NOTARY_PROXY = 'wss://notary.pse.dev/proxy';
|
||||
export const MAX_RECV = 16384;
|
||||
|
||||
@@ -89,6 +89,19 @@ export const copyText = async (text: string): Promise<void> => {
|
||||
}
|
||||
};
|
||||
|
||||
export function convertNotaryWsToHttp(notaryWs: string) {
|
||||
const { protocol, pathname, hostname, port } = new URL(notaryWs);
|
||||
|
||||
if (protocol === 'https:' || protocol === 'http:') {
|
||||
return notaryWs;
|
||||
}
|
||||
const p = protocol === 'wss:' ? 'https:' : 'http:';
|
||||
const pt = port ? `:${port}` : '';
|
||||
const path = pathname === '/' ? '' : pathname.replace('/notarize', '');
|
||||
const h = hostname === 'localhost' ? '127.0.0.1' : hostname;
|
||||
return p + '//' + h + pt + path;
|
||||
}
|
||||
|
||||
export async function replayRequest(req: RequestLog): Promise<string> {
|
||||
const options = {
|
||||
method: req.method,
|
||||
@@ -453,3 +466,9 @@ export function safeParseJSON(data?: string | null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function isPopupWindow(): boolean {
|
||||
return (
|
||||
!!window.opener || window.matchMedia('(display-mode: standalone)').matches
|
||||
);
|
||||
}
|
||||
|
||||
33
src/utils/parser.ts
Normal file
33
src/utils/parser.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { HTTPParser } from 'http-parser-js';
|
||||
|
||||
export function parseHttpMessage(buffer: Buffer, type: 'request' | 'response') {
|
||||
const parser = new HTTPParser(
|
||||
type === 'request' ? HTTPParser.REQUEST : HTTPParser.RESPONSE,
|
||||
);
|
||||
const body: Buffer[] = [];
|
||||
let complete = false;
|
||||
let headers: string[] = [];
|
||||
|
||||
parser.onBody = (t) => {
|
||||
body.push(t);
|
||||
};
|
||||
|
||||
parser.onHeadersComplete = (res) => {
|
||||
headers = res.headers;
|
||||
};
|
||||
|
||||
parser.onMessageComplete = () => {
|
||||
complete = true;
|
||||
};
|
||||
|
||||
parser.execute(buffer);
|
||||
parser.finish();
|
||||
|
||||
if (!complete) throw new Error(`Could not parse ${type.toUpperCase()}`);
|
||||
|
||||
return {
|
||||
info: buffer.toString('utf-8').split('\r\n')[0],
|
||||
headers,
|
||||
body,
|
||||
};
|
||||
}
|
||||
@@ -7,7 +7,6 @@ var webpack = require("webpack"),
|
||||
TerserPlugin = require("terser-webpack-plugin");
|
||||
var { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
var ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
|
||||
var ReactRefreshTypeScript = require("react-refresh-typescript");
|
||||
var ExtReloader = require('webpack-ext-reloader');
|
||||
|
||||
const ASSET_PATH = process.env.ASSET_PATH || "/";
|
||||
@@ -42,6 +41,9 @@ var options = {
|
||||
/Circular dependency between chunks with runtime/,
|
||||
/ResizeObserver loop completed with undelivered notifications/,
|
||||
/Should not import the named export/,
|
||||
/Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0/,
|
||||
/Global built-in functions are deprecated and will be removed in Dart Sass 3.0.0./,
|
||||
/repetitive deprecation warnings omitted/,
|
||||
],
|
||||
|
||||
entry: {
|
||||
@@ -111,11 +113,6 @@ var options = {
|
||||
{
|
||||
loader: require.resolve("ts-loader"),
|
||||
options: {
|
||||
getCustomTransformers: () => ({
|
||||
before: [isDevelopment && ReactRefreshTypeScript()].filter(
|
||||
Boolean
|
||||
),
|
||||
}),
|
||||
transpileOnly: isDevelopment,
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user