mirror of
https://github.com/tlsnotary/tlsn-extension.git
synced 2026-01-09 13:08:04 -05:00
Compare commits
22 Commits
alpha10
...
0.1.0.1201
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
650553e793 | ||
|
|
de676eb498 | ||
|
|
d334286cbd | ||
|
|
538331c847 | ||
|
|
df1af592b6 | ||
|
|
7db5b12629 | ||
|
|
cc3264f058 | ||
|
|
ea12322686 | ||
|
|
5a9e5bc77d | ||
|
|
f489800663 | ||
|
|
49056dc605 | ||
|
|
9567590e47 | ||
|
|
008cb10b30 | ||
|
|
6b8e9a3580 | ||
|
|
6aab10b7d0 | ||
|
|
7de46bf590 | ||
|
|
c67b794f40 | ||
|
|
9872a376c1 | ||
|
|
f34718f352 | ||
|
|
08520a90ff | ||
|
|
264128d1fb | ||
|
|
dbb617f516 |
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@@ -43,11 +43,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-lint-test
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- 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
|
||||
path: .
|
||||
|
||||
- name: 📦 Add extension zip file to release
|
||||
env:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@ bin/
|
||||
build
|
||||
tlsn/
|
||||
zip
|
||||
.vscode
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# Chrome Extension (MV3) for TLSNotary
|
||||
|
||||
> [!IMPORTANT]
|
||||
> ⚠️ When running the extension against a [notary server](https://github.com/tlsnotary/tlsn/tree/dev/notary-server), please ensure that the server's version is the same as the version of this extension
|
||||
> ⚠️ When running the extension against a [notary server](https://github.com/tlsnotary/tlsn/tree/main/crates/notary/server), please ensure that the server's version is the same as the version of this extension
|
||||
|
||||
## License
|
||||
This repository is licensed under either of
|
||||
|
||||
2278
package-lock.json
generated
2278
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tlsn-extension",
|
||||
"version": "0.1.0.900",
|
||||
"version": "0.1.0.1201",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -16,7 +16,7 @@
|
||||
"lint:fix": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@extism/extism": "^1.0.3",
|
||||
"@extism/extism": "^2.0.0-rc11",
|
||||
"@fortawesome/fontawesome-free": "^6.4.2",
|
||||
"async-mutex": "^0.4.0",
|
||||
"buffer": "^6.0.3",
|
||||
@@ -40,8 +40,7 @@
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-thunk": "^2.4.2",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tlsn-js": "0.1.0-alpha.9",
|
||||
"tlsn-js-v5": "npm:tlsn-js@0.1.0-alpha.5.4"
|
||||
"tlsn-js": "0.1.0-alpha.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.12",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -9,7 +9,6 @@ import Icon from '../Icon';
|
||||
import browser from 'webextension-polyfill';
|
||||
import classNames from 'classnames';
|
||||
import { useNavigate } from 'react-router';
|
||||
import PluginUploadInfo from '../PluginInfo';
|
||||
|
||||
export function MenuIcon(): ReactElement {
|
||||
const [opened, setOpen] = useState(false);
|
||||
@@ -54,24 +53,24 @@ export default function Menu(props: {
|
||||
<div className="absolute top-[100%] right-0 rounded-md z-20">
|
||||
<div className="flex flex-col bg-slate-200 w-40 shadow rounded-md py">
|
||||
<MenuRow
|
||||
fa="fa-solid fa-plus"
|
||||
fa="fa-solid fa-hammer"
|
||||
className="relative"
|
||||
onClick={() => {
|
||||
navigate('/custom');
|
||||
props.setOpen(false);
|
||||
}}
|
||||
>
|
||||
<span>Custom</span>
|
||||
</MenuRow>
|
||||
<MenuRow
|
||||
fa="fa-solid fa-certificate"
|
||||
className="relative"
|
||||
onClick={() => {
|
||||
props.setOpen(false);
|
||||
navigate('/verify');
|
||||
}}
|
||||
>
|
||||
<PluginUploadInfo onPluginInstalled={() => props.setOpen(false)} />
|
||||
<span>Install Plugin</span>
|
||||
</MenuRow>
|
||||
<MenuRow
|
||||
fa="fa-solid fa-toolbox"
|
||||
className="border-b border-slate-300"
|
||||
onClick={() => {
|
||||
props.setOpen(false);
|
||||
navigate('/plugins');
|
||||
}}
|
||||
>
|
||||
Plugins
|
||||
Verify
|
||||
</MenuRow>
|
||||
<MenuRow
|
||||
className="lg:hidden"
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
Children,
|
||||
MouseEventHandler,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { makePlugin, getPluginConfig } from '../../utils/misc';
|
||||
import { addPlugin } from '../../utils/rpc';
|
||||
import React, { Children, MouseEventHandler, ReactNode } from 'react';
|
||||
import Modal, {
|
||||
ModalHeader,
|
||||
ModalContent,
|
||||
@@ -22,77 +12,9 @@ import {
|
||||
MultipleParts,
|
||||
PermissionDescription,
|
||||
} from '../../utils/plugins';
|
||||
import { ErrorModal } from '../ErrorModal';
|
||||
import classNames from 'classnames';
|
||||
import DefaultPluginIcon from '../../assets/img/default-plugin-icon.png';
|
||||
|
||||
export default function PluginUploadInfo({
|
||||
onPluginInstalled,
|
||||
}: {
|
||||
onPluginInstalled?: () => void;
|
||||
}): ReactElement {
|
||||
const [error, showError] = useState('');
|
||||
const [pluginBuffer, setPluginBuffer] = useState<ArrayBuffer | any>(null);
|
||||
const [pluginContent, setPluginContent] = useState<PluginConfig | null>(null);
|
||||
|
||||
const onAddPlugin = useCallback(
|
||||
async (evt: React.MouseEvent<HTMLButtonElement>) => {
|
||||
try {
|
||||
await addPlugin(Buffer.from(pluginBuffer).toString('hex'));
|
||||
setPluginContent(null);
|
||||
onPluginInstalled?.();
|
||||
} catch (e: any) {
|
||||
showError(e?.message || 'Invalid Plugin');
|
||||
}
|
||||
},
|
||||
[pluginContent, pluginBuffer],
|
||||
);
|
||||
|
||||
const onPluginInfo = useCallback(
|
||||
async (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!evt.target.files) return;
|
||||
try {
|
||||
const [file] = evt.target.files;
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const plugin = await makePlugin(arrayBuffer);
|
||||
setPluginContent(await getPluginConfig(plugin));
|
||||
setPluginBuffer(arrayBuffer);
|
||||
} catch (e: any) {
|
||||
showError(e?.message || 'Invalid Plugin');
|
||||
} finally {
|
||||
evt.target.value = '';
|
||||
}
|
||||
},
|
||||
[setPluginContent, setPluginBuffer],
|
||||
);
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
setPluginContent(null);
|
||||
setPluginBuffer(null);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
className="opacity-0 absolute top-0 right-0 h-full w-full cursor-pointer"
|
||||
type="file"
|
||||
onChange={onPluginInfo}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
{error && <ErrorModal onClose={() => showError('')} message={error} />}
|
||||
{pluginContent && (
|
||||
<PluginInfoModal
|
||||
pluginContent={pluginContent}
|
||||
onClose={onClose}
|
||||
onAddPlugin={onAddPlugin}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function PluginInfoModalHeader(props: {
|
||||
className?: string;
|
||||
children: ReactNode | ReactNode[];
|
||||
|
||||
@@ -5,7 +5,12 @@ import React, {
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { fetchPluginHashes, removePlugin, runPlugin } from '../../utils/rpc';
|
||||
import {
|
||||
fetchPluginHashes,
|
||||
removePlugin,
|
||||
runPlugin,
|
||||
addPlugin,
|
||||
} from '../../utils/rpc';
|
||||
import { usePluginHashes } from '../../reducers/plugins';
|
||||
import {
|
||||
getPluginConfig,
|
||||
@@ -23,7 +28,7 @@ import {
|
||||
PluginInfoModalContent,
|
||||
PluginInfoModalHeader,
|
||||
} from '../PluginInfo';
|
||||
import { getPluginConfigByHash } from '../../entries/Background/db';
|
||||
import { getPluginConfigByUrl } from '../../entries/Background/db';
|
||||
import { SidePanelActionTypes } from '../../entries/SidePanel/types';
|
||||
import { openSidePanel } from '../../entries/utils';
|
||||
|
||||
@@ -31,20 +36,78 @@ export function PluginList({
|
||||
className,
|
||||
unremovable,
|
||||
onClick,
|
||||
showAddButton = false,
|
||||
}: {
|
||||
className?: string;
|
||||
unremovable?: boolean;
|
||||
onClick?: (hash: string) => void;
|
||||
showAddButton?: boolean;
|
||||
}): ReactElement {
|
||||
const hashes = usePluginHashes();
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchPluginHashes();
|
||||
}, []);
|
||||
|
||||
const handleFileUpload = useCallback(
|
||||
async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
if (!file.name.endsWith('.wasm')) {
|
||||
alert('Please select a .wasm file');
|
||||
return;
|
||||
}
|
||||
|
||||
setUploading(true);
|
||||
try {
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const hex = Buffer.from(arrayBuffer).toString('hex');
|
||||
const url = `file://${file.name}`;
|
||||
|
||||
await addPlugin(hex, url);
|
||||
await fetchPluginHashes();
|
||||
} catch (error: any) {
|
||||
alert(`Failed to add plugin: ${error.message}`);
|
||||
} finally {
|
||||
setUploading(false);
|
||||
e.target.value = '';
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNames('flex flex-col flex-nowrap gap-1', className)}>
|
||||
{!hashes.length && (
|
||||
{showAddButton && (
|
||||
<div className="relative">
|
||||
<input
|
||||
type="file"
|
||||
accept=".wasm"
|
||||
onChange={handleFileUpload}
|
||||
disabled={uploading}
|
||||
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10"
|
||||
/>
|
||||
<button
|
||||
className="flex flex-row items-center justify-center gap-2 p-3 border-2 border-dashed border-slate-300 rounded-lg text-slate-500 hover:text-slate-700 hover:border-slate-400 transition-colors cursor-pointer w-full"
|
||||
disabled={uploading}
|
||||
>
|
||||
{uploading ? (
|
||||
<>
|
||||
<Icon fa="fa-solid fa-spinner" className="animate-spin" />
|
||||
<span>Adding Plugin...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Icon fa="fa-solid fa-plus" />
|
||||
<span>Add Plugin (.wasm file)</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{!hashes.length && !showAddButton && (
|
||||
<div className="flex flex-col items-center justify-center text-slate-400 cursor-default select-none">
|
||||
<div>No available plugins</div>
|
||||
</div>
|
||||
@@ -110,7 +173,7 @@ export function Plugin({
|
||||
if (hex) {
|
||||
setConfig(await getPluginConfig(hexToArrayBuffer(hex)));
|
||||
} else {
|
||||
setConfig(await getPluginConfigByHash(hash));
|
||||
setConfig(await getPluginConfigByUrl(hash));
|
||||
}
|
||||
})();
|
||||
}, [hash, hex]);
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
MAX_RECEIVED_LS_KEY,
|
||||
getMaxRecv,
|
||||
getMaxSent,
|
||||
getDeveloperMode,
|
||||
} from '../../utils/storage';
|
||||
import { MAX_RECV, MAX_SENT } from '../../utils/constants';
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import NodeCache from 'node-cache';
|
||||
|
||||
let RequestsLogs: {
|
||||
[tabId: string]: NodeCache;
|
||||
} = {};
|
||||
|
||||
export const deleteCacheByTabId = (tabId: number) => {
|
||||
delete RequestsLogs[tabId];
|
||||
};
|
||||
|
||||
export const getCacheByTabId = (tabId: number): NodeCache => {
|
||||
RequestsLogs[tabId] =
|
||||
RequestsLogs[tabId] ||
|
||||
new NodeCache({
|
||||
stdTTL: 60 * 5, // default 5m TTL
|
||||
maxKeys: 1000000,
|
||||
});
|
||||
|
||||
return RequestsLogs[tabId];
|
||||
};
|
||||
|
||||
export const clearRequestCache = () => {
|
||||
RequestsLogs = {};
|
||||
};
|
||||
|
||||
export const clearCache = () => {
|
||||
clearRequestCache();
|
||||
};
|
||||
@@ -1,6 +1,12 @@
|
||||
import { Level } from 'level';
|
||||
import { AbstractSublevel } from 'abstract-level';
|
||||
import { PluginConfig, PluginMetadata, sha256, urlify } from '../../utils/misc';
|
||||
import { RequestHistory, RequestProgress } from './rpc';
|
||||
import {
|
||||
RequestHistory,
|
||||
RequestLog,
|
||||
RequestProgress,
|
||||
UpsertRequestLog,
|
||||
} from './rpc';
|
||||
import mutex from './mutex';
|
||||
import { minimatch } from 'minimatch';
|
||||
const charwise = require('charwise');
|
||||
@@ -23,12 +29,6 @@ const pluginMetadataDb = db.sublevel<string, PluginMetadata>('pluginMetadata', {
|
||||
const connectionDb = db.sublevel<string, boolean>('connections', {
|
||||
valueEncoding: 'json',
|
||||
});
|
||||
const cookiesDb = db.sublevel<string, boolean>('cookies', {
|
||||
valueEncoding: 'json',
|
||||
});
|
||||
const headersDb = db.sublevel<string, boolean>('headers', {
|
||||
valueEncoding: 'json',
|
||||
});
|
||||
const localStorageDb = db.sublevel<string, any>('sessionStorage', {
|
||||
valueEncoding: 'json',
|
||||
});
|
||||
@@ -38,8 +38,75 @@ const sessionStorageDb = db.sublevel<string, any>('localStorage', {
|
||||
const appDb = db.sublevel<string, any>('app', {
|
||||
valueEncoding: 'json',
|
||||
});
|
||||
enum AppDatabaseKey {
|
||||
DefaultPluginsInstalled = 'DefaultPluginsInstalled',
|
||||
const requestDb = db.sublevel<string, any>('requests', {
|
||||
valueEncoding: 'json',
|
||||
});
|
||||
|
||||
export async function upsertRequestLog(request: UpsertRequestLog) {
|
||||
const existing = await getRequestLog(request.requestId);
|
||||
|
||||
if (existing) {
|
||||
await requestDb.put(request.requestId, {
|
||||
...existing,
|
||||
...request,
|
||||
});
|
||||
} else if (request.url) {
|
||||
const host = urlify(request.url)?.host;
|
||||
if (host) {
|
||||
await requestDb.put(request.requestId, request);
|
||||
await requestDb
|
||||
.sublevel(request.tabId.toString())
|
||||
.put(request.requestId, '');
|
||||
await requestDb.sublevel(host).put(request.requestId, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRequestLog(
|
||||
requestId: string,
|
||||
): Promise<RequestLog | null> {
|
||||
return requestDb.get(requestId).catch(() => null);
|
||||
}
|
||||
|
||||
export async function removeRequestLog(requestId: string) {
|
||||
const existing = await getRequestLog(requestId);
|
||||
if (existing) {
|
||||
await requestDb.del(requestId);
|
||||
await requestDb.sublevel(existing.tabId.toString()).del(requestId);
|
||||
const host = urlify(existing.url)?.host;
|
||||
if (host) {
|
||||
await requestDb.sublevel(host).del(requestId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeRequestLogsByTabId(tabId: number) {
|
||||
const requests = requestDb.sublevel(tabId.toString());
|
||||
for await (const [requestId] of requests.iterator()) {
|
||||
await removeRequestLog(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRequestLogsByTabId(tabId: number) {
|
||||
const requests = requestDb.sublevel(tabId.toString());
|
||||
const ret: RequestLog[] = [];
|
||||
for await (const [requestId] of requests.iterator()) {
|
||||
ret.push(await requestDb.get(requestId));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
export async function getRequestLogsByHost(host: string) {
|
||||
const requests = requestDb.sublevel(host);
|
||||
const ret: RequestLog[] = [];
|
||||
for await (const [requestId] of requests.iterator()) {
|
||||
ret.push(await requestDb.get(requestId));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
export async function clearAllRequestLogs() {
|
||||
await requestDb.clear();
|
||||
}
|
||||
|
||||
export async function addNotaryRequest(
|
||||
@@ -75,6 +142,17 @@ export async function addNotaryRequestProofs(
|
||||
return newReq;
|
||||
}
|
||||
|
||||
export async function setNotaryRequestSessionId(
|
||||
id: string,
|
||||
sessionId: string,
|
||||
): Promise<RequestHistory | null> {
|
||||
const existing = await historyDb.get(id);
|
||||
if (!existing) return null;
|
||||
const newReq: RequestHistory = { ...existing, sessionId };
|
||||
await historyDb.put(id, newReq);
|
||||
return newReq;
|
||||
}
|
||||
|
||||
export async function setNotaryRequestStatus(
|
||||
id: string,
|
||||
status: '' | 'pending' | 'success' | 'error',
|
||||
@@ -188,41 +266,44 @@ export async function getPluginHashes(): Promise<string[]> {
|
||||
return retVal;
|
||||
}
|
||||
|
||||
export async function getPluginByHash(hash: string): Promise<string | null> {
|
||||
export async function getPluginByUrl(url: string): Promise<string | null> {
|
||||
try {
|
||||
const plugin = await pluginDb.get(hash);
|
||||
const plugin = await pluginDb.get(url);
|
||||
return plugin;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function addPlugin(hex: string): Promise<string | null> {
|
||||
export async function addPlugin(
|
||||
hex: string,
|
||||
url: string,
|
||||
): Promise<string | null> {
|
||||
const hash = await sha256(hex);
|
||||
|
||||
if (await getPluginByHash(hash)) {
|
||||
return null;
|
||||
if (await getPluginByUrl(url)) {
|
||||
return url;
|
||||
}
|
||||
|
||||
await pluginDb.put(hash, hex);
|
||||
await pluginDb.put(url, hex);
|
||||
return hash;
|
||||
}
|
||||
|
||||
export async function removePlugin(hash: string): Promise<string | null> {
|
||||
const existing = await pluginDb.get(hash);
|
||||
export async function removePlugin(url: string): Promise<string | null> {
|
||||
const existing = await pluginDb.get(url);
|
||||
|
||||
if (!existing) return null;
|
||||
|
||||
await pluginDb.del(hash);
|
||||
await pluginDb.del(url);
|
||||
|
||||
return hash;
|
||||
return url;
|
||||
}
|
||||
|
||||
export async function getPluginConfigByHash(
|
||||
hash: string,
|
||||
export async function getPluginConfigByUrl(
|
||||
url: string,
|
||||
): Promise<PluginConfig | null> {
|
||||
try {
|
||||
const config = await pluginConfigDb.get(hash);
|
||||
const config = await pluginConfigDb.get(url);
|
||||
return config;
|
||||
} catch (e) {
|
||||
return null;
|
||||
@@ -230,25 +311,25 @@ export async function getPluginConfigByHash(
|
||||
}
|
||||
|
||||
export async function addPluginConfig(
|
||||
hash: string,
|
||||
url: string,
|
||||
config: PluginConfig,
|
||||
): Promise<PluginConfig | null> {
|
||||
if (await getPluginConfigByHash(hash)) {
|
||||
if (await getPluginConfigByUrl(url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await pluginConfigDb.put(hash, config);
|
||||
await pluginConfigDb.put(url, config);
|
||||
return config;
|
||||
}
|
||||
|
||||
export async function removePluginConfig(
|
||||
hash: string,
|
||||
url: string,
|
||||
): Promise<PluginConfig | null> {
|
||||
const existing = await pluginConfigDb.get(hash);
|
||||
const existing = await pluginConfigDb.get(url);
|
||||
|
||||
if (!existing) return null;
|
||||
|
||||
await pluginConfigDb.del(hash);
|
||||
await pluginConfigDb.del(url);
|
||||
|
||||
return existing;
|
||||
}
|
||||
@@ -259,8 +340,8 @@ export async function getPlugins(): Promise<
|
||||
const hashes = await getPluginHashes();
|
||||
const ret: (PluginConfig & { hash: string; metadata: PluginMetadata })[] = [];
|
||||
for (const hash of hashes) {
|
||||
const config = await getPluginConfigByHash(hash);
|
||||
const metadata = await getPluginMetadataByHash(hash);
|
||||
const config = await getPluginConfigByUrl(hash);
|
||||
const metadata = await getPluginMetadataByUrl(hash);
|
||||
if (config) {
|
||||
ret.push({
|
||||
...config,
|
||||
@@ -281,11 +362,11 @@ export async function getPlugins(): Promise<
|
||||
return ret;
|
||||
}
|
||||
|
||||
export async function getPluginMetadataByHash(
|
||||
hash: string,
|
||||
export async function getPluginMetadataByUrl(
|
||||
url: string,
|
||||
): Promise<PluginMetadata | null> {
|
||||
try {
|
||||
const metadata = await pluginMetadataDb.get(hash);
|
||||
const metadata = await pluginMetadataDb.get(url);
|
||||
return metadata;
|
||||
} catch (e) {
|
||||
return null;
|
||||
@@ -293,21 +374,21 @@ export async function getPluginMetadataByHash(
|
||||
}
|
||||
|
||||
export async function addPluginMetadata(
|
||||
hash: string,
|
||||
url: string,
|
||||
metadata: PluginMetadata,
|
||||
): Promise<PluginMetadata | null> {
|
||||
await pluginMetadataDb.put(hash, metadata);
|
||||
await pluginMetadataDb.put(url, metadata);
|
||||
return metadata;
|
||||
}
|
||||
|
||||
export async function removePluginMetadata(
|
||||
hash: string,
|
||||
url: string,
|
||||
): Promise<PluginMetadata | null> {
|
||||
const existing = await pluginMetadataDb.get(hash);
|
||||
const existing = await pluginMetadataDb.get(url);
|
||||
|
||||
if (!existing) return null;
|
||||
|
||||
await pluginMetadataDb.del(hash);
|
||||
await pluginMetadataDb.del(url);
|
||||
|
||||
return existing;
|
||||
}
|
||||
@@ -336,48 +417,46 @@ export async function setConnection(origin: string) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function setCookies(host: string, name: string, value: string) {
|
||||
return mutex.runExclusive(async () => {
|
||||
await cookiesDb.sublevel(host).put(name, value);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export async function clearCookies(host: string) {
|
||||
return mutex.runExclusive(async () => {
|
||||
await cookiesDb.sublevel(host).clear();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getCookies(link: string, name: string) {
|
||||
try {
|
||||
const existing = await cookiesDb.sublevel(link).get(name);
|
||||
return existing;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCookiesByHost(link: string) {
|
||||
export async function getCookiesByHost(linkOrHost: string) {
|
||||
const ret: { [key: string]: string } = {};
|
||||
const links: { [k: string]: boolean } = {};
|
||||
const url = urlify(link);
|
||||
const url = urlify(linkOrHost);
|
||||
const isHost = !url;
|
||||
const host = isHost ? linkOrHost : url.host;
|
||||
const requests = await getRequestLogsByHost(host);
|
||||
|
||||
for await (const sublevel of cookiesDb.keys({ keyEncoding: 'utf8' })) {
|
||||
const l = sublevel.split('!')[1];
|
||||
links[l] = true;
|
||||
let filteredRequest: RequestLog | null = null;
|
||||
|
||||
for (const request of requests) {
|
||||
if (isHost) {
|
||||
if (!filteredRequest || filteredRequest.updatedAt > request.updatedAt) {
|
||||
filteredRequest = request;
|
||||
}
|
||||
} else {
|
||||
const { origin, pathname } = urlify(request.url) || {};
|
||||
const link = [origin, pathname].join('');
|
||||
if (
|
||||
minimatch(link, linkOrHost) &&
|
||||
(!filteredRequest || filteredRequest.updatedAt > request.updatedAt)
|
||||
) {
|
||||
filteredRequest = request;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cookieLink = url
|
||||
? Object.keys(links).filter((l) => minimatch(l, link))[0]
|
||||
: Object.keys(links).filter((l) => urlify(l)?.host === link)[0];
|
||||
if (!filteredRequest) return ret;
|
||||
|
||||
if (!cookieLink) return ret;
|
||||
|
||||
for await (const [key, value] of cookiesDb.sublevel(cookieLink).iterator()) {
|
||||
ret[key] = value;
|
||||
for (const header of filteredRequest.requestHeaders) {
|
||||
if (header.name.toLowerCase() === 'cookie') {
|
||||
header.value?.split(';').forEach((cookie) => {
|
||||
const i = cookie.indexOf('=');
|
||||
if (i !== -1) {
|
||||
const name = cookie.slice(0, i).trim();
|
||||
ret[name] = cookie.slice(i + 1).trim();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -397,49 +476,40 @@ export async function getConnection(origin: string) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function setHeaders(link: string, name: string, value?: string) {
|
||||
if (!value) return null;
|
||||
return mutex.runExclusive(async () => {
|
||||
await headersDb.sublevel(link).put(name, value);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export async function clearHeaders(host: string) {
|
||||
return mutex.runExclusive(async () => {
|
||||
await headersDb.sublevel(host).clear();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getHeaders(host: string, name: string) {
|
||||
try {
|
||||
const existing = await headersDb.sublevel(host).get(name);
|
||||
return existing;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
export async function getHeadersByHost(link: string) {
|
||||
export async function getHeadersByHost(linkOrHost: string) {
|
||||
const ret: { [key: string]: string } = {};
|
||||
const url = urlify(link);
|
||||
const url = urlify(linkOrHost);
|
||||
const isHost = !url;
|
||||
const host = isHost ? linkOrHost : url.host;
|
||||
const requests = await getRequestLogsByHost(host);
|
||||
|
||||
const links: { [k: string]: boolean } = {};
|
||||
for await (const sublevel of headersDb.keys({ keyEncoding: 'utf8' })) {
|
||||
const l = sublevel.split('!')[1];
|
||||
links[l] = true;
|
||||
let filteredRequest: RequestLog | null = null;
|
||||
|
||||
for (const request of requests) {
|
||||
if (isHost) {
|
||||
if (!filteredRequest || filteredRequest.updatedAt > request.updatedAt) {
|
||||
filteredRequest = request;
|
||||
}
|
||||
} else {
|
||||
const { origin, pathname } = urlify(request.url) || {};
|
||||
const link = [origin, pathname].join('');
|
||||
if (
|
||||
minimatch(link, linkOrHost) &&
|
||||
(!filteredRequest || filteredRequest.updatedAt > request.updatedAt)
|
||||
) {
|
||||
filteredRequest = request;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const headerLink = url
|
||||
? Object.keys(links).filter((l) => minimatch(l, link))[0]
|
||||
: Object.keys(links).filter((l) => urlify(l)?.host === link)[0];
|
||||
if (!filteredRequest) return ret;
|
||||
|
||||
if (!headerLink) return ret;
|
||||
|
||||
for await (const [key, value] of headersDb.sublevel(headerLink).iterator()) {
|
||||
ret[key] = value;
|
||||
for (const header of filteredRequest.requestHeaders) {
|
||||
if (header.name.toLowerCase() !== 'cookie') {
|
||||
ret[header.name] = header.value || '';
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -495,20 +565,61 @@ export async function getSessionStorageByHost(host: string) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
async function getDefaultPluginsInstalled(): Promise<string | boolean> {
|
||||
return appDb.get(AppDatabaseKey.DefaultPluginsInstalled).catch(() => false);
|
||||
}
|
||||
|
||||
export async function setDefaultPluginsInstalled(
|
||||
installed: string | boolean = false,
|
||||
) {
|
||||
export async function resetDB() {
|
||||
return mutex.runExclusive(async () => {
|
||||
await appDb.put(AppDatabaseKey.DefaultPluginsInstalled, installed);
|
||||
return Promise.all([
|
||||
localStorageDb.clear(),
|
||||
sessionStorageDb.clear(),
|
||||
requestDb.clear(),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAppState() {
|
||||
return {
|
||||
defaultPluginsInstalled: await getDefaultPluginsInstalled(),
|
||||
};
|
||||
export async function getDBSizeByRoot(
|
||||
rootDB: AbstractSublevel<Level, any, any, any>,
|
||||
): Promise<number> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let size = 0;
|
||||
|
||||
for await (const sublevel of rootDB.keys({ keyEncoding: 'utf8' })) {
|
||||
const link = sublevel.split('!')[1];
|
||||
const sub = rootDB.sublevel(link);
|
||||
for await (const [key, value] of sub.iterator()) {
|
||||
size += key.length + value.length;
|
||||
}
|
||||
}
|
||||
|
||||
resolve(size);
|
||||
});
|
||||
}
|
||||
|
||||
export async function getRecursiveDBSize(
|
||||
db: AbstractSublevel<Level, any, any, any>,
|
||||
): Promise<number> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let size = 0;
|
||||
for await (const sublevel of db.keys({ keyEncoding: 'utf8' })) {
|
||||
const parts = sublevel.split('!');
|
||||
if (parts.length === 1) {
|
||||
const value = await db.get(parts[0]);
|
||||
size += parts[0].length + (value ? JSON.stringify(value).length : 0);
|
||||
} else {
|
||||
const sub = db.sublevel(parts[1]);
|
||||
size +=
|
||||
(await getRecursiveDBSize(
|
||||
sub as unknown as AbstractSublevel<Level, any, any, any>,
|
||||
)) + parts[1].length;
|
||||
}
|
||||
}
|
||||
resolve(size);
|
||||
});
|
||||
}
|
||||
|
||||
export async function getDBSize(): Promise<number> {
|
||||
const sizes = await Promise.all([
|
||||
getDBSizeByRoot(localStorageDb),
|
||||
getDBSizeByRoot(sessionStorageDb),
|
||||
getRecursiveDBSize(requestDb),
|
||||
]);
|
||||
return sizes.reduce((a, b) => a + b, 0);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { getCacheByTabId } from './cache';
|
||||
import { BackgroundActiontype, RequestLog } from './rpc';
|
||||
import { BackgroundActiontype } from './rpc';
|
||||
import mutex from './mutex';
|
||||
import browser from 'webextension-polyfill';
|
||||
import { addRequest } from '../../reducers/requests';
|
||||
import { urlify } from '../../utils/misc';
|
||||
import { getHeadersByHost, setCookies, setHeaders } from './db';
|
||||
import { getRequestLog, upsertRequestLog } from './db';
|
||||
|
||||
export const onSendHeaders = (
|
||||
details: browser.WebRequest.OnSendHeadersDetailsType,
|
||||
) => {
|
||||
@@ -12,40 +12,22 @@ export const onSendHeaders = (
|
||||
const { method, tabId, requestId } = details;
|
||||
|
||||
if (method !== 'OPTIONS') {
|
||||
const cache = getCacheByTabId(tabId);
|
||||
const existing = cache.get<RequestLog>(requestId);
|
||||
const { origin, pathname } = urlify(details.url) || {};
|
||||
|
||||
const link = [origin, pathname].join('');
|
||||
|
||||
if (link && details.requestHeaders) {
|
||||
details.requestHeaders.forEach((header) => {
|
||||
const { name, value } = header;
|
||||
if (/^cookie$/i.test(name) && value) {
|
||||
value.split(';').forEach((cookieStr) => {
|
||||
const index = cookieStr.indexOf('=');
|
||||
if (index !== -1) {
|
||||
const cookieName = cookieStr.slice(0, index).trim();
|
||||
const cookieValue = cookieStr.slice(index + 1);
|
||||
setCookies(link, cookieName, cookieValue);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setHeaders(link, name, value);
|
||||
}
|
||||
upsertRequestLog({
|
||||
method: details.method as 'GET' | 'POST',
|
||||
type: details.type,
|
||||
url: details.url,
|
||||
initiator: details.initiator || null,
|
||||
requestHeaders: details.requestHeaders || [],
|
||||
tabId: tabId,
|
||||
requestId: requestId,
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
cache.set(requestId, {
|
||||
...existing,
|
||||
method: details.method as 'GET' | 'POST',
|
||||
type: details.type,
|
||||
url: details.url,
|
||||
initiator: details.initiator || null,
|
||||
requestHeaders: details.requestHeaders || [],
|
||||
tabId: tabId,
|
||||
requestId: requestId,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -59,24 +41,30 @@ export const onBeforeRequest = (
|
||||
if (method === 'OPTIONS') return;
|
||||
|
||||
if (requestBody) {
|
||||
const cache = getCacheByTabId(tabId);
|
||||
const existing = cache.get<RequestLog>(requestId);
|
||||
|
||||
if (requestBody.raw && requestBody.raw[0]?.bytes) {
|
||||
try {
|
||||
cache.set(requestId, {
|
||||
...existing,
|
||||
await upsertRequestLog({
|
||||
requestBody: Buffer.from(requestBody.raw[0].bytes).toString(
|
||||
'utf-8',
|
||||
),
|
||||
requestId: requestId,
|
||||
tabId: tabId,
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
} else if (requestBody.formData) {
|
||||
cache.set(requestId, {
|
||||
...existing,
|
||||
formData: requestBody.formData,
|
||||
await upsertRequestLog({
|
||||
formData: Object.fromEntries(
|
||||
Object.entries(requestBody.formData).map(([key, value]) => [
|
||||
key,
|
||||
Array.isArray(value) ? value : [value],
|
||||
]),
|
||||
),
|
||||
requestId: requestId,
|
||||
tabId: tabId,
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -91,12 +79,7 @@ export const onResponseStarted = (
|
||||
|
||||
if (method === 'OPTIONS') return;
|
||||
|
||||
const cache = getCacheByTabId(tabId);
|
||||
|
||||
const existing = cache.get<RequestLog>(requestId);
|
||||
const newLog: RequestLog = {
|
||||
requestHeaders: [],
|
||||
...existing,
|
||||
await upsertRequestLog({
|
||||
method: details.method,
|
||||
type: details.type,
|
||||
url: details.url,
|
||||
@@ -104,9 +87,15 @@ export const onResponseStarted = (
|
||||
tabId: tabId,
|
||||
requestId: requestId,
|
||||
responseHeaders,
|
||||
};
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
|
||||
cache.set(requestId, newLog);
|
||||
const newLog = await getRequestLog(requestId);
|
||||
|
||||
if (!newLog) {
|
||||
console.error('Request log not found', requestId);
|
||||
return;
|
||||
}
|
||||
|
||||
chrome.runtime.sendMessage({
|
||||
type: BackgroundActiontype.push_action,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { onBeforeRequest, onResponseStarted, onSendHeaders } from './handlers';
|
||||
import { deleteCacheByTabId } from './cache';
|
||||
import browser from 'webextension-polyfill';
|
||||
import { getAppState, removePlugin, setDefaultPluginsInstalled } from './db';
|
||||
import { removePlugin, removeRequestLogsByTabId } from './db';
|
||||
import { installPlugin } from './plugins/utils';
|
||||
|
||||
(async () => {
|
||||
@@ -30,43 +29,9 @@ import { installPlugin } from './plugins/utils';
|
||||
);
|
||||
|
||||
browser.tabs.onRemoved.addListener((tabId) => {
|
||||
deleteCacheByTabId(tabId);
|
||||
removeRequestLogsByTabId(tabId);
|
||||
});
|
||||
|
||||
const { defaultPluginsInstalled } = await getAppState();
|
||||
|
||||
switch (defaultPluginsInstalled) {
|
||||
case false: {
|
||||
try {
|
||||
const twitterProfileUrl = browser.runtime.getURL(
|
||||
'twitter_profile.wasm',
|
||||
);
|
||||
const discordDmUrl = browser.runtime.getURL('discord_dm.wasm');
|
||||
await installPlugin(twitterProfileUrl);
|
||||
await installPlugin(discordDmUrl);
|
||||
} finally {
|
||||
await setDefaultPluginsInstalled('0.1.0.703');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case true: {
|
||||
try {
|
||||
await removePlugin(
|
||||
'6931d2ad63340d3a1fb1a5c1e3f4454c5a518164d6de5ad272e744832355ee02',
|
||||
);
|
||||
const twitterProfileUrl = browser.runtime.getURL(
|
||||
'twitter_profile.wasm',
|
||||
);
|
||||
await installPlugin(twitterProfileUrl);
|
||||
} finally {
|
||||
await setDefaultPluginsInstalled('0.1.0.703');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '0.1.0.703':
|
||||
break;
|
||||
}
|
||||
|
||||
const { initRPC } = await import('./rpc');
|
||||
await createOffscreenDocument();
|
||||
initRPC();
|
||||
|
||||
@@ -2,25 +2,20 @@ import { addPlugin, addPluginConfig, addPluginMetadata } from '../db';
|
||||
import { getPluginConfig } from '../../../utils/misc';
|
||||
|
||||
export async function installPlugin(
|
||||
urlOrBuffer: ArrayBuffer | string,
|
||||
url: string,
|
||||
origin = '',
|
||||
filePath = '',
|
||||
metadata: {[key: string]: string} = {},
|
||||
) {
|
||||
let arrayBuffer;
|
||||
|
||||
if (typeof urlOrBuffer === 'string') {
|
||||
const resp = await fetch(urlOrBuffer);
|
||||
arrayBuffer = await resp.arrayBuffer();
|
||||
} else {
|
||||
arrayBuffer = urlOrBuffer;
|
||||
}
|
||||
const resp = await fetch(url);
|
||||
const arrayBuffer = await resp.arrayBuffer();
|
||||
|
||||
const config = await getPluginConfig(arrayBuffer);
|
||||
const hex = Buffer.from(arrayBuffer).toString('hex');
|
||||
const hash = await addPlugin(hex);
|
||||
await addPluginConfig(hash!, config);
|
||||
await addPluginMetadata(hash!, {
|
||||
const hash = await addPlugin(hex, url);
|
||||
|
||||
await addPluginConfig(url, config);
|
||||
await addPluginMetadata(url, {
|
||||
...metadata,
|
||||
origin,
|
||||
filePath,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import browser from 'webextension-polyfill';
|
||||
import { clearCache, getCacheByTabId } from './cache';
|
||||
import { addRequestHistory, setRequests } from '../../reducers/history';
|
||||
import {
|
||||
addNotaryRequest,
|
||||
@@ -12,23 +11,19 @@ import {
|
||||
setNotaryRequestVerification,
|
||||
addPlugin,
|
||||
getPluginHashes,
|
||||
getPluginByHash,
|
||||
getPluginByUrl,
|
||||
removePlugin,
|
||||
addPluginConfig,
|
||||
getPluginConfigByHash,
|
||||
getPluginConfigByUrl,
|
||||
removePluginConfig,
|
||||
getConnection,
|
||||
setConnection,
|
||||
deleteConnection,
|
||||
addPluginMetadata,
|
||||
getPlugins,
|
||||
getCookiesByHost,
|
||||
getHeadersByHost,
|
||||
getAppState,
|
||||
setDefaultPluginsInstalled,
|
||||
setLocalStorage,
|
||||
setSessionStorage,
|
||||
setNotaryRequestProgress,
|
||||
getRequestLogsByTabId,
|
||||
clearAllRequestLogs,
|
||||
setNotaryRequestSessionId,
|
||||
} from './db';
|
||||
import { addOnePlugin, removeOnePlugin } from '../../reducers/plugins';
|
||||
import {
|
||||
@@ -36,7 +31,6 @@ import {
|
||||
getPluginConfig,
|
||||
hexToArrayBuffer,
|
||||
makePlugin,
|
||||
PluginConfig,
|
||||
} from '../../utils/misc';
|
||||
import {
|
||||
getLoggingFilter,
|
||||
@@ -47,7 +41,6 @@ import {
|
||||
getRendezvousApi,
|
||||
} from '../../utils/storage';
|
||||
import { deferredPromise } from '../../utils/promise';
|
||||
import { minimatch } from 'minimatch';
|
||||
import { OffscreenActionTypes } from '../Offscreen/types';
|
||||
import { SidePanelActionTypes } from '../SidePanel/types';
|
||||
import { pushToRedux } from '../utils';
|
||||
@@ -61,6 +54,7 @@ import {
|
||||
sendMessage,
|
||||
sendPairedMessage,
|
||||
} from './ws';
|
||||
|
||||
import { parseHttpMessage } from '../../utils/parser';
|
||||
import { mapStringToRange, subtractRanges } from 'tlsn-js';
|
||||
import { PresentationJSON } from 'tlsn-js/build/types';
|
||||
@@ -95,20 +89,10 @@ export enum BackgroundActiontype {
|
||||
// Content Script
|
||||
open_popup = 'open_popup',
|
||||
change_route = 'change_route',
|
||||
connect_request = 'connect_request',
|
||||
connect_response = 'connect_response',
|
||||
get_history_request = 'get_history_request',
|
||||
get_history_response = 'get_history_response',
|
||||
get_proof_request = 'get_proof_request',
|
||||
get_proof_response = 'get_proof_response',
|
||||
notarize_request = 'notarize_request',
|
||||
notarize_response = 'notarize_response',
|
||||
install_plugin_request = 'install_plugin_request',
|
||||
install_plugin_response = 'install_plugin_response',
|
||||
get_plugins_request = 'get_plugins_request',
|
||||
get_plugins_response = 'get_plugins_response',
|
||||
run_plugin_request = 'run_plugin_request',
|
||||
run_plugin_response = 'run_plugin_response',
|
||||
run_plugin_by_url_request = 'run_plugin_by_url_request',
|
||||
run_plugin_by_url_response = 'run_plugin_by_url_response',
|
||||
get_secrets_from_transcript = 'get_secrets_from_transcript',
|
||||
// App State
|
||||
get_logging_level = 'get_logging_level',
|
||||
@@ -158,6 +142,23 @@ export type RequestLog = {
|
||||
[k: string]: string[];
|
||||
};
|
||||
responseHeaders?: browser.WebRequest.HttpHeaders;
|
||||
updatedAt: number;
|
||||
};
|
||||
|
||||
export type UpsertRequestLog = {
|
||||
requestId: string;
|
||||
tabId: number;
|
||||
method?: string;
|
||||
type?: string;
|
||||
url?: string;
|
||||
initiator?: string | null;
|
||||
requestHeaders?: browser.WebRequest.HttpHeaders;
|
||||
requestBody?: string;
|
||||
formData?: {
|
||||
[k: string]: string[];
|
||||
};
|
||||
responseHeaders?: browser.WebRequest.HttpHeaders;
|
||||
updatedAt: number;
|
||||
};
|
||||
|
||||
export enum RequestProgress {
|
||||
@@ -220,6 +221,7 @@ export type RequestHistory = {
|
||||
metadata?: {
|
||||
[k: string]: string;
|
||||
};
|
||||
sessionId?: string;
|
||||
};
|
||||
|
||||
export const initRPC = () => {
|
||||
@@ -229,7 +231,7 @@ export const initRPC = () => {
|
||||
case BackgroundActiontype.get_requests:
|
||||
return handleGetRequests(request, sendResponse);
|
||||
case BackgroundActiontype.clear_requests:
|
||||
clearCache();
|
||||
clearAllRequestLogs().then(() => pushToRedux(setRequests([])));
|
||||
return sendResponse();
|
||||
case BackgroundActiontype.get_prove_requests:
|
||||
return handleGetProveRequests(request, sendResponse);
|
||||
@@ -270,29 +272,13 @@ export const initRPC = () => {
|
||||
return handleExecP2PPluginProver(request);
|
||||
case BackgroundActiontype.open_popup:
|
||||
return handleOpenPopup(request);
|
||||
case BackgroundActiontype.connect_request:
|
||||
return handleConnect(request);
|
||||
case BackgroundActiontype.get_history_request:
|
||||
return handleGetHistory(request);
|
||||
case BackgroundActiontype.get_proof_request:
|
||||
return handleGetProof(request);
|
||||
case BackgroundActiontype.notarize_request:
|
||||
return handleNotarizeRequest(request);
|
||||
case BackgroundActiontype.install_plugin_request:
|
||||
return handleInstallPluginRequest(request);
|
||||
case BackgroundActiontype.get_plugins_request:
|
||||
return handleGetPluginsRequest(request);
|
||||
case BackgroundActiontype.run_plugin_request:
|
||||
return handleRunPluginCSRequest(request);
|
||||
case BackgroundActiontype.run_plugin_by_url_request:
|
||||
return handleRunPluginByURLRequest(request);
|
||||
case BackgroundActiontype.get_logging_level:
|
||||
getLoggingFilter().then(sendResponse);
|
||||
return true;
|
||||
case BackgroundActiontype.get_app_state:
|
||||
getAppState().then(sendResponse);
|
||||
return true;
|
||||
case BackgroundActiontype.set_default_plugins_installed:
|
||||
setDefaultPluginsInstalled(request.data).then(sendResponse);
|
||||
return true;
|
||||
case BackgroundActiontype.set_local_storage:
|
||||
return handleSetLocalStorage(request, sender, sendResponse);
|
||||
case BackgroundActiontype.set_session_storage:
|
||||
@@ -364,6 +350,7 @@ export const initRPC = () => {
|
||||
pluginHash: request.data,
|
||||
}).then(sendResponse);
|
||||
return;
|
||||
|
||||
case BackgroundActiontype.get_p2p_state:
|
||||
getP2PState();
|
||||
return;
|
||||
@@ -378,10 +365,7 @@ function handleGetRequests(
|
||||
request: BackgroundAction,
|
||||
sendResponse: (data?: any) => void,
|
||||
): boolean {
|
||||
const cache = getCacheByTabId(request.data);
|
||||
const keys = cache.keys() || [];
|
||||
const data = keys.map((key) => cache.get(key));
|
||||
sendResponse(data);
|
||||
getRequestLogsByTabId(request.data).then(sendResponse);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -407,8 +391,9 @@ async function handleFinishProveRequest(
|
||||
request: BackgroundAction,
|
||||
sendResponse: (data?: any) => void,
|
||||
) {
|
||||
const { id, proof, error, verification } = request.data;
|
||||
const { id, proof, error, verification, sessionId } = request.data;
|
||||
|
||||
console.log('handleFinishProveRequest', request.data);
|
||||
if (proof) {
|
||||
const newReq = await addNotaryRequestProofs(id, proof);
|
||||
if (!newReq) return;
|
||||
@@ -430,6 +415,12 @@ async function handleFinishProveRequest(
|
||||
await pushToRedux(addRequestHistory(await getNotaryRequest(id)));
|
||||
}
|
||||
|
||||
if (sessionId) {
|
||||
const newReq = await setNotaryRequestSessionId(id, sessionId);
|
||||
if (!newReq) return;
|
||||
await pushToRedux(addRequestHistory(await getNotaryRequest(id)));
|
||||
}
|
||||
|
||||
return sendResponse();
|
||||
}
|
||||
|
||||
@@ -538,6 +529,7 @@ async function runPluginProver(request: BackgroundAction, now = Date.now()) {
|
||||
websocketProxyUrl: _websocketProxyUrl,
|
||||
maxSentData: _maxSentData,
|
||||
maxRecvData: _maxRecvData,
|
||||
metadata,
|
||||
} = request.data;
|
||||
const notaryUrl = _notaryUrl || (await getNotaryApi());
|
||||
const websocketProxyUrl = _websocketProxyUrl || (await getProxyApi());
|
||||
@@ -557,6 +549,7 @@ async function runPluginProver(request: BackgroundAction, now = Date.now()) {
|
||||
maxSentData,
|
||||
secretHeaders,
|
||||
secretResps,
|
||||
metadata,
|
||||
});
|
||||
|
||||
await setNotaryRequestStatus(id, 'pending');
|
||||
@@ -686,7 +679,7 @@ async function handleGetSecretsFromTranscript(
|
||||
sendResponse: (data?: any) => void,
|
||||
) {
|
||||
const { pluginHash, pluginHex, p2p, transcript, method } = request.data;
|
||||
const hex = (await getPluginByHash(pluginHash)) || pluginHex;
|
||||
const hex = (await getPluginByUrl(pluginHash)) || pluginHex;
|
||||
const arrayBuffer = hexToArrayBuffer(hex!);
|
||||
const config = await getPluginConfig(arrayBuffer);
|
||||
const plugin = await makePlugin(arrayBuffer, config, p2p);
|
||||
@@ -701,7 +694,7 @@ async function handleGetSecretsFromTranscript(
|
||||
...recvBody.map((body) => body.toString('utf-8')),
|
||||
);
|
||||
|
||||
const secretResps = JSON.parse(out.string());
|
||||
const secretResps = JSON.parse(out?.string() || '{}');
|
||||
await browser.runtime.sendMessage({
|
||||
type: OffscreenActionTypes.get_secrets_from_transcript_success,
|
||||
data: {
|
||||
@@ -712,7 +705,7 @@ async function handleGetSecretsFromTranscript(
|
||||
|
||||
async function runP2PPluginProver(request: BackgroundAction, now = Date.now()) {
|
||||
const {
|
||||
pluginHash,
|
||||
pluginUrl,
|
||||
pluginHex,
|
||||
url,
|
||||
method,
|
||||
@@ -723,24 +716,38 @@ async function runP2PPluginProver(request: BackgroundAction, now = Date.now()) {
|
||||
websocketProxyUrl: _websocketProxyUrl,
|
||||
maxSentData: _maxSentData,
|
||||
maxRecvData: _maxRecvData,
|
||||
clientId,
|
||||
verifierPlugin,
|
||||
notaryUrl,
|
||||
} = request.data;
|
||||
const rendezvousApi = await getRendezvousApi();
|
||||
const proverUrl = `${rendezvousApi}?clientId=${clientId}:proof`;
|
||||
const websocketProxyUrl = _websocketProxyUrl || (await getProxyApi());
|
||||
const maxSentData = _maxSentData || (await getMaxSent());
|
||||
const maxRecvData = _maxRecvData || (await getMaxRecv());
|
||||
|
||||
const { id } = await addNotaryRequest(now, {
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
notaryUrl,
|
||||
websocketProxyUrl,
|
||||
maxRecvData,
|
||||
maxSentData,
|
||||
secretHeaders,
|
||||
secretResps: [],
|
||||
});
|
||||
|
||||
await browser.runtime.sendMessage({
|
||||
type: OffscreenActionTypes.start_p2p_prover,
|
||||
data: {
|
||||
pluginHash,
|
||||
id,
|
||||
pluginUrl,
|
||||
pluginHex,
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
proverUrl,
|
||||
proverUrl: notaryUrl,
|
||||
verifierPlugin,
|
||||
websocketProxyUrl,
|
||||
maxRecvData,
|
||||
maxSentData,
|
||||
@@ -824,10 +831,10 @@ async function handleAddPlugin(
|
||||
sendResponse: (data?: any) => void,
|
||||
) {
|
||||
try {
|
||||
const config = await getPluginConfig(hexToArrayBuffer(request.data));
|
||||
const config = await getPluginConfig(hexToArrayBuffer(request.data.hex));
|
||||
|
||||
if (config) {
|
||||
const hash = await addPlugin(request.data);
|
||||
const hash = await addPlugin(request.data.hex, request.data.url);
|
||||
|
||||
if (hash) {
|
||||
await addPluginConfig(hash, config);
|
||||
@@ -867,7 +874,7 @@ async function handleGetPluginByHash(
|
||||
sendResponse: (data?: any) => void,
|
||||
) {
|
||||
const hash = request.data;
|
||||
const hex = await getPluginByHash(hash);
|
||||
const hex = await getPluginByUrl(hash);
|
||||
return hex;
|
||||
}
|
||||
|
||||
@@ -876,7 +883,7 @@ async function handleGetPluginConfigByHash(
|
||||
sendResponse: (data?: any) => void,
|
||||
) {
|
||||
const hash = request.data;
|
||||
const config = await getPluginConfigByHash(hash);
|
||||
const config = await getPluginConfigByUrl(hash);
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -886,14 +893,14 @@ function handleRunPlugin(
|
||||
) {
|
||||
(async () => {
|
||||
const { hash, method, params, meta } = request.data;
|
||||
const hex = await getPluginByHash(hash);
|
||||
const hex = await getPluginByUrl(hash);
|
||||
const arrayBuffer = hexToArrayBuffer(hex!);
|
||||
const config = await getPluginConfig(arrayBuffer);
|
||||
const plugin = await makePlugin(arrayBuffer, config, meta?.p2p);
|
||||
devlog(`plugin::${method}`, params);
|
||||
const out = await plugin.call(method, params);
|
||||
devlog(`plugin response: `, out.string());
|
||||
sendResponse(JSON.parse(out.string()));
|
||||
devlog(`plugin response: `, out?.string());
|
||||
sendResponse(JSON.parse(out?.string() || '{}'));
|
||||
})();
|
||||
|
||||
return true;
|
||||
@@ -948,180 +955,6 @@ async function handleOpenPopup(request: BackgroundAction) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleConnect(request: BackgroundAction) {
|
||||
const connection = await getConnection(request.data.origin);
|
||||
const [currentTab] = await browser.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
|
||||
if (!connection) {
|
||||
const defer = deferredPromise();
|
||||
|
||||
const { popup, tab } = await openPopup(
|
||||
`connection-approval?origin=${encodeURIComponent(request.data.origin)}&favIconUrl=${encodeURIComponent(currentTab?.favIconUrl || '')}`,
|
||||
request.data.position.left,
|
||||
request.data.position.top,
|
||||
);
|
||||
|
||||
const onMessage = async (req: BackgroundAction) => {
|
||||
if (req.type === BackgroundActiontype.connect_response) {
|
||||
defer.resolve(req.data);
|
||||
if (req.data) {
|
||||
await setConnection(request.data.origin);
|
||||
} else {
|
||||
await deleteConnection(request.data.origin);
|
||||
}
|
||||
browser.runtime.onMessage.removeListener(onMessage);
|
||||
browser.tabs.remove(tab.id!);
|
||||
}
|
||||
};
|
||||
|
||||
const onPopUpClose = (windowId: number) => {
|
||||
if (windowId === popup.id) {
|
||||
defer.resolve(false);
|
||||
browser.windows.onRemoved.removeListener(onPopUpClose);
|
||||
}
|
||||
};
|
||||
|
||||
browser.runtime.onMessage.addListener(onMessage);
|
||||
browser.windows.onRemoved.addListener(onPopUpClose);
|
||||
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleGetHistory(request: BackgroundAction) {
|
||||
const [currentTab] = await browser.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
|
||||
const defer = deferredPromise();
|
||||
const {
|
||||
origin,
|
||||
position,
|
||||
method: filterMethod,
|
||||
url: filterUrl,
|
||||
metadata: filterMetadata,
|
||||
} = request.data;
|
||||
|
||||
const { popup, tab } = await openPopup(
|
||||
`get-history-approval?${filterMetadata ? `metadata=${JSON.stringify(filterMetadata)}&` : ''}method=${filterMethod}&url=${filterUrl}&origin=${encodeURIComponent(origin)}&favIconUrl=${encodeURIComponent(currentTab?.favIconUrl || '')}`,
|
||||
position.left,
|
||||
position.top,
|
||||
);
|
||||
|
||||
const onMessage = async (req: BackgroundAction) => {
|
||||
if (req.type === BackgroundActiontype.get_history_response) {
|
||||
if (req.data) {
|
||||
const response = await getNotaryRequests();
|
||||
|
||||
const result = response
|
||||
.map(
|
||||
({ id, method, url, notaryUrl, websocketProxyUrl, metadata }) => ({
|
||||
id,
|
||||
time: new Date(charwise.decode(id)),
|
||||
method,
|
||||
url,
|
||||
notaryUrl,
|
||||
websocketProxyUrl,
|
||||
metadata,
|
||||
}),
|
||||
)
|
||||
.filter(({ method, url, metadata }) => {
|
||||
let matchedMetadata = true;
|
||||
if (filterMetadata) {
|
||||
matchedMetadata = Object.entries(
|
||||
filterMetadata as { [k: string]: string },
|
||||
).reduce((bool, [k, v]) => {
|
||||
try {
|
||||
return bool && minimatch(metadata![k], v);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}, matchedMetadata);
|
||||
}
|
||||
return (
|
||||
minimatch(method, filterMethod, { nocase: true }) &&
|
||||
minimatch(url, filterUrl) &&
|
||||
matchedMetadata
|
||||
);
|
||||
});
|
||||
|
||||
defer.resolve(result);
|
||||
} else {
|
||||
defer.reject(new Error('user rejected.'));
|
||||
}
|
||||
|
||||
browser.runtime.onMessage.removeListener(onMessage);
|
||||
browser.tabs.remove(tab.id!);
|
||||
}
|
||||
};
|
||||
|
||||
const onPopUpClose = (windowId: number) => {
|
||||
if (windowId === popup.id) {
|
||||
defer.reject(new Error('user rejected.'));
|
||||
browser.windows.onRemoved.removeListener(onPopUpClose);
|
||||
}
|
||||
};
|
||||
|
||||
browser.runtime.onMessage.addListener(onMessage);
|
||||
browser.windows.onRemoved.addListener(onPopUpClose);
|
||||
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
async function handleGetProof(request: BackgroundAction) {
|
||||
const [currentTab] = await browser.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
|
||||
const defer = deferredPromise();
|
||||
const { origin, position, id } = request.data;
|
||||
|
||||
const response = await getNotaryRequest(id);
|
||||
|
||||
if (!response) {
|
||||
defer.reject(new Error('proof id not found.'));
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
const { popup, tab } = await openPopup(
|
||||
`get-proof-approval?id=${id}&origin=${encodeURIComponent(origin)}&favIconUrl=${encodeURIComponent(currentTab?.favIconUrl || '')}`,
|
||||
position.left,
|
||||
position.top,
|
||||
);
|
||||
|
||||
const onMessage = async (req: BackgroundAction) => {
|
||||
if (req.type === BackgroundActiontype.get_proof_response) {
|
||||
if (req.data) {
|
||||
defer.resolve(response?.proof || null);
|
||||
} else {
|
||||
defer.reject(new Error('user rejected.'));
|
||||
}
|
||||
|
||||
browser.runtime.onMessage.removeListener(onMessage);
|
||||
browser.tabs.remove(tab.id!);
|
||||
}
|
||||
};
|
||||
|
||||
const onPopUpClose = (windowId: number) => {
|
||||
if (windowId === popup.id) {
|
||||
defer.reject(new Error('user rejected.'));
|
||||
browser.windows.onRemoved.removeListener(onPopUpClose);
|
||||
}
|
||||
};
|
||||
|
||||
browser.runtime.onMessage.addListener(onMessage);
|
||||
browser.windows.onRemoved.addListener(onPopUpClose);
|
||||
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
async function handleNotarizeRequest(request: BackgroundAction) {
|
||||
const [currentTab] = await browser.tabs.query({
|
||||
active: true,
|
||||
@@ -1226,180 +1059,43 @@ async function handleNotarizeRequest(request: BackgroundAction) {
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
async function handleInstallPluginRequest(request: BackgroundAction) {
|
||||
async function handleRunPluginByURLRequest(request: BackgroundAction) {
|
||||
const [currentTab] = await browser.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
|
||||
const defer = deferredPromise();
|
||||
const { origin, position, url, metadata } = request.data;
|
||||
const { origin, position, url, params } = request.data;
|
||||
|
||||
let arrayBuffer: ArrayBuffer, config: PluginConfig;
|
||||
|
||||
try {
|
||||
const resp = await fetch(url);
|
||||
arrayBuffer = await resp.arrayBuffer();
|
||||
config = await getPluginConfig(arrayBuffer);
|
||||
} catch (e) {
|
||||
defer.reject(e);
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
const { popup, tab } = await openPopup(
|
||||
`install-plugin-approval?${metadata ? `metadata=${JSON.stringify(metadata)}&` : ''}url=${url}&origin=${encodeURIComponent(origin)}&favIconUrl=${encodeURIComponent(currentTab?.favIconUrl || '')}`,
|
||||
position.left,
|
||||
position.top,
|
||||
);
|
||||
|
||||
const onMessage = async (req: BackgroundAction) => {
|
||||
if (req.type === BackgroundActiontype.install_plugin_response) {
|
||||
if (req.data) {
|
||||
try {
|
||||
const hex = Buffer.from(arrayBuffer).toString('hex');
|
||||
const hash = await addPlugin(hex);
|
||||
|
||||
if (!hash) {
|
||||
throw new Error('Plugin already exist.');
|
||||
}
|
||||
|
||||
await addPluginConfig(hash!, config);
|
||||
await addPluginMetadata(hash!, {
|
||||
...metadata,
|
||||
origin,
|
||||
filePath: url,
|
||||
});
|
||||
defer.resolve(hash);
|
||||
} catch (e) {
|
||||
defer.reject(e);
|
||||
}
|
||||
} else {
|
||||
defer.reject(new Error('user rejected.'));
|
||||
}
|
||||
|
||||
browser.runtime.onMessage.removeListener(onMessage);
|
||||
browser.tabs.remove(tab.id!);
|
||||
}
|
||||
};
|
||||
|
||||
const onPopUpClose = (windowId: number) => {
|
||||
if (windowId === popup.id) {
|
||||
defer.reject(new Error('user rejected.'));
|
||||
browser.windows.onRemoved.removeListener(onPopUpClose);
|
||||
}
|
||||
};
|
||||
|
||||
browser.runtime.onMessage.addListener(onMessage);
|
||||
browser.windows.onRemoved.addListener(onPopUpClose);
|
||||
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
async function handleGetPluginsRequest(request: BackgroundAction) {
|
||||
const [currentTab] = await browser.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
|
||||
const defer = deferredPromise();
|
||||
const {
|
||||
origin,
|
||||
position,
|
||||
origin: filterOrigin,
|
||||
url: filterUrl,
|
||||
metadata: filterMetadata,
|
||||
} = request.data;
|
||||
|
||||
const { popup, tab } = await openPopup(
|
||||
`get-plugins-approval?${filterMetadata ? `metadata=${JSON.stringify(filterMetadata)}&` : ''}&filterOrigin=${filterOrigin}&url=${filterUrl}&origin=${encodeURIComponent(origin)}&favIconUrl=${encodeURIComponent(currentTab?.favIconUrl || '')}`,
|
||||
position.left,
|
||||
position.top,
|
||||
);
|
||||
|
||||
const onMessage = async (req: BackgroundAction) => {
|
||||
if (req.type === BackgroundActiontype.get_plugins_response) {
|
||||
if (req.data) {
|
||||
const response = await getPlugins();
|
||||
|
||||
const result = response.filter(({ metadata }) => {
|
||||
let matchedMetadata = true;
|
||||
if (filterMetadata) {
|
||||
matchedMetadata = Object.entries(
|
||||
filterMetadata as { [k: string]: string },
|
||||
).reduce((bool, [k, v]) => {
|
||||
try {
|
||||
return bool && minimatch(metadata![k], v);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}, matchedMetadata);
|
||||
}
|
||||
return (
|
||||
minimatch(metadata.filePath, filterUrl) &&
|
||||
minimatch(metadata.origin, filterOrigin || '**') &&
|
||||
matchedMetadata
|
||||
);
|
||||
});
|
||||
|
||||
defer.resolve(result);
|
||||
} else {
|
||||
defer.reject(new Error('user rejected.'));
|
||||
}
|
||||
|
||||
browser.runtime.onMessage.removeListener(onMessage);
|
||||
browser.tabs.remove(tab.id!);
|
||||
}
|
||||
};
|
||||
|
||||
const onPopUpClose = (windowId: number) => {
|
||||
if (windowId === popup.id) {
|
||||
defer.reject(new Error('user rejected.'));
|
||||
browser.windows.onRemoved.removeListener(onPopUpClose);
|
||||
}
|
||||
};
|
||||
|
||||
browser.runtime.onMessage.addListener(onMessage);
|
||||
browser.windows.onRemoved.addListener(onPopUpClose);
|
||||
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
async function handleRunPluginCSRequest(request: BackgroundAction) {
|
||||
const [currentTab] = await browser.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
|
||||
const defer = deferredPromise();
|
||||
const { origin, position, hash, params } = request.data;
|
||||
|
||||
const plugin = await getPluginByHash(hash);
|
||||
const config = await getPluginConfigByHash(hash);
|
||||
// const plugin = await getPluginByHash(hash);
|
||||
// const config = await getPluginConfigByHash(hash);
|
||||
let isUserClose = true;
|
||||
|
||||
if (!plugin || !config) {
|
||||
defer.reject(new Error('plugin not found.'));
|
||||
return defer.promise;
|
||||
}
|
||||
// if (!plugin || !config) {
|
||||
// defer.reject(new Error('plugin not found.'));
|
||||
// return defer.promise;
|
||||
// }
|
||||
|
||||
const { popup, tab } = await openPopup(
|
||||
`run-plugin-approval?hash=${hash}&origin=${encodeURIComponent(origin)}&favIconUrl=${encodeURIComponent(currentTab?.favIconUrl || '')}¶ms=${encodeURIComponent(JSON.stringify(params) || '')}`,
|
||||
`run-plugin-approval?url=${url}&origin=${encodeURIComponent(origin)}&favIconUrl=${encodeURIComponent(currentTab?.favIconUrl || '')}¶ms=${encodeURIComponent(JSON.stringify(params) || '')}`,
|
||||
position.left,
|
||||
position.top,
|
||||
);
|
||||
|
||||
const onPluginRequest = async (req: any) => {
|
||||
if (req.type !== SidePanelActionTypes.execute_plugin_response) return;
|
||||
if (req.data.hash !== hash) return;
|
||||
|
||||
console.log('onPluginRequest', req.data);
|
||||
if (req.data.url !== url) return;
|
||||
if (req.data.error) defer.reject(req.data.error);
|
||||
if (req.data.proof) defer.resolve(req.data.proof);
|
||||
if (req.data.sessionId) defer.resolve(req.data.sessionId);
|
||||
|
||||
browser.runtime.onMessage.removeListener(onPluginRequest);
|
||||
};
|
||||
|
||||
const onMessage = async (req: BackgroundAction) => {
|
||||
if (req.type === BackgroundActiontype.run_plugin_response) {
|
||||
if (req.type === BackgroundActiontype.run_plugin_by_url_response) {
|
||||
if (req.data) {
|
||||
browser.runtime.onMessage.addListener(onPluginRequest);
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
setPairing,
|
||||
} from '../../reducers/p2p';
|
||||
import { pushToRedux } from '../utils';
|
||||
import { getPluginByHash } from './db';
|
||||
import { getPluginByUrl } from './db';
|
||||
import browser from 'webextension-polyfill';
|
||||
import { OffscreenActionTypes } from '../Offscreen/types';
|
||||
import { getMaxRecv, getMaxSent, getRendezvousApi } from '../../utils/storage';
|
||||
@@ -69,6 +69,7 @@ export const connectSession = async () => {
|
||||
if (state.socket) return;
|
||||
|
||||
const rendezvousAPI = await getRendezvousApi();
|
||||
|
||||
const socket = new WebSocket(rendezvousAPI);
|
||||
|
||||
socket.onopen = () => {
|
||||
@@ -185,7 +186,7 @@ export const connectSession = async () => {
|
||||
}
|
||||
case 'request_proof_by_hash': {
|
||||
const { pluginHash, from } = message.params;
|
||||
const plugin = await getPluginByHash(pluginHash);
|
||||
const plugin = await getPluginByUrl(pluginHash);
|
||||
if (plugin) {
|
||||
state.incomingProofRequests = [
|
||||
...new Set(state.incomingProofRequests.concat(plugin)),
|
||||
@@ -316,9 +317,26 @@ export const connectSession = async () => {
|
||||
break;
|
||||
}
|
||||
};
|
||||
socket.onerror = () => {
|
||||
console.error('Error connecting to websocket');
|
||||
socket.onerror = (error) => {
|
||||
console.error('Error connecting to websocket:', error);
|
||||
pushToRedux(setConnected(false));
|
||||
pushToRedux(
|
||||
setP2PError(
|
||||
'Failed to connect to rendezvous server. Please check your connection and server URL.',
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
socket.onclose = (event) => {
|
||||
console.log('WebSocket connection closed:', event.code, event.reason);
|
||||
pushToRedux(setConnected(false));
|
||||
if (event.code !== 1000 && event.code !== 1001) {
|
||||
pushToRedux(
|
||||
setP2PError(
|
||||
`WebSocket connection lost: ${event.reason || 'Unknown error'}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -336,7 +354,7 @@ async function handleRemoveIncomingProofRequest(message: {
|
||||
params: { pluginHash: string };
|
||||
}) {
|
||||
const { pluginHash } = message.params;
|
||||
const plugin = await getPluginByHash(pluginHash);
|
||||
const plugin = await getPluginByUrl(pluginHash);
|
||||
const incomingProofRequest = [];
|
||||
for (const hex of state.incomingProofRequests) {
|
||||
if (plugin) {
|
||||
@@ -424,7 +442,7 @@ export async function sendPairedMessage(method: string, params?: any) {
|
||||
}
|
||||
|
||||
export const requestProof = async (pluginHash: string) => {
|
||||
const pluginHex = await getPluginByHash(pluginHash);
|
||||
const pluginHex = await getPluginByUrl(pluginHash);
|
||||
sendPairedMessage('request_proof', {
|
||||
plugin: pluginHex,
|
||||
pluginHash,
|
||||
@@ -436,8 +454,8 @@ export const endProofRequest = async (data: {
|
||||
proof: VerifierOutput;
|
||||
}) => {
|
||||
const transcript = new Transcript({
|
||||
sent: data.proof.transcript.sent,
|
||||
recv: data.proof.transcript.recv,
|
||||
sent: data.proof.transcript?.sent || [],
|
||||
recv: data.proof.transcript?.recv || [],
|
||||
});
|
||||
|
||||
state.presentation = {
|
||||
|
||||
@@ -1,40 +1,9 @@
|
||||
import { ContentScriptTypes, RPCClient } from './rpc';
|
||||
import { RequestHistory } from '../Background/rpc';
|
||||
import { PluginConfig, PluginMetadata } from '../../utils/misc';
|
||||
import { PresentationJSON } from '../../utils/types';
|
||||
import { PresentationJSON } from 'tlsn-js/build/types';
|
||||
|
||||
const client = new RPCClient();
|
||||
|
||||
class TLSN {
|
||||
async getHistory(
|
||||
method: string,
|
||||
url: string,
|
||||
metadata?: {
|
||||
[key: string]: string;
|
||||
},
|
||||
): Promise<
|
||||
(Pick<
|
||||
RequestHistory,
|
||||
'id' | 'method' | 'notaryUrl' | 'url' | 'websocketProxyUrl'
|
||||
> & { time: Date })[]
|
||||
> {
|
||||
const resp = await client.call(ContentScriptTypes.get_history, {
|
||||
method,
|
||||
url,
|
||||
metadata,
|
||||
});
|
||||
|
||||
return resp || [];
|
||||
}
|
||||
|
||||
async getProof(id: string): Promise<PresentationJSON | null> {
|
||||
const resp = await client.call(ContentScriptTypes.get_proof, {
|
||||
id,
|
||||
});
|
||||
|
||||
return resp || null;
|
||||
}
|
||||
|
||||
async notarize(
|
||||
url: string,
|
||||
requestOptions?: {
|
||||
@@ -67,37 +36,9 @@ class TLSN {
|
||||
return resp;
|
||||
}
|
||||
|
||||
async installPlugin(
|
||||
url: string,
|
||||
metadata?: { [k: string]: string },
|
||||
): Promise<string> {
|
||||
const resp = await client.call(ContentScriptTypes.install_plugin, {
|
||||
async runPlugin(url: string, params?: Record<string, string>) {
|
||||
const resp = await client.call(ContentScriptTypes.run_plugin_by_url, {
|
||||
url,
|
||||
metadata,
|
||||
});
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
async getPlugins(
|
||||
url: string,
|
||||
origin?: string,
|
||||
metadata?: {
|
||||
[key: string]: string;
|
||||
},
|
||||
): Promise<(PluginConfig & { hash: string; metadata: PluginMetadata })[]> {
|
||||
const resp = await client.call(ContentScriptTypes.get_plugins, {
|
||||
url,
|
||||
origin,
|
||||
metadata,
|
||||
});
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
async runPlugin(hash: string, params?: Record<string, string>) {
|
||||
const resp = await client.call(ContentScriptTypes.run_plugin, {
|
||||
hash,
|
||||
params,
|
||||
});
|
||||
|
||||
@@ -106,11 +47,7 @@ class TLSN {
|
||||
}
|
||||
|
||||
const connect = async () => {
|
||||
const resp = await client.call(ContentScriptTypes.connect);
|
||||
|
||||
if (resp) {
|
||||
return new TLSN();
|
||||
}
|
||||
return new TLSN();
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
@@ -25,70 +25,6 @@ import { urlify } from '../../utils/misc';
|
||||
}
|
||||
});
|
||||
|
||||
server.on(ContentScriptTypes.connect, async () => {
|
||||
const connected = await browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.connect_request,
|
||||
data: {
|
||||
...getPopupData(),
|
||||
},
|
||||
});
|
||||
|
||||
if (!connected) throw new Error('user rejected.');
|
||||
|
||||
return connected;
|
||||
});
|
||||
|
||||
server.on(
|
||||
ContentScriptTypes.get_history,
|
||||
async (
|
||||
request: ContentScriptRequest<{
|
||||
method: string;
|
||||
url: string;
|
||||
metadata?: { [k: string]: string };
|
||||
}>,
|
||||
) => {
|
||||
const {
|
||||
method: filterMethod,
|
||||
url: filterUrl,
|
||||
metadata,
|
||||
} = request.params || {};
|
||||
|
||||
if (!filterMethod || !filterUrl)
|
||||
throw new Error('params must include method and url.');
|
||||
|
||||
const response: RequestHistory[] = await browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.get_history_request,
|
||||
data: {
|
||||
...getPopupData(),
|
||||
method: filterMethod,
|
||||
url: filterUrl,
|
||||
metadata,
|
||||
},
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
);
|
||||
|
||||
server.on(
|
||||
ContentScriptTypes.get_proof,
|
||||
async (request: ContentScriptRequest<{ id: string }>) => {
|
||||
const { id } = request.params || {};
|
||||
|
||||
if (!id) throw new Error('params must include id.');
|
||||
|
||||
const proof = await browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.get_proof_request,
|
||||
data: {
|
||||
...getPopupData(),
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
return proof;
|
||||
},
|
||||
);
|
||||
|
||||
server.on(
|
||||
ContentScriptTypes.notarize,
|
||||
async (
|
||||
@@ -139,78 +75,22 @@ import { urlify } from '../../utils/misc';
|
||||
);
|
||||
|
||||
server.on(
|
||||
ContentScriptTypes.install_plugin,
|
||||
ContentScriptTypes.run_plugin_by_url,
|
||||
async (
|
||||
request: ContentScriptRequest<{
|
||||
url: string;
|
||||
metadata?: { [k: string]: string };
|
||||
}>,
|
||||
) => {
|
||||
const { url, metadata } = request.params || {};
|
||||
|
||||
if (!url) throw new Error('params must include url.');
|
||||
|
||||
const response: RequestHistory[] = await browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.install_plugin_request,
|
||||
data: {
|
||||
...getPopupData(),
|
||||
url,
|
||||
metadata,
|
||||
},
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
);
|
||||
|
||||
server.on(
|
||||
ContentScriptTypes.get_plugins,
|
||||
async (
|
||||
request: ContentScriptRequest<{
|
||||
url: string;
|
||||
origin?: string;
|
||||
metadata?: { [k: string]: string };
|
||||
}>,
|
||||
) => {
|
||||
const {
|
||||
url: filterUrl,
|
||||
origin: filterOrigin,
|
||||
metadata,
|
||||
} = request.params || {};
|
||||
|
||||
if (!filterUrl) throw new Error('params must include url.');
|
||||
|
||||
const response = await browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.get_plugins_request,
|
||||
data: {
|
||||
...getPopupData(),
|
||||
url: filterUrl,
|
||||
origin: filterOrigin,
|
||||
metadata,
|
||||
},
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
);
|
||||
|
||||
server.on(
|
||||
ContentScriptTypes.run_plugin,
|
||||
async (
|
||||
request: ContentScriptRequest<{
|
||||
hash: string;
|
||||
params?: Record<string, string>;
|
||||
}>,
|
||||
) => {
|
||||
const { hash, params } = request.params || {};
|
||||
const { url, params } = request.params || {};
|
||||
|
||||
if (!hash) throw new Error('params must include hash');
|
||||
if (!url) throw new Error('params must include url');
|
||||
|
||||
const response = await browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.run_plugin_request,
|
||||
type: BackgroundActiontype.run_plugin_by_url_request,
|
||||
data: {
|
||||
...getPopupData(),
|
||||
hash,
|
||||
url,
|
||||
params,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import { deferredPromise, PromiseResolvers } from '../../utils/promise';
|
||||
|
||||
export enum ContentScriptTypes {
|
||||
connect = 'tlsn/cs/connect',
|
||||
get_history = 'tlsn/cs/get_history',
|
||||
get_proof = 'tlsn/cs/get_proof',
|
||||
notarize = 'tlsn/cs/notarize',
|
||||
install_plugin = 'tlsn/cs/install_plugin',
|
||||
get_plugins = 'tlsn/cs/get_plugins',
|
||||
run_plugin = 'tlsn/cs/run_plugin',
|
||||
run_plugin_by_url = 'tlsn/cs/run_plugin_by_url',
|
||||
}
|
||||
|
||||
export type ContentScriptRequest<params> = {
|
||||
|
||||
@@ -16,10 +16,8 @@ import {
|
||||
} from 'tlsn-js';
|
||||
import { convertNotaryWsToHttp, devlog, urlify } from '../../utils/misc';
|
||||
import * as Comlink from 'comlink';
|
||||
import { PresentationJSON as PresentationJSONa7 } from 'tlsn-js/build/types';
|
||||
import { OffscreenActionTypes } from './types';
|
||||
import { PresentationJSON } from '../../utils/types';
|
||||
import { verify } from 'tlsn-js-v5';
|
||||
import { PresentationJSON } from 'tlsn-js/build/types';
|
||||
import { waitForEvent } from '../utils';
|
||||
import {
|
||||
setNotaryRequestError,
|
||||
@@ -37,7 +35,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;
|
||||
@@ -123,7 +124,7 @@ export const onCreatePresentationRequest = async (request: any) => {
|
||||
secretsHex: notarizationOutputs.secrets,
|
||||
notaryUrl: notarizationOutputs.notaryUrl,
|
||||
websocketProxyUrl: notarizationOutputs.websocketProxyUrl,
|
||||
reveal: commit,
|
||||
reveal: { ...commit, server_identity: false },
|
||||
})) as TPresentation;
|
||||
const json = await presentation.json();
|
||||
browser.runtime.sendMessage({
|
||||
@@ -132,11 +133,6 @@ export const onCreatePresentationRequest = async (request: any) => {
|
||||
id,
|
||||
proof: {
|
||||
...json,
|
||||
meta: {
|
||||
...json.meta,
|
||||
notaryUrl,
|
||||
websocketProxyUrl,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -249,7 +245,8 @@ export const startP2PVerifier = async (request: any) => {
|
||||
|
||||
export const startP2PProver = async (request: any) => {
|
||||
const {
|
||||
pluginHash,
|
||||
id,
|
||||
pluginUrl,
|
||||
pluginHex,
|
||||
url,
|
||||
method,
|
||||
@@ -261,52 +258,56 @@ export const startP2PProver = async (request: any) => {
|
||||
maxSentData,
|
||||
secretHeaders,
|
||||
getSecretResponse,
|
||||
verifierPlugin,
|
||||
} = request.data;
|
||||
|
||||
const hostname = urlify(url)?.hostname || '';
|
||||
|
||||
updateRequestProgress(id, RequestProgress.CreatingProver);
|
||||
const prover: TProver = await new Prover({
|
||||
id: pluginHash,
|
||||
id,
|
||||
serverDns: hostname,
|
||||
maxSentData,
|
||||
maxRecvData,
|
||||
serverIdentity: true,
|
||||
});
|
||||
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.prover_instantiated,
|
||||
data: {
|
||||
pluginHash,
|
||||
updateRequestProgress(id, RequestProgress.GettingSession);
|
||||
const resp = await fetch(`${proverUrl}/session`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
clientType: 'Websocket',
|
||||
maxRecvData,
|
||||
maxSentData,
|
||||
plugin: 'plugin-js',
|
||||
}),
|
||||
});
|
||||
const { sessionId } = await resp.json();
|
||||
const _url = new URL(proverUrl);
|
||||
const protocol = _url.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const pathname = _url.pathname;
|
||||
const sessionUrl = `${protocol}://${_url.host}${pathname === '/' ? '' : pathname}/notarize?sessionId=${sessionId!}`;
|
||||
|
||||
const proofRequestStart = waitForEvent(
|
||||
OffscreenActionTypes.start_p2p_proof_request,
|
||||
updateRequestProgress(id, RequestProgress.SettingUpProver);
|
||||
await prover.setup(sessionUrl);
|
||||
|
||||
await handleProgress(
|
||||
id,
|
||||
RequestProgress.SendingRequest,
|
||||
() =>
|
||||
prover.sendRequest(websocketProxyUrl + `?token=${hostname}`, {
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
}),
|
||||
`Error connecting to websocket proxy: ${websocketProxyUrl}. Please check the proxy URL and ensure it's accessible.`,
|
||||
);
|
||||
|
||||
const proverSetup = prover.setup(proverUrl);
|
||||
await new Promise((r) => setTimeout(r, 5000));
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.prover_setup,
|
||||
data: {
|
||||
pluginHash,
|
||||
},
|
||||
});
|
||||
|
||||
await proverSetup;
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.prover_started,
|
||||
data: {
|
||||
pluginHash,
|
||||
},
|
||||
});
|
||||
await proofRequestStart;
|
||||
await prover.sendRequest(websocketProxyUrl + `?token=${hostname}`, {
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
|
||||
updateRequestProgress(id, RequestProgress.ReadingTranscript);
|
||||
const transcript = await prover.transcript();
|
||||
|
||||
let secretResps: string[] = [];
|
||||
@@ -315,7 +316,7 @@ export const startP2PProver = async (request: any) => {
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.get_secrets_from_transcript,
|
||||
data: {
|
||||
pluginHash,
|
||||
pluginUrl,
|
||||
pluginHex,
|
||||
method: getSecretResponse,
|
||||
transcript,
|
||||
@@ -347,9 +348,15 @@ export const startP2PProver = async (request: any) => {
|
||||
),
|
||||
};
|
||||
|
||||
const endRequest = waitForEvent(OffscreenActionTypes.end_p2p_proof_request);
|
||||
await prover.reveal(commit);
|
||||
await endRequest;
|
||||
await prover.reveal({ ...commit, server_identity: true });
|
||||
updateRequestProgress(id, RequestProgress.FinalizingOutputs);
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.finish_prove_request,
|
||||
data: {
|
||||
id,
|
||||
sessionId: sessionId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
async function createProof(options: {
|
||||
@@ -366,7 +373,7 @@ async function createProof(options: {
|
||||
id: string;
|
||||
secretHeaders: string[];
|
||||
secretResps: string[];
|
||||
}): Promise<PresentationJSONa7> {
|
||||
}): Promise<PresentationJSON> {
|
||||
const {
|
||||
url,
|
||||
method = 'GET',
|
||||
@@ -398,13 +405,18 @@ async function createProof(options: {
|
||||
updateRequestProgress(id, RequestProgress.SettingUpProver);
|
||||
await prover.setup(sessionUrl);
|
||||
|
||||
updateRequestProgress(id, RequestProgress.SendingRequest);
|
||||
await prover.sendRequest(websocketProxyUrl + `?token=${hostname}`, {
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
await handleProgress(
|
||||
id,
|
||||
RequestProgress.SendingRequest,
|
||||
() =>
|
||||
prover.sendRequest(websocketProxyUrl + `?token=${hostname}`, {
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
}),
|
||||
`Error connecting to websocket proxy: ${websocketProxyUrl}. Please check the proxy URL and ensure it's accessible.`,
|
||||
);
|
||||
|
||||
updateRequestProgress(id, RequestProgress.ReadingTranscript);
|
||||
const transcript = await prover.transcript();
|
||||
@@ -434,17 +446,12 @@ async function createProof(options: {
|
||||
secretsHex: notarizationOutputs.secrets,
|
||||
notaryUrl: notarizationOutputs.notaryUrl,
|
||||
websocketProxyUrl: notarizationOutputs.websocketProxyUrl,
|
||||
reveal: commit,
|
||||
reveal: { ...commit, server_identity: false },
|
||||
})) as TPresentation;
|
||||
|
||||
const json = await presentation.json();
|
||||
return {
|
||||
...json,
|
||||
meta: {
|
||||
...json,
|
||||
notaryUrl: notaryUrl,
|
||||
websocketProxyUrl: websocketProxyUrl,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -513,7 +520,7 @@ async function createProver(options: {
|
||||
headers,
|
||||
body,
|
||||
}),
|
||||
'Error sending request',
|
||||
`Error connecting to websocket proxy: ${websocketProxyUrl}. Please check the proxy URL and ensure it's accessible.`,
|
||||
);
|
||||
|
||||
return prover;
|
||||
@@ -536,36 +543,44 @@ async function verifyProof(proof: PresentationJSON): Promise<{
|
||||
};
|
||||
|
||||
switch (proof.version) {
|
||||
case undefined: {
|
||||
case '0.1.0-alpha.12':
|
||||
result = await verify(proof);
|
||||
break;
|
||||
}
|
||||
case '0.1.0-alpha.7':
|
||||
case '0.1.0-alpha.8':
|
||||
case '0.1.0-alpha.9':
|
||||
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(() => '');
|
||||
default:
|
||||
result = {
|
||||
sent: transcript.sent(),
|
||||
recv: transcript.recv(),
|
||||
verifierKey: verifyingKey,
|
||||
notaryKey: publicKey,
|
||||
sent: 'version not supported',
|
||||
recv: 'version not supported',
|
||||
};
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
|
||||
return result!;
|
||||
}
|
||||
|
||||
async function verify(proof: PresentationJSON) {
|
||||
if (proof.version !== '0.1.0-alpha.12') {
|
||||
throw new Error('wrong version');
|
||||
}
|
||||
const presentation: TPresentation = await new Presentation(proof.data);
|
||||
const verifierOutput = await presentation.verify();
|
||||
const transcript = new Transcript({
|
||||
sent: verifierOutput.transcript?.sent || [],
|
||||
recv: verifierOutput.transcript?.recv || [],
|
||||
});
|
||||
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(
|
||||
@@ -589,6 +604,75 @@ function updateRequestProgress(
|
||||
});
|
||||
}
|
||||
|
||||
function getWebsocketErrorMessage(
|
||||
lowerError: string,
|
||||
fallbackMessage: string,
|
||||
): string {
|
||||
const isWebsocketError =
|
||||
lowerError.includes('websocket') ||
|
||||
lowerError.includes('proxy') ||
|
||||
lowerError.includes('connection') ||
|
||||
lowerError.includes('network') ||
|
||||
lowerError.includes('prover error') ||
|
||||
lowerError.includes('io error') ||
|
||||
lowerError.includes('certificate') ||
|
||||
lowerError.includes('cert') ||
|
||||
lowerError.includes('ssl') ||
|
||||
lowerError.includes('tls');
|
||||
|
||||
if (!isWebsocketError) {
|
||||
return fallbackMessage;
|
||||
}
|
||||
|
||||
const errorPatterns = [
|
||||
{
|
||||
patterns: ['protocol', 'must use ws://', 'must use wss://'],
|
||||
message:
|
||||
'Invalid websocket proxy URL protocol. Please use ws:// or wss:// protocol in your websocket proxy URL settings.',
|
||||
},
|
||||
{
|
||||
patterns: [
|
||||
'not allowed',
|
||||
'not whitelisted',
|
||||
'forbidden',
|
||||
'unauthorized',
|
||||
'permission denied',
|
||||
'access denied',
|
||||
],
|
||||
message:
|
||||
'Target domain not allowed by websocket proxy. Please check if the website domain is supported by your proxy service.',
|
||||
},
|
||||
{
|
||||
patterns: ['dns', 'resolve'],
|
||||
message:
|
||||
'Cannot resolve websocket proxy domain. Please check your websocket proxy URL in settings.',
|
||||
},
|
||||
{
|
||||
patterns: ['timeout'],
|
||||
message:
|
||||
'Websocket proxy connection timeout. Please check your websocket proxy URL in settings and ensure the server is accessible.',
|
||||
},
|
||||
{
|
||||
patterns: ['refused', 'unreachable'],
|
||||
message:
|
||||
'Cannot reach websocket proxy server. Please check your websocket proxy URL in settings and ensure the server is accessible.',
|
||||
},
|
||||
{
|
||||
patterns: ['cert', 'certificate', 'certnotvalidforname'],
|
||||
message:
|
||||
'Cannot connect to websocket proxy server. Please check your websocket proxy URL in settings and ensure it points to a valid websocket proxy service.',
|
||||
},
|
||||
];
|
||||
|
||||
for (const { patterns, message } of errorPatterns) {
|
||||
if (patterns.some((pattern) => lowerError.includes(pattern))) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
return 'Websocket proxy connection failed. Please check your websocket proxy URL in settings and ensure the server is accessible.';
|
||||
}
|
||||
|
||||
async function handleProgress<T>(
|
||||
id: string,
|
||||
progress: RequestProgress,
|
||||
@@ -599,12 +683,17 @@ async function handleProgress<T>(
|
||||
updateRequestProgress(id, progress);
|
||||
return await action();
|
||||
} catch (error: any) {
|
||||
updateRequestProgress(id, RequestProgress.Error, errorMessage);
|
||||
await setNotaryRequestStatus(id, 'error');
|
||||
await setNotaryRequestError(
|
||||
id,
|
||||
errorMessage || error.message || 'Unknown error',
|
||||
const specificError = error?.message || '';
|
||||
const lowerError = specificError.toLowerCase();
|
||||
|
||||
const finalErrorMessage = getWebsocketErrorMessage(
|
||||
lowerError,
|
||||
errorMessage,
|
||||
);
|
||||
|
||||
updateRequestProgress(id, RequestProgress.Error, finalErrorMessage);
|
||||
await setNotaryRequestStatus(id, 'error');
|
||||
await setNotaryRequestError(id, finalErrorMessage);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Navigate, Route, Routes, useNavigate } from 'react-router';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import {
|
||||
setActiveTab,
|
||||
setRequests,
|
||||
useActiveTab,
|
||||
useActiveTabUrl,
|
||||
} from '../../reducers/requests';
|
||||
import { setActiveTab, setRequests } from '../../reducers/requests';
|
||||
import { BackgroundActiontype } from '../Background/rpc';
|
||||
import Requests from '../../pages/Requests';
|
||||
import Options from '../../pages/Options';
|
||||
import Request from '../../pages/Requests/Request';
|
||||
import Home from '../../pages/Home';
|
||||
@@ -16,28 +10,15 @@ import logo from '../../assets/img/icon-128.png';
|
||||
import RequestBuilder from '../../pages/RequestBuilder';
|
||||
import Notarize from '../../pages/Notarize';
|
||||
import ProofViewer from '../../pages/ProofViewer';
|
||||
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';
|
||||
import { GetHistoryApproval } from '../../pages/GetHistoryApproval';
|
||||
import { GetProofApproval } from '../../pages/GetProofApproval';
|
||||
import { NotarizeApproval } from '../../pages/NotarizeApproval';
|
||||
import { InstallPluginApproval } from '../../pages/InstallPluginApproval';
|
||||
import { GetPluginsApproval } from '../../pages/GetPluginsApproval';
|
||||
import { RunPluginApproval } from '../../pages/RunPluginApproval';
|
||||
import Icon from '../../components/Icon';
|
||||
import classNames from 'classnames';
|
||||
import { getConnection } from '../Background/db';
|
||||
import { useIsConnected, setConnection } from '../../reducers/requests';
|
||||
import { MenuIcon } from '../../components/Menu';
|
||||
import Plugins from '../../pages/Plugins';
|
||||
import { P2PHome } from '../../pages/PeerToPeer';
|
||||
import { fetchP2PState } from '../../reducers/p2p';
|
||||
import { RunPluginByUrlApproval } from '../../pages/RunPluginByUrlApproval';
|
||||
|
||||
const Popup = () => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -104,7 +85,6 @@ const Popup = () => {
|
||||
<div className="flex flex-row flex-grow items-center justify-end gap-4">
|
||||
{!isPopup && (
|
||||
<>
|
||||
<AppConnectionLogo />
|
||||
<MenuIcon />
|
||||
</>
|
||||
)}
|
||||
@@ -119,20 +99,13 @@ const Popup = () => {
|
||||
<Route path="/requests" element={<Home tab="network" />} />
|
||||
<Route path="/custom/*" element={<RequestBuilder />} />
|
||||
<Route path="/options" element={<Options />} />
|
||||
<Route path="/plugins" element={<Plugins />} />
|
||||
<Route path="/home" element={<Home />} />
|
||||
<Route path="/plugininfo" element={<PluginUploadInfo />} />
|
||||
<Route path="/connection-approval" element={<ConnectionApproval />} />
|
||||
<Route path="/get-history-approval" element={<GetHistoryApproval />} />
|
||||
<Route path="/get-proof-approval" element={<GetProofApproval />} />
|
||||
<Route path="/notarize-approval" element={<NotarizeApproval />} />
|
||||
<Route path="/get-plugins-approval" element={<GetPluginsApproval />} />
|
||||
<Route path="/run-plugin-approval" element={<RunPluginApproval />} />
|
||||
<Route path="/p2p" element={<P2PHome />} />
|
||||
<Route
|
||||
path="/install-plugin-approval"
|
||||
element={<InstallPluginApproval />}
|
||||
path="/run-plugin-approval"
|
||||
element={<RunPluginByUrlApproval />}
|
||||
/>
|
||||
<Route path="/p2p" element={<P2PHome />} />
|
||||
<Route path="*" element={<Navigate to="/home" />} />
|
||||
</Routes>
|
||||
</div>
|
||||
@@ -140,58 +113,3 @@ const Popup = () => {
|
||||
};
|
||||
|
||||
export default Popup;
|
||||
|
||||
function AppConnectionLogo() {
|
||||
const dispatch = useDispatch();
|
||||
const activeTab = useActiveTab();
|
||||
const url = useActiveTabUrl();
|
||||
const [showConnectionDetails, setShowConnectionDetails] = useState(false);
|
||||
const connected = useIsConnected();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (url) {
|
||||
const isConnected: boolean | null = await getConnection(url?.origin);
|
||||
dispatch(setConnection(!!isConnected));
|
||||
}
|
||||
})();
|
||||
}, [url]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-nowrap flex-row items-center gap-1 justify-center w-fit cursor-pointer"
|
||||
onClick={() => setShowConnectionDetails(true)}
|
||||
>
|
||||
<div className="flex flex-row relative bg-black border-[1px] border-black rounded-full">
|
||||
{!!activeTab?.favIconUrl ? (
|
||||
<img
|
||||
src={activeTab?.favIconUrl}
|
||||
className="h-5 rounded-full"
|
||||
alt="logo"
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
fa="fa-solid fa-globe"
|
||||
className="bg-white text-slate-400 rounded-full"
|
||||
size={1.25}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute right-[-2px] bottom-[-2px] rounded-full h-[10px] w-[10px] border-[2px]',
|
||||
{
|
||||
'bg-green-500': connected,
|
||||
'bg-slate-500': !connected,
|
||||
},
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{showConnectionDetails && (
|
||||
<ConnectionDetailsModal
|
||||
showConnectionDetails={showConnectionDetails}
|
||||
setShowConnectionDetails={setShowConnectionDetails}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
makePlugin,
|
||||
PluginConfig,
|
||||
StepConfig,
|
||||
InputFieldConfig,
|
||||
} from '../../utils/misc';
|
||||
import DefaultPluginIcon from '../../assets/img/default-plugin-icon.png';
|
||||
import logo from '../../assets/img/icon-128.png';
|
||||
@@ -18,13 +19,13 @@ import {
|
||||
progressText,
|
||||
RequestProgress,
|
||||
} from '../Background/rpc';
|
||||
import { getPluginByHash, getPluginConfigByHash } from '../Background/db';
|
||||
import { getPluginByUrl, getPluginConfigByUrl } from '../Background/db';
|
||||
import { SidePanelActionTypes } from './types';
|
||||
import { fetchP2PState, useClientId } from '../../reducers/p2p';
|
||||
|
||||
export default function SidePanel(): ReactElement {
|
||||
const [config, setConfig] = useState<PluginConfig | null>(null);
|
||||
const [hash, setHash] = useState('');
|
||||
const [url, setUrl] = useState('');
|
||||
const [hex, setHex] = useState('');
|
||||
const [p2p, setP2P] = useState(false);
|
||||
const [params, setParams] = useState<Record<string, string> | undefined>();
|
||||
@@ -44,8 +45,9 @@ export default function SidePanel(): ReactElement {
|
||||
|
||||
switch (type) {
|
||||
case SidePanelActionTypes.execute_plugin_request: {
|
||||
setConfig(await getPluginConfigByHash(data.pluginHash));
|
||||
setHash(data.pluginHash);
|
||||
const pluginIdentifier = data.pluginUrl || data.pluginHash;
|
||||
setConfig(await getPluginConfigByUrl(pluginIdentifier));
|
||||
setUrl(pluginIdentifier);
|
||||
setParams(data.pluginParams);
|
||||
setStarted(true);
|
||||
break;
|
||||
@@ -53,10 +55,10 @@ export default function SidePanel(): ReactElement {
|
||||
case SidePanelActionTypes.run_p2p_plugin_request: {
|
||||
const { pluginHash, plugin } = data;
|
||||
const config =
|
||||
(await getPluginConfigByHash(pluginHash)) ||
|
||||
(await getPluginConfigByUrl(pluginHash)) ||
|
||||
(await getPluginConfig(hexToArrayBuffer(plugin)));
|
||||
|
||||
setHash(pluginHash);
|
||||
setUrl(pluginHash);
|
||||
setHex(plugin);
|
||||
setP2P(true);
|
||||
setConfig(config);
|
||||
@@ -71,7 +73,7 @@ export default function SidePanel(): ReactElement {
|
||||
}
|
||||
case SidePanelActionTypes.reset_panel: {
|
||||
setConfig(null);
|
||||
setHash('');
|
||||
setUrl('');
|
||||
setHex('');
|
||||
setStarted(false);
|
||||
break;
|
||||
@@ -92,9 +94,10 @@ export default function SidePanel(): ReactElement {
|
||||
</button>
|
||||
</div>
|
||||
{/*{!config && <PluginList />}*/}
|
||||
|
||||
{started && config && (
|
||||
<PluginBody
|
||||
hash={hash}
|
||||
url={url}
|
||||
hex={hex}
|
||||
config={config}
|
||||
p2p={p2p}
|
||||
@@ -106,15 +109,21 @@ export default function SidePanel(): ReactElement {
|
||||
);
|
||||
}
|
||||
|
||||
function PluginBody(props: {
|
||||
function PluginBody({
|
||||
url,
|
||||
hex,
|
||||
config,
|
||||
p2p,
|
||||
clientId,
|
||||
presetParameterValues,
|
||||
}: {
|
||||
config: PluginConfig;
|
||||
hash: string;
|
||||
url: string;
|
||||
hex?: string;
|
||||
clientId?: string;
|
||||
p2p?: boolean;
|
||||
presetParameterValues?: Record<string, string>;
|
||||
}): ReactElement {
|
||||
const { hash, hex, config, p2p, clientId, presetParameterValues } = props;
|
||||
const { title, description, icon, steps } = config;
|
||||
const [responses, setResponses] = useState<any[]>([]);
|
||||
const [notarizationId, setNotarizationId] = useState('');
|
||||
@@ -129,7 +138,7 @@ function PluginBody(props: {
|
||||
setNotarizationId(response);
|
||||
}
|
||||
},
|
||||
[hash, responses],
|
||||
[url, responses],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -137,20 +146,31 @@ function PluginBody(props: {
|
||||
browser.runtime.sendMessage({
|
||||
type: SidePanelActionTypes.execute_plugin_response,
|
||||
data: {
|
||||
hash,
|
||||
url,
|
||||
proof: notaryRequest.proof,
|
||||
},
|
||||
});
|
||||
} else if (notaryRequest?.sessionId) {
|
||||
browser.runtime.sendMessage({
|
||||
type: SidePanelActionTypes.execute_plugin_response,
|
||||
data: {
|
||||
url,
|
||||
sessionId: notaryRequest.sessionId,
|
||||
},
|
||||
});
|
||||
} else if (notaryRequest?.status === 'error') {
|
||||
browser.runtime.sendMessage({
|
||||
type: SidePanelActionTypes.execute_plugin_response,
|
||||
data: {
|
||||
hash,
|
||||
error: notaryRequest.error,
|
||||
url,
|
||||
error:
|
||||
notaryRequest.errorMessage ||
|
||||
notaryRequest.error ||
|
||||
'Notarization failed',
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [hash, notaryRequest?.status]);
|
||||
}, [url, notaryRequest?.status, notaryRequest?.sessionId]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col p-4">
|
||||
@@ -167,7 +187,7 @@ function PluginBody(props: {
|
||||
{steps?.map((step, i) => (
|
||||
<StepContent
|
||||
key={i}
|
||||
hash={hash}
|
||||
url={url}
|
||||
config={config}
|
||||
hex={hex}
|
||||
index={i}
|
||||
@@ -187,7 +207,7 @@ function PluginBody(props: {
|
||||
|
||||
function StepContent(
|
||||
props: StepConfig & {
|
||||
hash: string;
|
||||
url: string;
|
||||
hex?: string;
|
||||
clientId?: string;
|
||||
index: number;
|
||||
@@ -208,42 +228,70 @@ function StepContent(
|
||||
setResponse,
|
||||
lastResponse,
|
||||
prover,
|
||||
hash,
|
||||
url,
|
||||
hex: _hex,
|
||||
config,
|
||||
p2p = false,
|
||||
clientId = '',
|
||||
parameterValues,
|
||||
inputs,
|
||||
} = props;
|
||||
const [completed, setCompleted] = useState(false);
|
||||
const [pending, setPending] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [notarizationId, setNotarizationId] = useState('');
|
||||
const [inputValues, setInputValues] = useState<Record<string, string>>({});
|
||||
const notaryRequest = useRequestHistory(notarizationId);
|
||||
|
||||
useEffect(() => {
|
||||
if (inputs) {
|
||||
const initialValues: Record<string, string> = {};
|
||||
inputs.forEach((input) => {
|
||||
if (input.defaultValue) {
|
||||
initialValues[input.name] = input.defaultValue;
|
||||
}
|
||||
});
|
||||
setInputValues(initialValues);
|
||||
}
|
||||
}, [inputs]);
|
||||
|
||||
const getPlugin = useCallback(async () => {
|
||||
const hex = (await getPluginByHash(hash)) || _hex;
|
||||
const hex = (await getPluginByUrl(url)) || _hex;
|
||||
const arrayBuffer = hexToArrayBuffer(hex!);
|
||||
return makePlugin(arrayBuffer, config, { p2p, clientId });
|
||||
}, [hash, _hex, config, p2p, clientId]);
|
||||
}, [url, _hex, config, p2p, clientId]);
|
||||
|
||||
const processStep = useCallback(async () => {
|
||||
const plugin = await getPlugin();
|
||||
if (!plugin) return;
|
||||
if (index > 0 && !lastResponse) return;
|
||||
|
||||
// Validate required input fields
|
||||
if (inputs) {
|
||||
for (const input of inputs) {
|
||||
if (
|
||||
input.required &&
|
||||
(!inputValues[input.name] || inputValues[input.name].trim() === '')
|
||||
) {
|
||||
setError(`${input.label} is required`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setPending(true);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const out = await plugin.call(
|
||||
action,
|
||||
index > 0
|
||||
? JSON.stringify(lastResponse)
|
||||
: JSON.stringify(parameterValues),
|
||||
);
|
||||
console.log(out);
|
||||
const val = JSON.parse(out.string());
|
||||
let stepData: any;
|
||||
if (index > 0) {
|
||||
stepData = lastResponse;
|
||||
} else {
|
||||
stepData = { ...parameterValues, ...inputValues };
|
||||
}
|
||||
|
||||
const out = await plugin.call(action, JSON.stringify(stepData));
|
||||
const val = JSON.parse(out!.string());
|
||||
if (val && prover) {
|
||||
setNotarizationId(val);
|
||||
} else {
|
||||
@@ -252,11 +300,20 @@ function StepContent(
|
||||
setResponse(val, index);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
setError(e?.message || 'Unkonwn error');
|
||||
setError(e?.message || 'Unknown error');
|
||||
} finally {
|
||||
setPending(false);
|
||||
}
|
||||
}, [action, index, lastResponse, prover, getPlugin]);
|
||||
}, [
|
||||
action,
|
||||
index,
|
||||
lastResponse,
|
||||
prover,
|
||||
getPlugin,
|
||||
inputs,
|
||||
inputValues,
|
||||
parameterValues,
|
||||
]);
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
if (
|
||||
@@ -301,11 +358,16 @@ function StepContent(
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
processStep();
|
||||
}, [processStep]);
|
||||
// only auto-progress if this step does need inputs
|
||||
if (!inputs || inputs.length === 0) {
|
||||
processStep();
|
||||
}
|
||||
}, [processStep, inputs]);
|
||||
|
||||
let btnContent = null;
|
||||
|
||||
console.log('notaryRequest', notaryRequest);
|
||||
console.log('notarizationId', notarizationId);
|
||||
if (prover && p2p) {
|
||||
btnContent = (
|
||||
<button
|
||||
@@ -317,7 +379,7 @@ function StepContent(
|
||||
<span className="text-sm">View in P2P</span>
|
||||
</button>
|
||||
);
|
||||
} else if (completed) {
|
||||
} else if (completed || notaryRequest?.sessionId) {
|
||||
btnContent = (
|
||||
<button
|
||||
className={classNames(
|
||||
@@ -344,12 +406,18 @@ function StepContent(
|
||||
btnContent = (
|
||||
<div className="flex flex-col gap-2">
|
||||
{notaryRequest?.progress === RequestProgress.Error && (
|
||||
<div className="flex flex-row items-center gap-2 text-red-600">
|
||||
<Icon fa="fa-solid fa-triangle-exclamation" size={1} />
|
||||
<span className="text-sm">
|
||||
{notaryRequest?.errorMessage ||
|
||||
progressText(notaryRequest.progress)}
|
||||
</span>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex flex-row items-start gap-2 text-red-600">
|
||||
<Icon
|
||||
fa="fa-solid fa-triangle-exclamation"
|
||||
size={1}
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<span className="text-sm">
|
||||
{notaryRequest?.errorMessage ||
|
||||
progressText(notaryRequest.progress)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{notaryRequest?.progress !== RequestProgress.Error && (
|
||||
@@ -392,9 +460,117 @@ function StepContent(
|
||||
{!!description && (
|
||||
<div className="text-slate-500 text-sm">{description}</div>
|
||||
)}
|
||||
{!!error && <div className="text-red-500 text-sm">{error}</div>}
|
||||
{!!error && (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex flex-row items-start gap-2 text-red-600">
|
||||
<Icon
|
||||
fa="fa-solid fa-triangle-exclamation"
|
||||
size={1}
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<div className="text-red-500 text-sm">{error}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{inputs && inputs.length > 0 && !completed && (
|
||||
<div className="flex flex-col gap-3 mt-3">
|
||||
{inputs.map((input) => (
|
||||
<InputField
|
||||
key={input.name}
|
||||
config={input}
|
||||
value={inputValues[input.name] || ''}
|
||||
onChange={(value) =>
|
||||
setInputValues((prev) => ({ ...prev, [input.name]: value }))
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{btnContent}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface InputFieldProps {
|
||||
config: InputFieldConfig;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
function InputField({
|
||||
config,
|
||||
value,
|
||||
onChange,
|
||||
disabled = false,
|
||||
}: InputFieldProps): ReactElement {
|
||||
const { name, label, type, placeholder, required, options } = config;
|
||||
|
||||
const baseClasses =
|
||||
'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent';
|
||||
|
||||
const renderInput = () => {
|
||||
switch (type) {
|
||||
case 'textarea':
|
||||
return (
|
||||
<textarea
|
||||
id={name}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
required={required}
|
||||
disabled={disabled}
|
||||
className={classNames(baseClasses, 'resize-y min-h-[80px]')}
|
||||
rows={3}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'select':
|
||||
return (
|
||||
<select
|
||||
id={name}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
required={required}
|
||||
disabled={disabled}
|
||||
className={baseClasses}
|
||||
>
|
||||
<option value="">{placeholder || 'Select an option'}</option>
|
||||
{options?.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
id={name}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
required={required}
|
||||
disabled={disabled}
|
||||
className={baseClasses}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor={name} className="text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
{required && <span className="text-red-500 ml-1">*</span>}
|
||||
</label>
|
||||
{renderInput()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
],
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": ["content.styles.css", "icon-128.png", "icon-34.png", "content.bundle.js", "discord_dm.wasm", "twitter_profile.wasm"],
|
||||
"resources": ["content.styles.css", "icon-128.png", "icon-34.png", "content.bundle.js"],
|
||||
"matches": ["http://*/*", "https://*/*", "<all_urls>"]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import React, { ReactElement, useCallback } from 'react';
|
||||
import Icon from '../../components/Icon';
|
||||
import logo from '../../assets/img/icon-128.png';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { urlify } from '../../utils/misc';
|
||||
import browser from 'webextension-polyfill';
|
||||
import { BackgroundActiontype } from '../../entries/Background/rpc';
|
||||
import { BaseApproval } from '../BaseApproval';
|
||||
|
||||
export function ConnectionApproval(): ReactElement {
|
||||
const [params] = useSearchParams();
|
||||
const origin = params.get('origin');
|
||||
const favIconUrl = params.get('favIconUrl');
|
||||
const hostname = urlify(origin || '')?.hostname;
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.connect_response,
|
||||
data: false,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onAccept = useCallback(() => {
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.connect_response,
|
||||
data: true,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BaseApproval
|
||||
header={`Connecting to ${hostname}`}
|
||||
onSecondaryClick={onCancel}
|
||||
onPrimaryClick={onAccept}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-2 py-8">
|
||||
{!!favIconUrl ? (
|
||||
<img
|
||||
src={favIconUrl}
|
||||
className="h-16 w-16 border border-slate-200 bg-slate-200 rounded-full"
|
||||
alt="logo"
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
fa="fa-solid fa-globe"
|
||||
size={4}
|
||||
className="h-16 w-16 rounded-full border border-slate-200 text-blue-500"
|
||||
/>
|
||||
)}
|
||||
<div className="text-sm font-semibold">{hostname}</div>
|
||||
</div>
|
||||
<div className="text-lg font-bold text-center">Connect to this site?</div>
|
||||
<div className="text-sm px-8 text-center text-slate-500 flex-grow">
|
||||
Do you trust this site? By granting this permission, you're allowing
|
||||
this site to view your installed plugins, suggest requests to notarize,
|
||||
suggest plugins to install, ask you to share proofs metadata{' '}
|
||||
<i>(method, url, notary url, and proxy url)</i>, and ask to view a
|
||||
specific proof.
|
||||
</div>
|
||||
</BaseApproval>
|
||||
);
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
import React, { ReactElement, useCallback, useEffect } from 'react';
|
||||
import Icon from '../../components/Icon';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { safeParseJSON, urlify } from '../../utils/misc';
|
||||
import browser from 'webextension-polyfill';
|
||||
import { BackgroundActiontype } from '../../entries/Background/rpc';
|
||||
import { BaseApproval } from '../BaseApproval';
|
||||
import { minimatch } from 'minimatch';
|
||||
import { useAllProofHistory } from '../../reducers/history';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export function GetHistoryApproval(): ReactElement {
|
||||
const [params] = useSearchParams();
|
||||
const origin = params.get('origin');
|
||||
const favIconUrl = params.get('favIconUrl');
|
||||
const method = params.get('method');
|
||||
const url = params.get('url');
|
||||
const rawMetadata = params.get('metadata');
|
||||
const metadata = safeParseJSON(rawMetadata);
|
||||
const hostname = urlify(origin || '')?.hostname;
|
||||
const proofs = useAllProofHistory();
|
||||
|
||||
useEffect(() => {
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.get_prove_requests,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.get_history_response,
|
||||
data: false,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onAccept = useCallback(() => {
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.get_history_response,
|
||||
data: true,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const result = proofs.filter((proof) => {
|
||||
let matchedMetadata = true;
|
||||
if (metadata) {
|
||||
matchedMetadata = Object.entries(
|
||||
metadata as { [k: string]: string },
|
||||
).reduce((bool, [k, v]) => {
|
||||
try {
|
||||
return bool && minimatch(proof.metadata![k], v);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}, matchedMetadata);
|
||||
}
|
||||
|
||||
return (
|
||||
minimatch(proof.method, method!, { nocase: true }) &&
|
||||
minimatch(proof.url, url!) &&
|
||||
matchedMetadata
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<BaseApproval
|
||||
header="Requesting Proof History"
|
||||
onSecondaryClick={onCancel}
|
||||
onPrimaryClick={onAccept}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-2 py-8">
|
||||
{!!favIconUrl ? (
|
||||
<img
|
||||
src={favIconUrl}
|
||||
className="h-16 w-16 rounded-full border border-slate-200 bg-slate-200"
|
||||
alt="logo"
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
fa="fa-solid fa-globe"
|
||||
size={4}
|
||||
className="h-16 w-16 rounded-full border border-slate-200 text-blue-500"
|
||||
/>
|
||||
)}
|
||||
<div className="text-2xl text-center px-8">
|
||||
Do you want to share proof history with{' '}
|
||||
<b className="text-blue-500">{hostname}</b>?
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-4 text-sm px-8 text-center flex-grow">
|
||||
<div className="text-slate-500">
|
||||
All proofs matching the following patterns with be shared:
|
||||
</div>
|
||||
<table className="border border-collapse table-auto rounded text-xs w-full">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="px-2 py-1 border border-slate-300 bg-slate-100 text-slate-500 align-top w-16 text-left">
|
||||
Method
|
||||
</td>
|
||||
<td className="px-2 py-1 border border-slate-300 font-semibold text-black font-mono text-left">
|
||||
{method?.toUpperCase()}
|
||||
</td>
|
||||
</tr>
|
||||
<tr className="">
|
||||
<td className="px-2 py-1 border border-slate-300 bg-slate-100 text-slate-500 align-top w-16 text-left">
|
||||
URL
|
||||
</td>
|
||||
<td className="px-2 py-1 border border-slate-300 font-semibold text-black font-mono break-all text-left">
|
||||
{url}
|
||||
</td>
|
||||
</tr>
|
||||
{rawMetadata && (
|
||||
<tr className="">
|
||||
<td className="px-2 py-1 border border-slate-300 bg-slate-100 text-slate-500 align-top w-16 text-left">
|
||||
Metadata
|
||||
</td>
|
||||
<td className="px-2 py-1 border border-slate-300 font-semibold text-black font-mono break-all text-left">
|
||||
{rawMetadata}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<div
|
||||
className={classNames('border rounded font-semibold px-2 py-1', {
|
||||
'text-green-500 bg-green-200 border-green-300': result.length,
|
||||
'text-slate-500 bg-slate-200 border-slate-300': !result.length,
|
||||
})}
|
||||
>
|
||||
{result.length} results found
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs px-8 pb-2 text-center text-slate-500">
|
||||
Only certain metadata will be shared with the app, such as <i>id</i>,{' '}
|
||||
<i>method</i>, <i>url</i>, <i>notary</i>, <i>proxy</i>, and{' '}
|
||||
<i>timestamp</i>.
|
||||
</div>
|
||||
</BaseApproval>
|
||||
);
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
|
||||
import Icon from '../../components/Icon';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { safeParseJSON, urlify } from '../../utils/misc';
|
||||
import browser from 'webextension-polyfill';
|
||||
import { BackgroundActiontype } from '../../entries/Background/rpc';
|
||||
import { BaseApproval } from '../BaseApproval';
|
||||
import { getPlugins } from '../../entries/Background/db';
|
||||
import { minimatch } from 'minimatch';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export function GetPluginsApproval(): ReactElement {
|
||||
const [params] = useSearchParams();
|
||||
const origin = params.get('origin');
|
||||
const favIconUrl = params.get('favIconUrl');
|
||||
const url = params.get('url');
|
||||
const filterOrigin = params.get('filterOrigin');
|
||||
const rawMetadata = params.get('metadata');
|
||||
const filterMetadata = safeParseJSON(rawMetadata);
|
||||
const hostname = urlify(origin || '')?.hostname;
|
||||
const [result, setResult] = useState<any[]>([]);
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.get_plugins_response,
|
||||
data: false,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onAccept = useCallback(() => {
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.get_plugins_response,
|
||||
data: true,
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const response = await getPlugins();
|
||||
const res = response.filter(({ metadata }) => {
|
||||
let matchedMetadata = true;
|
||||
if (filterMetadata) {
|
||||
matchedMetadata = Object.entries(
|
||||
filterMetadata as { [k: string]: string },
|
||||
).reduce((bool, [k, v]) => {
|
||||
try {
|
||||
return bool && minimatch(metadata![k], v);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}, matchedMetadata);
|
||||
}
|
||||
return (
|
||||
minimatch(metadata.filePath, url || '**') &&
|
||||
minimatch(metadata.origin, filterOrigin || '**') &&
|
||||
matchedMetadata
|
||||
);
|
||||
});
|
||||
setResult(res);
|
||||
})();
|
||||
}, [url, JSON.stringify(filterMetadata)]);
|
||||
|
||||
return (
|
||||
<BaseApproval
|
||||
header="Requesting Plugins"
|
||||
onSecondaryClick={onCancel}
|
||||
onPrimaryClick={onAccept}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-2 py-8">
|
||||
{!!favIconUrl ? (
|
||||
<img
|
||||
src={favIconUrl}
|
||||
className="h-16 w-16 rounded-full border border-slate-200 bg-slate-200"
|
||||
alt="logo"
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
fa="fa-solid fa-globe"
|
||||
size={4}
|
||||
className="h-16 w-16 rounded-full border border-slate-200 text-blue-500"
|
||||
/>
|
||||
)}
|
||||
<div className="text-2xl text-center px-8">
|
||||
Do you want to share installed plugins with{' '}
|
||||
<b className="text-blue-500">{hostname}</b>?
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-4 text-sm px-8 text-center flex-grow">
|
||||
<div className="text-slate-500">
|
||||
All plugins matching the following patterns with be shared:
|
||||
</div>
|
||||
<table className="border border-collapse table-auto rounded text-xs w-full">
|
||||
<tbody>
|
||||
<tr className="">
|
||||
<td className="px-2 py-1 border border-slate-300 bg-slate-100 text-slate-500 align-top w-16 text-left">
|
||||
URL
|
||||
</td>
|
||||
<td className="px-2 py-1 border border-slate-300 font-semibold text-black font-mono break-all text-left">
|
||||
{url}
|
||||
</td>
|
||||
</tr>
|
||||
<tr className="">
|
||||
<td className="px-2 py-1 border border-slate-300 bg-slate-100 text-slate-500 align-top w-16 text-left">
|
||||
Origin
|
||||
</td>
|
||||
<td className="px-2 py-1 border border-slate-300 font-semibold text-black font-mono break-all text-left">
|
||||
{filterOrigin}
|
||||
</td>
|
||||
</tr>
|
||||
{rawMetadata && (
|
||||
<tr className="">
|
||||
<td className="px-2 py-1 border border-slate-300 bg-slate-100 text-slate-500 align-top w-16 text-left">
|
||||
Metadata
|
||||
</td>
|
||||
<td className="px-2 py-1 border border-slate-300 font-semibold text-black font-mono break-all text-left">
|
||||
{rawMetadata}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<div
|
||||
className={classNames('border rounded font-semibold px-2 py-1', {
|
||||
'text-green-500 bg-green-200 border-green-300': result.length,
|
||||
'text-slate-500 bg-slate-200 border-slate-300': !result.length,
|
||||
})}
|
||||
>
|
||||
{result.length} results found
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs px-8 pb-2 text-center text-slate-500">
|
||||
Only certain metadata will be shared with the app, such as <i>id</i>,{' '}
|
||||
<i>method</i>, <i>url</i>, <i>notary</i>, <i>proxy</i>, and{' '}
|
||||
<i>timestamp</i>.
|
||||
</div>
|
||||
</BaseApproval>
|
||||
);
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import React, { ReactElement, useCallback, useEffect } from 'react';
|
||||
import Icon from '../../components/Icon';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { urlify } from '../../utils/misc';
|
||||
import browser from 'webextension-polyfill';
|
||||
import { BackgroundActiontype } from '../../entries/Background/rpc';
|
||||
import { BaseApproval } from '../BaseApproval';
|
||||
import { OneRequestHistory } from '../History';
|
||||
|
||||
export function GetProofApproval(): ReactElement {
|
||||
const [params] = useSearchParams();
|
||||
const origin = params.get('origin');
|
||||
const favIconUrl = params.get('favIconUrl');
|
||||
const id = params.get('id');
|
||||
const hostname = urlify(origin || '')?.hostname;
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.get_proof_response,
|
||||
data: false,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onAccept = useCallback(() => {
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.get_proof_response,
|
||||
data: true,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BaseApproval
|
||||
header="Requesting Proof History"
|
||||
onSecondaryClick={onCancel}
|
||||
onPrimaryClick={onAccept}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-2 py-8">
|
||||
{!!favIconUrl ? (
|
||||
<img
|
||||
src={favIconUrl}
|
||||
className="h-16 w-16 rounded-full border border-slate-200 bg-slate-200"
|
||||
alt="logo"
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
fa="fa-solid fa-globe"
|
||||
size={4}
|
||||
className="h-16 w-16 rounded-full border border-slate-200 text-blue-500"
|
||||
/>
|
||||
)}
|
||||
<div className="text-2xl text-center px-8">
|
||||
Do you want to share proof data with{' '}
|
||||
<b className="text-blue-500">{hostname}</b>?
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-4 text-sm px-8 text-center flex-grow">
|
||||
<div className="text-slate-500">
|
||||
The following proof will be shared:
|
||||
</div>
|
||||
<OneRequestHistory
|
||||
className="w-full !cursor-default hover:bg-white text-xs"
|
||||
requestId={id!}
|
||||
hideActions={['share', 'delete', 'retry']}
|
||||
/>
|
||||
</div>
|
||||
</BaseApproval>
|
||||
);
|
||||
}
|
||||
@@ -1,60 +1,50 @@
|
||||
import React, {
|
||||
MouseEventHandler,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import Icon from '../../components/Icon';
|
||||
import classNames from 'classnames';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { ErrorModal } from '../../components/ErrorModal';
|
||||
import History from '../History';
|
||||
import './index.scss';
|
||||
import Requests from '../Requests';
|
||||
import PluginUploadInfo from '../../components/PluginInfo';
|
||||
import {
|
||||
useOnPluginClick,
|
||||
usePluginConfig,
|
||||
usePluginHashes,
|
||||
} from '../../reducers/plugins';
|
||||
import { fetchPluginHashes } from '../../utils/rpc';
|
||||
import DefaultPluginIcon from '../../assets/img/default-plugin-icon.png';
|
||||
import { useClientId } from '../../reducers/p2p';
|
||||
import { PluginList } from '../../components/PluginList';
|
||||
import { getDeveloperMode } from '../../utils/storage';
|
||||
|
||||
export default function Home(props: {
|
||||
tab?: 'history' | 'network';
|
||||
}): ReactElement {
|
||||
const [error, showError] = useState('');
|
||||
const [tab, setTab] = useState<'history' | 'network'>(props.tab || 'history');
|
||||
const [tab, setTab] = useState<'history' | 'network' | 'plugins'>(
|
||||
props.tab || 'history',
|
||||
);
|
||||
const scrollableContent = useRef<HTMLDivElement | null>(null);
|
||||
const [shouldFix, setFix] = useState(false);
|
||||
const [actionPanelElement, setActionPanelElement] =
|
||||
useState<HTMLDivElement | null>(null);
|
||||
const [scrollTop, setScrollTop] = useState(0);
|
||||
const [developerMode, setDeveloperMode] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchPluginHashes();
|
||||
getDeveloperMode().then(setDeveloperMode);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.tab === 'network' && !developerMode) {
|
||||
setTab('history');
|
||||
}
|
||||
}, [props.tab, developerMode]);
|
||||
|
||||
useEffect(() => {
|
||||
const element = scrollableContent.current;
|
||||
if (!element) return;
|
||||
if (!actionPanelElement) return;
|
||||
|
||||
let timer = Date.now();
|
||||
const onScroll = () => {
|
||||
const now = Date.now();
|
||||
if (now - timer > 20) {
|
||||
timer = now;
|
||||
setScrollTop(element.scrollTop);
|
||||
if (element.scrollTop >= actionPanelElement.clientHeight) {
|
||||
setFix(true);
|
||||
} else {
|
||||
setFix(false);
|
||||
}
|
||||
if (element.scrollTop > 0) {
|
||||
setFix(true);
|
||||
} else {
|
||||
setFix(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -63,7 +53,7 @@ export default function Home(props: {
|
||||
return () => {
|
||||
element.removeEventListener('scroll', onScroll);
|
||||
};
|
||||
}, [scrollableContent, actionPanelElement]);
|
||||
}, [scrollableContent]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -72,10 +62,6 @@ export default function Home(props: {
|
||||
className="flex flex-col flex-grow overflow-y-auto"
|
||||
>
|
||||
{error && <ErrorModal onClose={() => showError('')} message={error} />}
|
||||
<ActionPanel
|
||||
setActionPanelElement={setActionPanelElement}
|
||||
scrollTop={scrollTop}
|
||||
/>
|
||||
<div
|
||||
className={classNames(
|
||||
'flex flex-row justify-center items-center z-10',
|
||||
@@ -85,179 +71,45 @@ export default function Home(props: {
|
||||
},
|
||||
)}
|
||||
>
|
||||
<TabSelector
|
||||
onClick={() => setTab('network')}
|
||||
selected={tab === 'network'}
|
||||
>
|
||||
Network
|
||||
</TabSelector>
|
||||
{developerMode && (
|
||||
<TabSelector
|
||||
onClick={() => setTab('network')}
|
||||
selected={tab === 'network'}
|
||||
>
|
||||
Network
|
||||
</TabSelector>
|
||||
)}
|
||||
<TabSelector
|
||||
onClick={() => setTab('history')}
|
||||
selected={tab === 'history'}
|
||||
>
|
||||
History
|
||||
</TabSelector>
|
||||
{developerMode && (
|
||||
<TabSelector
|
||||
onClick={() => setTab('plugins')}
|
||||
selected={tab === 'plugins'}
|
||||
>
|
||||
Plugins
|
||||
</TabSelector>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-grow">
|
||||
{tab === 'history' && <History />}
|
||||
{tab === 'network' && <Requests shouldFix={shouldFix} />}
|
||||
{tab === 'network' && developerMode && (
|
||||
<Requests shouldFix={shouldFix} />
|
||||
)}
|
||||
{tab === 'plugins' && (
|
||||
<PluginList
|
||||
className="p-2 overflow-y-auto"
|
||||
showAddButton={developerMode}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ActionPanel({
|
||||
setActionPanelElement,
|
||||
scrollTop,
|
||||
}: {
|
||||
scrollTop: number;
|
||||
setActionPanelElement: (el: HTMLDivElement) => void;
|
||||
}) {
|
||||
const pluginHashes = usePluginHashes();
|
||||
const navigate = useNavigate();
|
||||
const clientId = useClientId();
|
||||
const container = useRef<HTMLDivElement | null>(null);
|
||||
const [isOverflow, setOverflow] = useState(false);
|
||||
const [expanded, setExpand] = useState(false);
|
||||
|
||||
const onCheckSize = useCallback(() => {
|
||||
const element = container.current;
|
||||
|
||||
if (!element) return;
|
||||
|
||||
setActionPanelElement(element);
|
||||
|
||||
if (element.scrollWidth > element.clientWidth) {
|
||||
setOverflow(true);
|
||||
} else {
|
||||
setOverflow(false);
|
||||
}
|
||||
}, [container]);
|
||||
|
||||
useEffect(() => {
|
||||
onCheckSize();
|
||||
|
||||
window.addEventListener('resize', onCheckSize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', onCheckSize);
|
||||
};
|
||||
}, [onCheckSize, pluginHashes]);
|
||||
|
||||
useEffect(() => {
|
||||
const element = container.current;
|
||||
|
||||
if (!element) return;
|
||||
|
||||
if (scrollTop >= element.clientHeight) {
|
||||
setExpand(false);
|
||||
}
|
||||
}, [container, scrollTop]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={container}
|
||||
className={classNames(
|
||||
'flex flex-row justify-start items-center gap-4 p-4 border-b relative',
|
||||
{
|
||||
'flex-wrap': expanded,
|
||||
'flex-nowrap': !expanded,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<NavButton
|
||||
fa="fa-solid fa-hammer"
|
||||
onClick={() => navigate('/custom')}
|
||||
title="Build a custom request"
|
||||
>
|
||||
Custom
|
||||
</NavButton>
|
||||
<NavButton
|
||||
fa="fa-solid fa-certificate"
|
||||
onClick={() => navigate('/verify')}
|
||||
title="Visualize an attestation"
|
||||
>
|
||||
Verify
|
||||
</NavButton>
|
||||
<NavButton
|
||||
className={'relative'}
|
||||
fa="fa-solid fa-network-wired"
|
||||
iconSize={0.5}
|
||||
iconClassName={classNames({
|
||||
'!text-green-500': clientId,
|
||||
})}
|
||||
onClick={() => navigate('/p2p')}
|
||||
>
|
||||
P2P
|
||||
</NavButton>
|
||||
{pluginHashes.map((hash) => (
|
||||
<PluginIcon hash={hash} onCheckSize={onCheckSize} />
|
||||
))}
|
||||
<button
|
||||
className={
|
||||
'flex flex-row shrink-0 items-center justify-center self-start rounded relative border-2 border-dashed border-slate-300 hover:border-slate-400 text-slate-300 hover:text-slate-400 h-16 w-16 mx-1'
|
||||
}
|
||||
title="Install a plugin"
|
||||
>
|
||||
<PluginUploadInfo />
|
||||
<Icon fa="fa-solid fa-plus" />
|
||||
</button>
|
||||
<button
|
||||
className={classNames(
|
||||
'absolute right-0 top-0 w-6 h-full bg-slate-100 hover:bg-slate-200 font-semibold',
|
||||
'flex flex-row items-center justify-center gap-2 text-slate-500 hover:text-slate-700',
|
||||
{
|
||||
hidden: !isOverflow || expanded,
|
||||
},
|
||||
)}
|
||||
onClick={() => setExpand(true)}
|
||||
>
|
||||
<Icon fa="fa-solid fa-caret-down" size={0.875} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PluginIcon({
|
||||
hash,
|
||||
onCheckSize,
|
||||
}: {
|
||||
hash: string;
|
||||
onCheckSize: () => void;
|
||||
}) {
|
||||
const config = usePluginConfig(hash);
|
||||
const onPluginClick = useOnPluginClick(hash);
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
if (!config) return;
|
||||
onPluginClick();
|
||||
}, [onPluginClick, config]);
|
||||
|
||||
if (!config) return null;
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={() => {
|
||||
onCheckSize();
|
||||
}}
|
||||
className={classNames(
|
||||
'flex flex-col flex-nowrap items-center justify-center',
|
||||
'text-white px-2 py-1 gap-1 opacity-90 hover:opacity-100 w-18',
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Icon
|
||||
className="rounded-full flex flex-row items-center justify-center flex-grow-0 flex-shrink-0"
|
||||
url={config?.icon || DefaultPluginIcon}
|
||||
size={2}
|
||||
/>
|
||||
<span className="font-bold text-primary h-10 w-14 overflow-hidden text-ellipsis">
|
||||
{config?.title}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function TabSelector(props: {
|
||||
children: string;
|
||||
className?: string;
|
||||
@@ -280,39 +132,3 @@ function TabSelector(props: {
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function NavButton(props: {
|
||||
fa: string;
|
||||
children?: ReactNode;
|
||||
onClick?: MouseEventHandler;
|
||||
className?: string;
|
||||
title?: string;
|
||||
iconClassName?: string;
|
||||
disabled?: boolean;
|
||||
iconSize?: number;
|
||||
}): ReactElement {
|
||||
return (
|
||||
<button
|
||||
className={classNames(
|
||||
'flex flex-col flex-nowrap items-center justify-center',
|
||||
'text-white px-2 py-1 gap-1 opacity-90 hover:opacity-100 w-18',
|
||||
props.className,
|
||||
)}
|
||||
onClick={props.onClick}
|
||||
disabled={props.disabled}
|
||||
title={props.title}
|
||||
>
|
||||
<Icon
|
||||
className={classNames(
|
||||
'w-8 h-8 rounded-full bg-primary flex flex-row items-center justify-center flex-grow-0 flex-shrink-0',
|
||||
props.iconClassName,
|
||||
)}
|
||||
fa={props.fa}
|
||||
size={0.875}
|
||||
/>
|
||||
<span className="font-bold text-primary h-10 w-14 overflow-hidden text-ellipsis">
|
||||
{props.children}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
|
||||
import Icon from '../../components/Icon';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import {
|
||||
getPluginConfig,
|
||||
makePlugin,
|
||||
type PluginConfig,
|
||||
urlify,
|
||||
} from '../../utils/misc';
|
||||
import browser from 'webextension-polyfill';
|
||||
import { BackgroundActiontype } from '../../entries/Background/rpc';
|
||||
import { BaseApproval } from '../BaseApproval';
|
||||
import { PluginPermissions } from '../../components/PluginInfo';
|
||||
|
||||
export function InstallPluginApproval(): ReactElement {
|
||||
const [params] = useSearchParams();
|
||||
const origin = params.get('origin');
|
||||
const favIconUrl = params.get('favIconUrl');
|
||||
const url = params.get('url');
|
||||
const rawMetadata = params.get('metadata');
|
||||
const hostname = urlify(origin || '')?.hostname;
|
||||
|
||||
const [error, showError] = useState('');
|
||||
const [pluginBuffer, setPluginBuffer] = useState<ArrayBuffer | any>(null);
|
||||
const [pluginContent, setPluginContent] = useState<PluginConfig | null>(null);
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.install_plugin_response,
|
||||
data: false,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onAccept = useCallback(() => {
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.install_plugin_response,
|
||||
data: true,
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const resp = await fetch(url!);
|
||||
const arrayBuffer = await resp.arrayBuffer();
|
||||
const plugin = await makePlugin(arrayBuffer);
|
||||
setPluginContent(await getPluginConfig(plugin));
|
||||
setPluginBuffer(arrayBuffer);
|
||||
} catch (e: any) {
|
||||
showError(e?.message || 'Invalid Plugin');
|
||||
}
|
||||
})();
|
||||
}, [url]);
|
||||
|
||||
return (
|
||||
<BaseApproval
|
||||
header={`Installing Plugin`}
|
||||
onSecondaryClick={onCancel}
|
||||
onPrimaryClick={onAccept}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-2 py-8">
|
||||
{!!favIconUrl ? (
|
||||
<img
|
||||
src={favIconUrl}
|
||||
className="h-16 w-16 rounded-full border border-slate-200 bg-slate-200"
|
||||
alt="logo"
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
fa="fa-solid fa-globe"
|
||||
size={4}
|
||||
className="h-16 w-16 rounded-full border border-slate-200 text-blue-500"
|
||||
/>
|
||||
)}
|
||||
<div className="text-2xl text-center px-8">
|
||||
<b className="text-blue-500">{hostname}</b> wants to install a plugin:
|
||||
</div>
|
||||
</div>
|
||||
{!pluginContent && (
|
||||
<div className="flex flex-col items-center flex-grow gap-4 border border-slate-300 p-8 mx-8 rounded bg-slate-100">
|
||||
<Icon
|
||||
className="animate-spin w-fit text-slate-500"
|
||||
fa="fa-solid fa-spinner"
|
||||
size={1}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{pluginContent && (
|
||||
<div className="flex flex-col flex-grow gap-4 border border-slate-300 p-8 mx-8 rounded bg-slate-100">
|
||||
<div className="flex flex-col items-center">
|
||||
<img
|
||||
className="w-12 h-12 mb-2"
|
||||
src={pluginContent.icon}
|
||||
alt="Plugin Icon"
|
||||
/>
|
||||
<span className="text-3xl text-blue-600 font-semibold">
|
||||
{pluginContent.title}
|
||||
</span>
|
||||
<div className="text-slate-500 text-lg">
|
||||
{pluginContent.description}
|
||||
</div>
|
||||
</div>
|
||||
<PluginPermissions className="w-full" pluginContent={pluginContent} />
|
||||
</div>
|
||||
)}
|
||||
</BaseApproval>
|
||||
);
|
||||
}
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
LOGGING_FILTER_KEY,
|
||||
getRendezvousApi,
|
||||
RENDEZVOUS_API_LS_KEY,
|
||||
getDeveloperMode,
|
||||
DEVELOPER_MODE_LS_KEY,
|
||||
} from '../../utils/storage';
|
||||
import {
|
||||
EXPLORER_API,
|
||||
@@ -32,6 +34,7 @@ import Modal, { ModalContent } from '../../components/Modal/Modal';
|
||||
import browser from 'webextension-polyfill';
|
||||
import { LoggingLevel } from 'tlsn-js';
|
||||
import { version } from '../../../package.json';
|
||||
import { getDBSize, resetDB } from '../../entries/Background/db';
|
||||
|
||||
export default function Options(): ReactElement {
|
||||
const [notary, setNotary] = useState(NOTARY_API);
|
||||
@@ -40,16 +43,28 @@ export default function Options(): ReactElement {
|
||||
const [maxReceived, setMaxReceived] = useState(MAX_RECV);
|
||||
const [loggingLevel, setLoggingLevel] = useState<LoggingLevel>('Info');
|
||||
const [rendezvous, setRendezvous] = useState(RENDEZVOUS_API);
|
||||
const [developerMode, setDeveloperMode] = useState(false);
|
||||
|
||||
const [dirty, setDirty] = useState(false);
|
||||
const [shouldReload, setShouldReload] = useState(false);
|
||||
const [advanced, setAdvanced] = useState(false);
|
||||
const [showReloadModal, setShowReloadModal] = useState(false);
|
||||
const [dbSize, setDbSize] = useState(0);
|
||||
const [isCalculatingDbSize, setIsCalculatingDbSize] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setIsCalculatingDbSize(true);
|
||||
setDbSize(await getDBSize());
|
||||
setIsCalculatingDbSize(false);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setNotary(await getNotaryApi());
|
||||
setProxy(await getProxyApi());
|
||||
setDeveloperMode(await getDeveloperMode());
|
||||
})();
|
||||
}, []);
|
||||
|
||||
@@ -74,6 +89,7 @@ export default function Options(): ReactElement {
|
||||
await set(MAX_RECEIVED_LS_KEY, maxReceived.toString());
|
||||
await set(LOGGING_FILTER_KEY, loggingLevel);
|
||||
await set(RENDEZVOUS_API_LS_KEY, rendezvous);
|
||||
await set(DEVELOPER_MODE_LS_KEY, developerMode.toString());
|
||||
setDirty(false);
|
||||
},
|
||||
[
|
||||
@@ -83,6 +99,7 @@ export default function Options(): ReactElement {
|
||||
maxReceived,
|
||||
loggingLevel,
|
||||
rendezvous,
|
||||
developerMode,
|
||||
shouldReload,
|
||||
],
|
||||
);
|
||||
@@ -103,6 +120,13 @@ export default function Options(): ReactElement {
|
||||
browser.tabs.create({ url });
|
||||
}, []);
|
||||
|
||||
const onCleanCache = useCallback(async () => {
|
||||
setIsCalculatingDbSize(true);
|
||||
await resetDB();
|
||||
setDbSize(await getDBSize());
|
||||
setIsCalculatingDbSize(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-nowrap flex-grow overflow-y-auto">
|
||||
{showReloadModal && (
|
||||
@@ -139,6 +163,8 @@ export default function Options(): ReactElement {
|
||||
proxy={proxy}
|
||||
setProxy={setProxy}
|
||||
setDirty={setDirty}
|
||||
developerMode={developerMode}
|
||||
setDeveloperMode={setDeveloperMode}
|
||||
/>
|
||||
<div className="justify-left px-2 pt-3 gap-2">
|
||||
<button className="font-bold" onClick={onAdvanced}>
|
||||
@@ -192,6 +218,15 @@ export default function Options(): ReactElement {
|
||||
>
|
||||
Join our Discord
|
||||
</button>
|
||||
<button className="button" onClick={onCleanCache}>
|
||||
<span>Clean Cache (</span>
|
||||
{isCalculatingDbSize ? (
|
||||
<i className="fa-solid fa-spinner fa-spin"></i>
|
||||
) : (
|
||||
<span>{(dbSize / 1024 / 1024).toFixed(2)} MB</span>
|
||||
)}
|
||||
<span>)</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -228,8 +263,18 @@ function NormalOptions(props: {
|
||||
proxy: string;
|
||||
setProxy: (value: string) => void;
|
||||
setDirty: (value: boolean) => void;
|
||||
developerMode: boolean;
|
||||
setDeveloperMode: (value: boolean) => void;
|
||||
}) {
|
||||
const { notary, setNotary, proxy, setProxy, setDirty } = props;
|
||||
const {
|
||||
notary,
|
||||
setNotary,
|
||||
proxy,
|
||||
setProxy,
|
||||
setDirty,
|
||||
developerMode,
|
||||
setDeveloperMode,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -261,6 +306,33 @@ function NormalOptions(props: {
|
||||
<div className="font-semibold">Explorer URL</div>
|
||||
<div className="input border bg-slate-100">{EXPLORER_API}</div>
|
||||
</div>
|
||||
<div className="flex flex-row items-center py-3 px-2 gap-2">
|
||||
<div className="font-semibold">Developer Mode</div>
|
||||
<div className="relative inline-block w-9 h-5">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="developer-mode"
|
||||
checked={developerMode}
|
||||
onChange={(e) => {
|
||||
setDeveloperMode(e.target.checked);
|
||||
setDirty(true);
|
||||
}}
|
||||
className="sr-only"
|
||||
/>
|
||||
<label
|
||||
htmlFor="developer-mode"
|
||||
className={`block h-5 rounded-full cursor-pointer transition-all duration-300 ease-in-out ${
|
||||
developerMode ? 'bg-blue-500' : 'bg-gray-300'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`absolute top-0.5 left-0.5 w-4 h-4 bg-white rounded-full shadow-sm transform transition-all duration-300 ease-in-out ${
|
||||
developerMode ? 'translate-x-4' : 'translate-x-0'
|
||||
}`}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -312,15 +384,6 @@ function AdvancedOptions(props: {
|
||||
setDirty(true);
|
||||
}}
|
||||
/>
|
||||
<InputField
|
||||
label="Rendezvous API (for P2P)"
|
||||
value={rendezvous}
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
setRendezvous(e.target.value);
|
||||
setDirty(true);
|
||||
}}
|
||||
/>
|
||||
<div className="flex flex-col flex-nowrap py-1 px-2 gap-2">
|
||||
<div className="font-semibold">Logging Level</div>
|
||||
<select
|
||||
|
||||
@@ -38,7 +38,6 @@ import browser from 'webextension-polyfill';
|
||||
import { sha256 } from '../../utils/misc';
|
||||
import { openSidePanel } from '../../entries/utils';
|
||||
import { SidePanelActionTypes } from '../../entries/SidePanel/types';
|
||||
import { verify } from 'tlsn-js-v5';
|
||||
import ProofViewer from '../ProofViewer';
|
||||
|
||||
export function P2PHome(): ReactElement {
|
||||
|
||||
@@ -151,6 +151,15 @@ export default function ProofViewer(props?: {
|
||||
label="Notary Key"
|
||||
value={props?.notaryKey || request?.verification?.notaryKey}
|
||||
/>
|
||||
|
||||
{request?.metadata &&
|
||||
Object.entries(request.metadata).map(([key, value]) => (
|
||||
<MetadataRow
|
||||
key={`req-${key}`}
|
||||
label={`Custom: ${key}`}
|
||||
value={String(value)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -7,40 +7,59 @@ import { BackgroundActiontype } from '../../entries/Background/rpc';
|
||||
import { BaseApproval } from '../BaseApproval';
|
||||
import { PluginPermissions } from '../../components/PluginInfo';
|
||||
import {
|
||||
getPluginConfigByHash,
|
||||
getPluginMetadataByHash,
|
||||
getPluginConfigByUrl,
|
||||
getPluginMetadataByUrl,
|
||||
getPluginByUrl,
|
||||
} from '../../entries/Background/db';
|
||||
import { runPlugin } from '../../utils/rpc';
|
||||
import { SidePanelActionTypes } from '../../entries/SidePanel/types';
|
||||
import { deferredPromise } from '../../utils/promise';
|
||||
import { installPlugin } from '../../entries/Background/plugins/utils';
|
||||
|
||||
export function RunPluginApproval(): ReactElement {
|
||||
export function RunPluginByUrlApproval(): ReactElement {
|
||||
const [params] = useSearchParams();
|
||||
const origin = params.get('origin');
|
||||
const favIconUrl = params.get('favIconUrl');
|
||||
const hash = params.get('hash');
|
||||
const url = params.get('url');
|
||||
const pluginParams = params.get('params');
|
||||
const hostname = urlify(origin || '')?.hostname;
|
||||
const [error, showError] = useState('');
|
||||
const [metadata, setPluginMetadata] = useState<PluginMetadata | null>(null);
|
||||
const [pluginContent, setPluginContent] = useState<PluginConfig | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!url) return;
|
||||
(async () => {
|
||||
try {
|
||||
const hex = await getPluginByUrl(url);
|
||||
|
||||
if (!hex) {
|
||||
await installPlugin(url);
|
||||
}
|
||||
|
||||
const config = await getPluginConfigByUrl(url);
|
||||
const metadata = await getPluginMetadataByUrl(url);
|
||||
setPluginContent(config);
|
||||
setPluginMetadata(metadata);
|
||||
} catch (e: any) {
|
||||
showError(e?.message || 'Invalid Plugin');
|
||||
}
|
||||
})();
|
||||
}, [url]);
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.run_plugin_response,
|
||||
type: BackgroundActiontype.run_plugin_by_url_response,
|
||||
data: false,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onAccept = useCallback(async () => {
|
||||
if (!hash) return;
|
||||
if (!url) return;
|
||||
try {
|
||||
const tab = await browser.tabs.create({
|
||||
active: true,
|
||||
});
|
||||
|
||||
await browser.storage.local.set({ plugin_hash: hash });
|
||||
|
||||
const { promise, resolve } = deferredPromise();
|
||||
|
||||
const listener = async (request: any) => {
|
||||
@@ -60,33 +79,19 @@ export function RunPluginApproval(): ReactElement {
|
||||
browser.runtime.sendMessage({
|
||||
type: SidePanelActionTypes.execute_plugin_request,
|
||||
data: {
|
||||
pluginHash: hash,
|
||||
pluginUrl: url,
|
||||
pluginParams: pluginParams ? JSON.parse(pluginParams) : undefined,
|
||||
},
|
||||
});
|
||||
|
||||
browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.run_plugin_response,
|
||||
type: BackgroundActiontype.run_plugin_by_url_response,
|
||||
data: true,
|
||||
});
|
||||
} catch (e: any) {
|
||||
showError(e.message);
|
||||
}
|
||||
}, [hash]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!hash) return;
|
||||
try {
|
||||
const config = await getPluginConfigByHash(hash);
|
||||
const metadata = await getPluginMetadataByHash(hash);
|
||||
setPluginContent(config);
|
||||
setPluginMetadata(metadata);
|
||||
} catch (e: any) {
|
||||
showError(e?.message || 'Invalid Plugin');
|
||||
}
|
||||
})();
|
||||
}, [hash]);
|
||||
}, [url]);
|
||||
|
||||
return (
|
||||
<BaseApproval
|
||||
@@ -1,13 +1,9 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppRootState } from './index';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { getPluginConfigByHash } from '../entries/Background/db';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getPluginConfigByUrl } from '../entries/Background/db';
|
||||
import { PluginConfig } from '../utils/misc';
|
||||
import { runPlugin } from '../utils/rpc';
|
||||
import browser from 'webextension-polyfill';
|
||||
import { openSidePanel } from '../entries/utils';
|
||||
import { SidePanelActionTypes } from '../entries/SidePanel/types';
|
||||
|
||||
enum ActionType {
|
||||
'/plugin/addPlugin' = '/plugin/addPlugin',
|
||||
@@ -64,27 +60,8 @@ export const usePluginConfig = (hash: string) => {
|
||||
const [config, setConfig] = useState<PluginConfig | null>(null);
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
setConfig(await getPluginConfigByHash(hash));
|
||||
setConfig(await getPluginConfigByUrl(hash));
|
||||
})();
|
||||
}, [hash]);
|
||||
return config;
|
||||
};
|
||||
|
||||
export const useOnPluginClick = (hash: string) => {
|
||||
return useCallback(async () => {
|
||||
await browser.storage.local.set({ plugin_hash: hash });
|
||||
|
||||
await openSidePanel();
|
||||
|
||||
browser.runtime.sendMessage({
|
||||
type: SidePanelActionTypes.execute_plugin_request,
|
||||
data: {
|
||||
pluginHash: hash,
|
||||
},
|
||||
});
|
||||
|
||||
await runPlugin(hash, 'start');
|
||||
|
||||
window.close();
|
||||
}, [hash]);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const EXPLORER_API = 'https://explorer.tlsnotary.org';
|
||||
export const NOTARY_API = 'https://notary.pse.dev/v0.1.0-alpha.9';
|
||||
export const NOTARY_API = 'https://notary.pse.dev/v0.1.0-alpha.12';
|
||||
export const RENDEZVOUS_API = 'wss://explorer.tlsnotary.org';
|
||||
export const NOTARY_PROXY = 'wss://notary.pse.dev/proxy';
|
||||
export const MAX_RECV = 16384;
|
||||
|
||||
@@ -188,11 +188,13 @@ export const makePlugin = async (
|
||||
} = {
|
||||
redirect: function (context: CallContext, off: bigint) {
|
||||
const r = context.read(off);
|
||||
if (!r) throw new Error('Failed to read context');
|
||||
const url = r.text();
|
||||
browser.tabs.update(tab.id, { url });
|
||||
},
|
||||
notarize: function (context: CallContext, off: bigint) {
|
||||
const r = context.read(off);
|
||||
if (!r) throw new Error('Failed to read context');
|
||||
const params = JSON.parse(r.text());
|
||||
const now = Date.now();
|
||||
const id = charwise.encode(now).toString('hex');
|
||||
@@ -221,19 +223,29 @@ export const makePlugin = async (
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const { getSecretResponse, body: reqBody } = params;
|
||||
const {
|
||||
getSecretResponse,
|
||||
body: reqBody,
|
||||
interactive,
|
||||
verifierPlugin,
|
||||
} = params;
|
||||
|
||||
if (meta?.p2p) {
|
||||
console.log('interactive', interactive);
|
||||
console.log('verifierPlugin', verifierPlugin);
|
||||
console.log('params', params);
|
||||
if (interactive) {
|
||||
const pluginHex = Buffer.from(arrayBuffer).toString('hex');
|
||||
const pluginUrl = await sha256(pluginHex);
|
||||
|
||||
handleExecP2PPluginProver({
|
||||
type: BackgroundActiontype.execute_p2p_plugin_prover,
|
||||
data: {
|
||||
...params,
|
||||
pluginHash: await sha256(pluginHex),
|
||||
pluginUrl,
|
||||
pluginHex,
|
||||
body: reqBody,
|
||||
now,
|
||||
clientId: meta.clientId,
|
||||
verifierPlugin,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
@@ -246,7 +258,7 @@ export const makePlugin = async (
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(async () => {
|
||||
const out = await plugin.call(getSecretResponse, body);
|
||||
resolve(JSON.parse(out.string()));
|
||||
resolve(JSON.parse(out?.string() || '{}'));
|
||||
}, 0);
|
||||
});
|
||||
},
|
||||
@@ -337,7 +349,10 @@ export const makePlugin = async (
|
||||
|
||||
const pluginConfig: ExtismPluginOptions = {
|
||||
useWasi: true,
|
||||
config: injectedConfig,
|
||||
config: {
|
||||
...injectedConfig,
|
||||
tabId: tab.id?.toString() || '',
|
||||
},
|
||||
// allowedHosts: approvedRequests.map((r) => urlify(r.url)?.origin),
|
||||
functions: {
|
||||
'extism:host/user': funcs,
|
||||
@@ -348,12 +363,23 @@ export const makePlugin = async (
|
||||
return plugin;
|
||||
};
|
||||
|
||||
export type InputFieldConfig = {
|
||||
name: string; // Unique identifier for the input field
|
||||
label: string; // Display label for the input
|
||||
type: 'text' | 'password' | 'email' | 'number' | 'textarea' | 'select'; // Input field type
|
||||
placeholder?: string; // Optional placeholder text
|
||||
required?: boolean; // Whether the field is required
|
||||
defaultValue?: string; // Default value for the field
|
||||
options?: { value: string; label: string }[]; // Options for select type
|
||||
};
|
||||
|
||||
export type StepConfig = {
|
||||
title: string; // Text for the step's title
|
||||
description?: string; // Text for the step's description (optional)
|
||||
cta: string; // Text for the step's call-to-action button
|
||||
action: string; // The function name that this step will execute
|
||||
prover?: boolean; // Boolean indicating if this step outputs a notarization (optional)
|
||||
inputs?: InputFieldConfig[]; // Input fields for user data collection (optional)
|
||||
};
|
||||
|
||||
export type PluginConfig = {
|
||||
@@ -381,6 +407,7 @@ export const getPluginConfig = async (
|
||||
): Promise<PluginConfig> => {
|
||||
const plugin = data instanceof ArrayBuffer ? await makePlugin(data) : data;
|
||||
const out = await plugin.call('config');
|
||||
if (!out) throw new Error('Plugin config call returned null');
|
||||
const config: PluginConfig = JSON.parse(out.string());
|
||||
|
||||
assert(typeof config.title === 'string' && config.title.length);
|
||||
@@ -438,6 +465,23 @@ export const getPluginConfig = async (
|
||||
assert(typeof step.cta === 'string' && step.cta.length);
|
||||
assert(typeof step.action === 'string' && step.action.length);
|
||||
assert(!step.prover || typeof step.prover === 'boolean');
|
||||
|
||||
if (step.inputs) {
|
||||
for (const input of step.inputs) {
|
||||
assert(typeof input.name === 'string' && input.name.length);
|
||||
assert(typeof input.label === 'string' && input.label.length);
|
||||
assert(!input.placeholder || typeof input.placeholder === 'string');
|
||||
assert(!input.required || typeof input.required === 'boolean');
|
||||
assert(!input.defaultValue || typeof input.defaultValue === 'string');
|
||||
if (input.type === 'select') {
|
||||
assert(Array.isArray(input.options) && input.options.length > 0);
|
||||
for (const option of input.options!) {
|
||||
assert(typeof option.value === 'string');
|
||||
assert(typeof option.label === 'string');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@ import browser from 'webextension-polyfill';
|
||||
import { BackgroundActiontype } from '../entries/Background/rpc';
|
||||
import { PluginConfig } from './misc';
|
||||
|
||||
export async function addPlugin(hex: string) {
|
||||
export async function addPlugin(hex: string, url: string) {
|
||||
return browser.runtime.sendMessage({
|
||||
type: BackgroundActiontype.add_plugin,
|
||||
data: hex,
|
||||
data: { hex, url },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ export const MAX_SENT_LS_KEY = 'max-sent';
|
||||
export const MAX_RECEIVED_LS_KEY = 'max-received';
|
||||
export const LOGGING_FILTER_KEY = 'logging-filter-2';
|
||||
export const RENDEZVOUS_API_LS_KEY = 'rendezvous-api';
|
||||
export const DEVELOPER_MODE_LS_KEY = 'developer-mode';
|
||||
|
||||
export async function set(key: string, value: string) {
|
||||
return chrome.storage.sync.set({ [key]: value });
|
||||
@@ -43,3 +44,8 @@ export async function getLoggingFilter(): Promise<LoggingLevel> {
|
||||
export async function getRendezvousApi(): Promise<string> {
|
||||
return await get(RENDEZVOUS_API_LS_KEY, RENDEZVOUS_API);
|
||||
}
|
||||
|
||||
export async function getDeveloperMode(): Promise<boolean> {
|
||||
const value = await get(DEVELOPER_MODE_LS_KEY, 'false');
|
||||
return value === 'true';
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { PresentationJSON as PresentationJSONa7 } from 'tlsn-js/build/types';
|
||||
|
||||
export type PresentationJSON = PresentationJSONa5 | PresentationJSONa7;
|
||||
|
||||
export type PresentationJSONa5 = {
|
||||
version?: undefined;
|
||||
session: any;
|
||||
substrings: any;
|
||||
notaryUrl: string;
|
||||
};
|
||||
@@ -22,6 +22,28 @@ config.plugins = (config.plugins || []).concat(
|
||||
}),
|
||||
);
|
||||
|
||||
webpack(config, function (err) {
|
||||
if (err) throw err;
|
||||
webpack(config, function (err, stats) {
|
||||
if (err) {
|
||||
console.error('Webpack error:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (stats.hasErrors()) {
|
||||
console.error('Build failed with errors:');
|
||||
const info = stats.toJson();
|
||||
console.error(info.errors.map((e) => e.message).join('\n\n'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (stats.hasWarnings()) {
|
||||
console.warn('Build completed with warnings:');
|
||||
const info = stats.toJson();
|
||||
console.warn(info.warnings.map((w) => w.message).join('\n\n'));
|
||||
}
|
||||
|
||||
console.log('Build completed successfully!');
|
||||
console.log(`Output: ${path.join(__dirname, '../', 'build')}`);
|
||||
console.log(
|
||||
`Zip: ${path.join(__dirname, '../', 'zip', `${packageInfo.name}-${packageInfo.version}.zip`)}`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -205,16 +205,6 @@ var options = {
|
||||
to: path.join(__dirname, "build"),
|
||||
force: true,
|
||||
},
|
||||
{
|
||||
from: "src/assets/plugins/discord_dm.wasm",
|
||||
to: path.join(__dirname, "build"),
|
||||
force: true,
|
||||
},
|
||||
{
|
||||
from: "src/assets/plugins/twitter_profile.wasm",
|
||||
to: path.join(__dirname, "build"),
|
||||
force: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
|
||||
Reference in New Issue
Block a user