Compare commits

...

18 Commits

Author SHA1 Message Date
tsukino
303448a3c1 chore: add gitignore 2025-04-30 15:03:01 +08:00
tsukino
46674491c6 chore: update to alpha.9 2025-04-30 15:02:35 +08:00
Hendrik Eeckhaut
0ba4a71bba Added link to chrome web store (#173) 2025-04-16 17:22:26 +02:00
Tanner
ade6d7e575 Added Errors to Notarization Progress (#147)
* feat: added Error for RequestProgress

* feat: added logic to handle errors depending on progress

* chore: linting errors

* fix: linting build error

* Avoid magic numbers

* lint

* feat: alpha.8

* fix: package.json path

* chore: update lockfiles

* fix: notary url

* feat: notarization timeouts

* fix: notarization timeouts

* feat: fixed comments + errors are properly thrown and status changes

* fix: notarization timeouts work properly now

* fix: timeout error message fix

* fix: lint

* fix: lint

* fix: errors should keep state after being thrown now

* chore: lint

* fix: fixing metadata with new progress status

---------

Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
Co-authored-by: tsukino <0xtsukino@gmail.com>
2025-04-15 10:00:57 -07:00
Hendrik Eeckhaut
3f37c1aee8 Update extism to v1.0.3 (#166) 2025-04-10 10:02:48 +02:00
tsukino
5545d7abed refactor: remove unused versions (#167) 2025-03-31 16:57:41 +02:00
Hendrik Eeckhaut
b674f5c579 Correct package version (#165) 2025-03-28 13:52:26 +01:00
Hendrik Eeckhaut
49a94e1bbc build: Update to alpha.9 (#162) 2025-03-28 13:44:22 +01:00
Hendrik Eeckhaut
64d572d663 ci: Merge GH workflows into one ci flow + add release job 2025-03-28 11:32:24 +01:00
Hendrik Eeckhaut
2f35278235 ci: use npm to build (instead of pnpm) (#160) 2025-03-27 16:02:36 +01:00
tsukino
5af783e48c fix: notaryUrl and websoccketProxyUrl types in proof viewer (#159) 2025-03-14 04:47:56 -04:00
tsukino
1ea69d0574 feat: alpha.8 (#156)
* feat: alpha.8

* fix: package.json path

* chore: update lockfiles

* fix: notary url
2025-03-14 04:05:11 -04:00
Hendrik Eeckhaut
8bfcf8c8d5 fix: correct github workflow name (#144) 2025-03-14 03:42:10 -04:00
dylan1951
fe825d7ebb Add support for passing params to plugin (#152)
* Add support for passing params to plugin start() method.

* Remove GUI parameter inputs for now

---------

Co-authored-by: dylan <dylanbradshaw107@hotmail.com>
2025-03-14 03:41:19 -04:00
tsukino
ca4986c3bb fix: parse raw buffers from transcript (#154) 2025-03-12 10:43:16 -04:00
Tanner
da756721d4 UI changes (#142)
* feat: adding metadata to proofviewer

* feat: X button conditionally displays on how the proof viewer is opened

* feat: added confirmation modal to deleting history

* chore: clean up

* feat: added verifierKey and notaryKey to metadata in proofviewer

* feat: UI fixes and adding RemoveHistory modal to ProofViewer
2025-03-05 18:59:59 -08:00
Tanner
38852620d2 feat: sidepanel now refreshes on subsequent plugin runs if still open (#153) 2025-03-05 18:52:45 -08:00
Hendrik Eeckhaut
1c9f340add Use GitHub ci to release extension to the Chrome webstore (#133) 2025-02-07 03:30:35 -05:00
29 changed files with 4201 additions and 20900 deletions

View File

@@ -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
View 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 }}

View File

@@ -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
View File

@@ -8,3 +8,4 @@ bin/
build
tlsn/
zip
.vscode

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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);

View File

@@ -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 || '')}&params=${encodeURIComponent(JSON.stringify(params) || '')}`,
position.left,
position.top,
);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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,
},
});

View File

@@ -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;
}
}

View 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,
});

View File

@@ -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>

View File

@@ -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 = (

View File

@@ -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',
}

View File

@@ -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,

View File

@@ -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"

View File

@@ -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>
);
}

View File

@@ -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 (

View File

@@ -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>
);
}

View File

@@ -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,
},
});

View File

@@ -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),
};
}

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.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;

View File

@@ -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
View 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,
};
}

View File

@@ -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,
},
},