Compare commits

...

10 Commits

Author SHA1 Message Date
Codetrauma
104457d2c6 feat: pass verifier and notary api urls to prover 2025-05-20 14:43:24 +02:00
Codetrauma
d20441b553 fix: wip 2025-05-20 11:10:17 +02:00
Codetrauma
f955ab7fd5 feat: making changes to runPlugins with the interactive verifier 2025-05-20 11:03:14 +02:00
Hendrik Eeckhaut
f34718f352 ci: Add missing checkout step in release job (#179) 2025-05-14 14:17:05 +02:00
tsukino
08520a90ff chore: update to alpha.10 (#177) 2025-04-30 16:18:51 +02:00
tsukino
264128d1fb feat: 1-click plugin (#175) 2025-04-30 03:01:54 -04:00
Hendrik Eeckhaut
dbb617f516 Update extism to v2.0.0-rc11 (#174) 2025-04-25 22:27:07 +02:00
Hendrik Eeckhaut
0ba4a71bba Added link to chrome web store (#173) 2025-04-16 17:22:26 +02:00
Tanner
ade6d7e575 Added Errors to Notarization Progress (#147)
* feat: added Error for RequestProgress

* feat: added logic to handle errors depending on progress

* chore: linting errors

* fix: linting build error

* Avoid magic numbers

* lint

* feat: alpha.8

* fix: package.json path

* chore: update lockfiles

* fix: notary url

* feat: notarization timeouts

* fix: notarization timeouts

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

* fix: notarization timeouts work properly now

* fix: timeout error message fix

* fix: lint

* fix: lint

* fix: errors should keep state after being thrown now

* chore: lint

* fix: fixing metadata with new progress status

---------

Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
Co-authored-by: tsukino <0xtsukino@gmail.com>
2025-04-15 10:00:57 -07:00
Hendrik Eeckhaut
3f37c1aee8 Update extism to v1.0.3 (#166) 2025-04-10 10:02:48 +02:00
32 changed files with 568 additions and 2032 deletions

View File

@@ -43,6 +43,8 @@ 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:

1
.gitignore vendored
View File

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

View File

@@ -25,7 +25,11 @@ at your option.
## Installing and Running
### Procedures:
The easiest way to install the TLSN browser extension is to use the [Chrome Web Store](https://chromewebstore.google.com/detail/tlsn-extension/gcfkkledipjbgdbimfpijgbkhajiaaph).
You can also build and run it locally as explained in the following steps.
### Procedure:
1. Check if your [Node.js](https://nodejs.org/) version is >= **18**.
2. Clone this repository.

318
package-lock.json generated
View File

@@ -1,15 +1,15 @@
{
"name": "tlsn-extension",
"version": "0.1.0.900",
"version": "0.1.0.1000",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tlsn-extension",
"version": "0.1.0.900",
"version": "0.1.0.1000",
"license": "MIT",
"dependencies": {
"@extism/extism": "^1.0.2",
"@extism/extism": "^2.0.0-rc11",
"@fortawesome/fontawesome-free": "^6.4.2",
"async-mutex": "^0.4.0",
"buffer": "^6.0.3",
@@ -33,8 +33,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.10.0"
},
"devDependencies": {
"@babel/core": "^7.20.12",
@@ -3266,9 +3265,9 @@
}
},
"node_modules/@extism/extism": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@extism/extism/-/extism-1.0.3.tgz",
"integrity": "sha512-kHwJwNP19c2q6tN2VYez/Bvv/mvx5J+UEyVbHJKAQ8+l9Z+MJ7TTjBw/GwDQcieXlLtPDNphugeH8ChKKfVtQg==",
"version": "2.0.0-rc11",
"resolved": "https://registry.npmjs.org/@extism/extism/-/extism-2.0.0-rc11.tgz",
"integrity": "sha512-+vjXOjQbCLpIAkMZjqkDIT4WUp2FA0mglj3a4nBpCp/gjDngiFqG6TmXP0esOubktY2EUklFiPCeaDr/sQXzKA==",
"license": "BSD-3-Clause"
},
"node_modules/@fortawesome/fontawesome-free": {
@@ -3558,27 +3557,6 @@
"@parcel/watcher-win32-x64": "2.5.1"
}
},
"node_modules/@parcel/watcher-android-arm64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-arm64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
@@ -3600,237 +3578,6 @@
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-x64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
"integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-freebsd-x64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
"integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-glibc": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
"integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
"integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
"integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
"integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
"integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-arm64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
"integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-ia32": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
"integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-x64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -3855,9 +3602,9 @@
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz",
"integrity": "sha512-LFWllMA55pzB9D34w/wXUCf8+c+IYKuJDgxiZ3qMhl64KRMBHYM1I3VdGaD2BV5FNPV2/S2596bppxHbv2ZydQ==",
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.16.tgz",
"integrity": "sha512-kLQc9xz6QIqd2oIYyXRUiAp79kGpFBm3fEM9ahfG1HI0WI5gdZ2OVHWdmZYnwODt7ISck+QuQ6sBPrtvUBML7Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8962,9 +8709,9 @@
}
},
"node_modules/html-entities": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.3.tgz",
"integrity": "sha512-D3AfvN7SjhTgBSA8L1BN4FpPzuEd06uy4lHwSoRWr0lndi9BKaNzPLKGOWZ2ocSGguozr08TTb2jhCLHaemruw==",
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
"integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
"dev": true,
"funding": [
{
@@ -11987,9 +11734,9 @@
}
},
"node_modules/postcss-load-config/node_modules/yaml": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
"integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
"integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
@@ -14566,34 +14313,21 @@
"license": "MIT"
},
"node_modules/tlsn-js": {
"version": "0.1.0-alpha.9",
"resolved": "https://registry.npmjs.org/tlsn-js/-/tlsn-js-0.1.0-alpha.9.tgz",
"integrity": "sha512-aEg/Pkdj0Oz9fB3xMUv67Lq69yLbuNS6IzA9j2lDwAmzOfgRBS7ZptcGuLz1hWoNvF1ma7JvdAJpHpL0ee8dkQ==",
"version": "0.1.0-alpha.10.0",
"resolved": "https://registry.npmjs.org/tlsn-js/-/tlsn-js-0.1.0-alpha.10.0.tgz",
"integrity": "sha512-+kwcT5AISESGmSI4sZ3rZ4VqOB/ogadTBisKB8yT8j8l5RqeI3xW+gZ+gF6ZE/Y4zEtXe3d6CbZFU11lEaAo0g==",
"license": "ISC",
"dependencies": {
"tlsn-wasm": "^0.1.0-alpha.9"
},
"engines": {
"node": ">= 16.20.2"
}
},
"node_modules/tlsn-js-v5": {
"name": "tlsn-js",
"version": "0.1.0-alpha.5.4",
"resolved": "https://registry.npmjs.org/tlsn-js/-/tlsn-js-0.1.0-alpha.5.4.tgz",
"integrity": "sha512-qbqaDjApXarohn/XMJrxMsNHYTCzy+pw0Fc8gtPPN17PLE+1DwwLTXAAhnxYqYQyo3+Hmy+ODd4C02+KCwRWmg==",
"license": "ISC",
"dependencies": {
"comlink": "^4.4.1"
"tlsn-wasm": "0.1.0-alpha.10"
},
"engines": {
"node": ">= 16.20.2"
}
},
"node_modules/tlsn-wasm": {
"version": "0.1.0-alpha.9",
"resolved": "https://registry.npmjs.org/tlsn-wasm/-/tlsn-wasm-0.1.0-alpha.9.tgz",
"integrity": "sha512-/7DKVXzFdlzD9vwsROb/tvGHJ+xHlAbvaVjMGBWOrecG5KR+Dcg6QMSb4R0/2jePX6u8r6JNXbRpKgQ+yf1zaA==",
"version": "0.1.0-alpha.10",
"resolved": "https://registry.npmjs.org/tlsn-wasm/-/tlsn-wasm-0.1.0-alpha.10.tgz",
"integrity": "sha512-HgGLmaxyw18v34hxAOnVc9P/HuEjVuQeb/6TcskaSHGFOY2t2pjWBz93toinEAD2N1LwVQJXoECxsP5Qo81Haw==",
"license": "MIT OR Apache-2.0"
},
"node_modules/tmp": {
@@ -14996,9 +14730,9 @@
}
},
"node_modules/use-sync-external-store": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"

View File

@@ -1,6 +1,6 @@
{
"name": "tlsn-extension",
"version": "0.1.0.900",
"version": "0.1.0.1000",
"license": "MIT",
"repository": {
"type": "git",
@@ -16,7 +16,7 @@
"lint:fix": "eslint . --fix"
},
"dependencies": {
"@extism/extism": "^1.0.2",
"@extism/extism": "^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.10.0"
},
"devDependencies": {
"@babel/core": "^7.20.12",

View File

@@ -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,34 @@ 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>
Verify
</MenuRow>
<MenuRow
fa="fa-solid fa-toolbox"
fa="fa-solid fa-network-wired"
className="border-b border-slate-300"
onClick={() => {
props.setOpen(false);
navigate('/plugins');
navigate('/p2p');
}}
>
Plugins
P2P
</MenuRow>
<MenuRow
className="lg:hidden"

View File

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

View File

@@ -23,7 +23,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';
@@ -110,7 +110,7 @@ export function Plugin({
if (hex) {
setConfig(await getPluginConfig(hexToArrayBuffer(hex)));
} else {
setConfig(await getPluginConfigByHash(hash));
setConfig(await getPluginConfigByUrl(hash));
}
})();
}, [hash, hex]);

View File

@@ -115,14 +115,15 @@ export async function setNotaryRequestError(
export async function setNotaryRequestProgress(
id: string,
progress: RequestProgress,
errorMessage?: string,
): Promise<RequestHistory | null> {
const existing = await historyDb.get(id);
if (!existing) return null;
const newReq: RequestHistory = {
...existing,
progress,
errorMessage,
};
await historyDb.put(id, newReq);
@@ -187,41 +188,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;
@@ -229,25 +233,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;
}
@@ -258,8 +262,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,
@@ -280,11 +284,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;
@@ -292,21 +296,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;
}

View File

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

View File

@@ -12,16 +12,11 @@ import {
setNotaryRequestVerification,
addPlugin,
getPluginHashes,
getPluginByHash,
getPluginByUrl,
removePlugin,
addPluginConfig,
getPluginConfigByHash,
getPluginConfigByUrl,
removePluginConfig,
getConnection,
setConnection,
deleteConnection,
addPluginMetadata,
getPlugins,
getCookiesByHost,
getHeadersByHost,
getAppState,
@@ -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';
@@ -95,20 +88,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',
@@ -167,9 +150,13 @@ export enum RequestProgress {
SendingRequest,
ReadingTranscript,
FinalizingOutputs,
Error,
}
export function progressText(progress: RequestProgress): string {
export function progressText(
progress: RequestProgress,
errorMessage?: string,
): string {
switch (progress) {
case RequestProgress.CreatingProver:
return 'Creating prover...';
@@ -183,6 +170,8 @@ export function progressText(progress: RequestProgress): string {
return 'Reading request transcript...';
case RequestProgress.FinalizingOutputs:
return 'Finalizing notarization outputs...';
case RequestProgress.Error:
return errorMessage ? errorMessage : 'Error: Notarization Failed';
}
}
@@ -210,6 +199,7 @@ export type RequestHistory = {
secretHeaders?: string[];
secretResps?: string[];
cid?: string;
errorMessage?: string;
metadata?: {
[k: string]: string;
};
@@ -263,20 +253,10 @@ 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;
@@ -430,9 +410,9 @@ async function handleUpdateRequestProgress(
request: BackgroundAction,
sendResponse: (data?: any) => void,
) {
const { id, progress } = request.data;
const { id, progress, errorMessage } = request.data;
const newReq = await setNotaryRequestProgress(id, progress);
const newReq = await setNotaryRequestProgress(id, progress, errorMessage);
if (!newReq) return;
await pushToRedux(addRequestHistory(await getNotaryRequest(id)));
@@ -553,69 +533,82 @@ async function runPluginProver(request: BackgroundAction, now = Date.now()) {
});
await setNotaryRequestStatus(id, 'pending');
await pushToRedux(addRequestHistory(await getNotaryRequest(id)));
const onProverResponse = async (request: any) => {
const { data, type } = request;
let listenerActive = true;
let responseListener: (request: any) => void;
if (type !== OffscreenActionTypes.create_prover_response) {
return;
}
const proverPromise = new Promise<void>((resolve, reject) => {
responseListener = async (request: any) => {
if (!listenerActive) return;
if (data.error) {
console.error(data.error);
return;
}
const { data, type } = request;
if (data.id !== id) {
return;
}
if (type !== OffscreenActionTypes.create_prover_response) {
return;
}
const transcript: { recv: number[]; sent: number[] } = data.transcript;
if (data.id !== id) {
return;
}
const { body: recvBody } = parseHttpMessage(
Buffer.from(transcript.recv),
'response',
);
try {
if (data.error) {
throw new Error(data.error);
}
if (getSecretResponse) {
secretResps = await getSecretResponseFn(
...recvBody.map((body) => body.toString('utf-8')),
);
}
const transcript: { recv: number[]; sent: number[] } = data.transcript;
const commit = {
sent: subtractRanges(
{ start: 0, end: transcript.sent.length },
mapStringToRange(
secretHeaders,
Buffer.from(transcript.sent).toString('utf-8'),
),
),
recv: subtractRanges(
{ start: 0, end: transcript.recv.length },
mapStringToRange(
secretResps,
Buffer.from(transcript.recv).toString('utf-8'),
),
),
const { body: recvBody } = parseHttpMessage(
Buffer.from(transcript.recv),
'response',
);
if (getSecretResponse) {
secretResps = await getSecretResponseFn(
...recvBody.map((body) => body.toString('utf-8')),
);
}
const commit = {
sent: subtractRanges(
{ start: 0, end: transcript.sent.length },
mapStringToRange(
secretHeaders,
Buffer.from(transcript.sent).toString('utf-8'),
),
),
recv: subtractRanges(
{ start: 0, end: transcript.recv.length },
mapStringToRange(
secretResps,
Buffer.from(transcript.recv).toString('utf-8'),
),
),
};
browser.runtime.sendMessage({
type: OffscreenActionTypes.create_presentation_request,
data: {
id,
commit,
notaryUrl,
websocketProxyUrl,
},
});
resolve();
} catch (error) {
console.error('Prover response error:', error);
reject(error);
} finally {
listenerActive = false;
browser.runtime.onMessage.removeListener(responseListener);
}
};
browser.runtime.sendMessage({
type: OffscreenActionTypes.create_presentation_request,
data: {
id,
commit,
notaryUrl,
websocketProxyUrl,
},
});
browser.runtime.onMessage.removeListener(onProverResponse);
};
browser.runtime.onMessage.addListener(onProverResponse);
browser.runtime.onMessage.addListener(responseListener);
});
browser.runtime.sendMessage({
type: OffscreenActionTypes.create_prover_request,
@@ -631,6 +624,34 @@ async function runPluginProver(request: BackgroundAction, now = Date.now()) {
maxSentData,
},
});
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
if (listenerActive) {
listenerActive = false;
browser.runtime.onMessage.removeListener(responseListener);
reject(new Error('Notarization Timed Out'));
}
// 3 minute timeout
}, 180000);
});
try {
await Promise.race([proverPromise, timeoutPromise]);
} catch (error: any) {
await setNotaryRequestStatus(id, 'error');
await setNotaryRequestError(id, error.message);
browser.runtime.sendMessage({
type: BackgroundActiontype.update_request_progress,
data: {
id,
progress: RequestProgress.Error,
error: error.message,
},
});
await pushToRedux(addRequestHistory(await getNotaryRequest(id)));
throw error;
}
}
async function handleGetSecretsFromTranscript(
@@ -638,7 +659,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);
@@ -653,7 +674,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: {
@@ -664,7 +685,7 @@ async function handleGetSecretsFromTranscript(
async function runP2PPluginProver(request: BackgroundAction, now = Date.now()) {
const {
pluginHash,
pluginUrl,
pluginHex,
url,
method,
@@ -686,7 +707,7 @@ async function runP2PPluginProver(request: BackgroundAction, now = Date.now()) {
await browser.runtime.sendMessage({
type: OffscreenActionTypes.start_p2p_prover,
data: {
pluginHash,
pluginUrl,
pluginHex,
url,
method,
@@ -776,10 +797,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);
@@ -819,7 +840,7 @@ async function handleGetPluginByHash(
sendResponse: (data?: any) => void,
) {
const hash = request.data;
const hex = await getPluginByHash(hash);
const hex = await getPluginByUrl(hash);
return hex;
}
@@ -828,7 +849,7 @@ async function handleGetPluginConfigByHash(
sendResponse: (data?: any) => void,
) {
const hash = request.data;
const config = await getPluginConfigByHash(hash);
const config = await getPluginConfigByUrl(hash);
return config;
}
@@ -838,14 +859,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;
@@ -900,180 +921,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,
@@ -1178,76 +1025,7 @@ async function handleNotarizeRequest(request: BackgroundAction) {
return defer.promise;
}
async function handleInstallPluginRequest(request: BackgroundAction) {
const [currentTab] = await browser.tabs.query({
active: true,
currentWindow: true,
});
const defer = deferredPromise();
const { origin, position, url, metadata } = 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) {
async function handleRunPluginByURLRequest(request: BackgroundAction) {
const [currentTab] = await browser.tabs.query({
active: true,
currentWindow: true,
@@ -1257,92 +1035,59 @@ async function handleGetPluginsRequest(request: BackgroundAction) {
const {
origin,
position,
origin: filterOrigin,
url: filterUrl,
metadata: filterMetadata,
url,
params,
skipConfirmation,
verifierApiUrl,
proxyApiUrl,
headers,
} = 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,
);
if (skipConfirmation) {
try {
browser.runtime.onMessage.addListener(async (req: any) => {
if (req.type !== SidePanelActionTypes.execute_plugin_response) return;
if (req.data.url !== url) return;
const onMessage = async (req: BackgroundAction) => {
if (req.type === BackgroundActiontype.get_plugins_response) {
if (req.data) {
const response = await getPlugins();
if (req.data.error) defer.reject(req.data.error);
if (req.data.proof) defer.resolve(req.data.proof);
});
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
);
});
const now = Date.now();
const id = charwise.encode(now).toString('hex');
defer.resolve(result);
} else {
defer.reject(new Error('user rejected.'));
}
await browser.runtime.sendMessage({
type: OffscreenActionTypes.create_prover_request,
data: {
id,
url,
params,
headers,
verifierApiUrl,
proxyApiUrl,
maxRecvData: await getMaxRecv(),
maxSentData: await getMaxSent(),
},
});
browser.runtime.onMessage.removeListener(onMessage);
browser.tabs.remove(tab.id!);
return defer.promise;
} catch (error) {
defer.reject(error);
return defer.promise;
}
};
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);
let isUserClose = true;
if (!plugin || !config) {
defer.reject(new Error('plugin not found.'));
return defer.promise;
}
let isUserClose = true;
const { popup, tab } = await openPopup(
`run-plugin-approval?hash=${hash}&origin=${encodeURIComponent(origin)}&favIconUrl=${encodeURIComponent(currentTab?.favIconUrl || '')}&params=${encodeURIComponent(JSON.stringify(params) || '')}`,
`run-plugin-approval?url=${url}&origin=${encodeURIComponent(origin)}&favIconUrl=${encodeURIComponent(currentTab?.favIconUrl || '')}&params=${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;
if (req.data.url !== url) return;
if (req.data.error) defer.reject(req.data.error);
if (req.data.proof) defer.resolve(req.data.proof);
@@ -1351,7 +1096,7 @@ async function handleRunPluginCSRequest(request: BackgroundAction) {
};
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 {

View File

@@ -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';
@@ -185,7 +185,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)),
@@ -336,7 +336,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 +424,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,

View File

@@ -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';
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,50 +36,39 @@ 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,
});
return resp;
}
async runPluginWithVerifier(
url: string,
verifierOptions: {
verifierApiUrl: string;
proxyApiUrl: string;
headers?: { [key: string]: string };
},
params?: Record<string, string>,
) {
const resp = await client.call(ContentScriptTypes.run_plugin_by_url, {
url,
params,
skipConfirmation: true, // Signal to skip confirmation window
verifierApiUrl: verifierOptions.verifierApiUrl,
proxyApiUrl: verifierOptions.proxyApiUrl,
headers: verifierOptions.headers,
});
return resp;
}
}
const connect = async () => {
const resp = await client.call(ContentScriptTypes.connect);
if (resp) {
return new TLSN();
}
return new TLSN();
};
// @ts-ignore

View File

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

View File

@@ -1,13 +1,17 @@
import { deferredPromise, PromiseResolvers } from '../../utils/promise';
export interface RunPluginByUrlData {
url: string;
params?: Record<string, string>;
skipConfirmation?: boolean;
verifierApiUrl?: string;
proxyApiUrl?: string;
headers?: { [key: string]: string };
}
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> = {

View File

@@ -19,8 +19,12 @@ 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 { waitForEvent } from '../utils';
import {
setNotaryRequestError,
setNotaryRequestStatus,
} from '../Background/db';
import { getNotaryApi, getProxyApi } from '../../utils/storage';
const { init, Prover, Presentation, Verifier }: any = Comlink.wrap(
new Worker(new URL('./worker.ts', import.meta.url)),
@@ -33,7 +37,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;
@@ -122,7 +129,6 @@ export const onCreatePresentationRequest = async (request: any) => {
reveal: commit,
})) as TPresentation;
const json = await presentation.json();
browser.runtime.sendMessage({
type: BackgroundActiontype.finish_prove_request,
data: {
@@ -246,7 +252,7 @@ export const startP2PVerifier = async (request: any) => {
export const startP2PProver = async (request: any) => {
const {
pluginHash,
pluginUrl,
pluginHex,
url,
method,
@@ -263,7 +269,7 @@ export const startP2PProver = async (request: any) => {
const hostname = urlify(url)?.hostname || '';
const prover: TProver = await new Prover({
id: pluginHash,
id: pluginUrl,
serverDns: hostname,
maxSentData,
maxRecvData,
@@ -272,7 +278,7 @@ export const startP2PProver = async (request: any) => {
browser.runtime.sendMessage({
type: BackgroundActiontype.prover_instantiated,
data: {
pluginHash,
pluginUrl,
},
});
@@ -285,7 +291,7 @@ export const startP2PProver = async (request: any) => {
browser.runtime.sendMessage({
type: BackgroundActiontype.prover_setup,
data: {
pluginHash,
pluginUrl,
},
});
@@ -293,7 +299,7 @@ export const startP2PProver = async (request: any) => {
browser.runtime.sendMessage({
type: BackgroundActiontype.prover_started,
data: {
pluginHash,
pluginUrl,
},
});
await proofRequestStart;
@@ -312,7 +318,7 @@ export const startP2PProver = async (request: any) => {
browser.runtime.sendMessage({
type: BackgroundActiontype.get_secrets_from_transcript,
data: {
pluginHash,
pluginUrl,
pluginHex,
method: getSecretResponse,
transcript,
@@ -435,7 +441,6 @@ async function createProof(options: {
})) as TPresentation;
const json = await presentation.json();
return {
...json,
meta: {
@@ -448,8 +453,9 @@ async function createProof(options: {
async function createProver(options: {
url: string;
notaryUrl: string;
websocketProxyUrl: string;
verifierApiUrl?: string;
proxyApiUrl?: string;
notaryUrl?: string;
method?: Method;
headers?: {
[name: string]: string;
@@ -466,37 +472,69 @@ async function createProver(options: {
body,
maxSentData,
maxRecvData,
notaryUrl,
websocketProxyUrl,
verifierApiUrl,
proxyApiUrl,
id,
} = options;
const hostname = urlify(url)?.hostname || '';
const notary = NotaryServer.from(notaryUrl);
updateRequestProgress(id, RequestProgress.CreatingProver);
const prover: TProver = await new Prover({
id,
serverDns: hostname,
maxSentData,
maxRecvData,
});
try {
const prover: TProver = await handleProgress(
id,
RequestProgress.CreatingProver,
() =>
new Prover({
id,
serverDns: hostname,
maxSentData,
maxRecvData,
}),
'Error creating prover',
);
updateRequestProgress(id, RequestProgress.GettingSession);
const sessionUrl = await notary.sessionUrl(maxSentData, maxRecvData);
const sessionUrl = await handleProgress(
id,
RequestProgress.GettingSession,
async () => {
if (verifierApiUrl) {
return verifierApiUrl;
} else {
const notary = NotaryServer.from(
options.notaryUrl || (await getNotaryApi()),
);
return notary.sessionUrl(maxSentData, maxRecvData);
}
},
'Error getting session from Verifier/Notary',
);
updateRequestProgress(id, RequestProgress.SettingUpProver);
await prover.setup(sessionUrl);
await handleProgress(
id,
RequestProgress.SettingUpProver,
() => prover.setup(sessionUrl),
'Error setting up prover',
);
updateRequestProgress(id, RequestProgress.SendingRequest);
await prover.sendRequest(websocketProxyUrl + `?token=${hostname}`, {
url,
method,
headers,
body,
});
const proxyUrl = proxyApiUrl || (await getProxyApi());
return prover;
await handleProgress(
id,
RequestProgress.SendingRequest,
() =>
prover.sendRequest(proxyUrl + `?token=${hostname}`, {
url,
method,
headers,
body,
}),
'Error sending request',
);
return prover;
} catch (error) {
throw error;
}
}
async function verifyProof(proof: PresentationJSON): Promise<{
@@ -513,45 +551,86 @@ async function verifyProof(proof: PresentationJSON): Promise<{
};
switch (proof.version) {
case undefined: {
result = await verify(proof);
break;
}
case undefined:
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(() => '');
result = {
sent: transcript.sent(),
recv: transcript.recv(),
verifierKey: verifyingKey,
notaryKey: publicKey,
sent: 'version not supported',
recv: 'version not supported',
};
break;
case '0.1.0-alpha.10':
result = await verify(proof);
break;
}
return result;
return result!;
}
function updateRequestProgress(id: string, progress: RequestProgress) {
devlog(`Request ${id}: ${progressText(progress)}`);
async function verify(proof: PresentationJSON) {
if (proof.version !== '0.1.0-alpha.10') {
throw new Error('wrong version');
}
const presentation: TPresentation = await new Presentation(proof.data);
const verifierOutput = await presentation.verify();
const transcript = new Transcript({
sent: verifierOutput.transcript.sent,
recv: verifierOutput.transcript.recv,
});
const vk = await presentation.verifyingKey();
const verifyingKey = Buffer.from(vk.data).toString('hex');
const notaryUrl = proof.meta.notaryUrl
? convertNotaryWsToHttp(proof.meta.notaryUrl)
: '';
const publicKey = await new NotaryServer(notaryUrl)
.publicKey()
.catch(() => '');
return {
sent: transcript.sent(),
recv: transcript.recv(),
verifierKey: verifyingKey,
notaryKey: publicKey,
};
}
function updateRequestProgress(
id: string,
progress: RequestProgress,
errorMessage?: string,
) {
const progressMessage =
progress === RequestProgress.Error
? `${errorMessage || 'Notarization Failed'}`
: progressText(progress);
devlog(`Request ${id}: ${progressMessage}`);
browser.runtime.sendMessage({
type: BackgroundActiontype.update_request_progress,
data: {
id,
progress: progress,
progress,
errorMessage,
},
});
}
async function handleProgress<T>(
id: string,
progress: RequestProgress,
action: () => Promise<T>,
errorMessage: string,
): Promise<T> {
try {
updateRequestProgress(id, progress);
return await action();
} catch (error: any) {
updateRequestProgress(id, RequestProgress.Error, errorMessage);
await setNotaryRequestStatus(id, 'error');
await setNotaryRequestError(
id,
errorMessage || error.message || 'Unknown error',
);
throw error;
}
}

View File

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

View File

@@ -13,14 +13,18 @@ import logo from '../../assets/img/icon-128.png';
import classNames from 'classnames';
import Icon from '../../components/Icon';
import { useRequestHistory } from '../../reducers/history';
import { BackgroundActiontype, progressText } from '../Background/rpc';
import { getPluginByHash, getPluginConfigByHash } from '../Background/db';
import {
BackgroundActiontype,
progressText,
RequestProgress,
} from '../Background/rpc';
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>();
@@ -40,8 +44,8 @@ export default function SidePanel(): ReactElement {
switch (type) {
case SidePanelActionTypes.execute_plugin_request: {
setConfig(await getPluginConfigByHash(data.pluginHash));
setHash(data.pluginHash);
setConfig(await getPluginConfigByUrl(data.pluginUrl));
setUrl(data.pluginUrl);
setParams(data.pluginParams);
setStarted(true);
break;
@@ -49,10 +53,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);
@@ -67,7 +71,7 @@ export default function SidePanel(): ReactElement {
}
case SidePanelActionTypes.reset_panel: {
setConfig(null);
setHash('');
setUrl('');
setHex('');
setStarted(false);
break;
@@ -90,7 +94,7 @@ export default function SidePanel(): ReactElement {
{/*{!config && <PluginList />}*/}
{started && config && (
<PluginBody
hash={hash}
url={url}
hex={hex}
config={config}
p2p={p2p}
@@ -102,15 +106,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('');
@@ -125,7 +135,7 @@ function PluginBody(props: {
setNotarizationId(response);
}
},
[hash, responses],
[url, responses],
);
useEffect(() => {
@@ -133,7 +143,7 @@ function PluginBody(props: {
browser.runtime.sendMessage({
type: SidePanelActionTypes.execute_plugin_response,
data: {
hash,
url,
proof: notaryRequest.proof,
},
});
@@ -141,12 +151,12 @@ function PluginBody(props: {
browser.runtime.sendMessage({
type: SidePanelActionTypes.execute_plugin_response,
data: {
hash,
url,
error: notaryRequest.error,
},
});
}
}, [hash, notaryRequest?.status]);
}, [url, notaryRequest?.status]);
return (
<div className="flex flex-col p-4">
@@ -163,7 +173,7 @@ function PluginBody(props: {
{steps?.map((step, i) => (
<StepContent
key={i}
hash={hash}
url={url}
config={config}
hex={hex}
index={i}
@@ -183,7 +193,7 @@ function PluginBody(props: {
function StepContent(
props: StepConfig & {
hash: string;
url: string;
hex?: string;
clientId?: string;
index: number;
@@ -204,7 +214,7 @@ function StepContent(
setResponse,
lastResponse,
prover,
hash,
url,
hex: _hex,
config,
p2p = false,
@@ -218,10 +228,10 @@ function StepContent(
const notaryRequest = useRequestHistory(notarizationId);
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();
@@ -238,7 +248,6 @@ function StepContent(
? JSON.stringify(lastResponse)
: JSON.stringify(parameterValues),
);
console.log(out);
const val = JSON.parse(out.string());
if (val && prover) {
setNotarizationId(val);
@@ -338,17 +347,27 @@ function StepContent(
);
} else if (notaryRequest?.status === 'pending' || pending || notarizationId) {
btnContent = (
<button className="button mt-2 w-fit flex flex-row flex-nowrap items-center gap-2 cursor-default">
<Icon className="animate-spin" fa="fa-solid fa-spinner" size={1} />
<span className="text-sm">
{notaryRequest?.progress
? `(${(
((notaryRequest.progress + 1) / 6.06) *
100
).toFixed()}%) ${progressText(notaryRequest.progress)}`
: 'Pending...'}
</span>
</button>
<div className="flex flex-col gap-2">
{notaryRequest?.progress === RequestProgress.Error && (
<div className="flex flex-row items-center gap-2 text-red-600">
<Icon fa="fa-solid fa-triangle-exclamation" size={1} />
<span className="text-sm">
{notaryRequest?.errorMessage ||
progressText(notaryRequest.progress)}
</span>
</div>
)}
{notaryRequest?.progress !== RequestProgress.Error && (
<button className="button mt-2 w-fit flex flex-row flex-nowrap items-center gap-2 cursor-default">
<Icon className="animate-spin" fa="fa-solid fa-spinner" size={1} />
<span className="text-sm">
{notaryRequest?.progress !== undefined
? `(${(((notaryRequest.progress + 1) / 6.06) * 100).toFixed()}%) ${progressText(notaryRequest.progress)}`
: 'Pending...'}
</span>
</button>
)}
</div>
);
} else {
btnContent = (

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,23 +1,20 @@
import React, { ReactElement, useState, useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router';
import {
useHistoryOrder,
useRequestHistory,
deleteRequestHistory,
} from '../../reducers/history';
import { useHistoryOrder, useRequestHistory } from '../../reducers/history';
import Icon from '../../components/Icon';
import NotarizeIcon from '../../assets/img/notarize.png';
import { getNotaryApi, getProxyApi } from '../../utils/storage';
import { urlify } from '../../utils/misc';
import {
BackgroundActiontype,
progressText,
RequestProgress,
} from '../../entries/Background/rpc';
import Modal, { ModalContent } from '../../components/Modal/Modal';
import classNames from 'classnames';
import dayjs from 'dayjs';
import RequestMenu from './request-menu';
const charwise = require('charwise');
export default function History(): ReactElement {
@@ -110,12 +107,14 @@ export function OneRequestHistory(props: {
size={1}
/>
<span className="">
{request?.progress
? `(${(
((request.progress + 1) / 6.06) *
100
).toFixed()}%) ${progressText(request.progress)}`
: 'Pending...'}
{request?.progress === RequestProgress.Error
? `${progressText(request.progress, request.errorMessage)}`
: request?.progress
? `(${(
((request.progress + 1) / 6.06) *
100
).toFixed()}%) ${progressText(request.progress)}`
: 'Pending...'}
</span>
</div>
)}
@@ -155,7 +154,7 @@ export function OneRequestHistory(props: {
onClose={closeAllModal}
>
<ModalContent className="flex justify-center items-center text-slate-500">
{msg || 'Something went wrong :('}
{msg || request?.errorMessage}
</ModalContent>
<button
className="m-0 w-24 bg-red-100 text-red-300 hover:bg-red-200 hover:text-red-500"

View File

@@ -1,28 +1,16 @@
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';
export default function Home(props: {
tab?: 'history' | 'network';
@@ -31,9 +19,6 @@ export default function Home(props: {
const [tab, setTab] = useState<'history' | 'network'>(props.tab || 'history');
const scrollableContent = useRef<HTMLDivElement | null>(null);
const [shouldFix, setFix] = useState(false);
const [actionPanelElement, setActionPanelElement] =
useState<HTMLDivElement | null>(null);
const [scrollTop, setScrollTop] = useState(0);
useEffect(() => {
fetchPluginHashes();
@@ -42,19 +27,12 @@ export default function Home(props: {
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 +41,7 @@ export default function Home(props: {
return () => {
element.removeEventListener('scroll', onScroll);
};
}, [scrollableContent, actionPanelElement]);
}, [scrollableContent]);
return (
<div
@@ -72,10 +50,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',
@@ -106,158 +80,6 @@ export default function Home(props: {
);
}
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 +102,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>
);
}

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import {
BackgroundActiontype,
RequestHistory,
RequestProgress,
} from '../entries/Background/rpc';
import { useSelector } from 'react-redux';
import { AppRootState } from './index';
@@ -76,6 +77,9 @@ export default function history(
if (!payload) return state;
const existing = state.map[payload.id];
if (existing?.progress === RequestProgress.Error) {
return state;
}
const newMap = {
...state.map,
[payload.id]: payload,
@@ -90,13 +94,20 @@ export default function history(
}
case ActionType['/history/setRequests']: {
const payload: RequestHistory[] = action.payload;
const newMap = payload.reduce(
(map: { [id: string]: RequestHistory }, req) => {
if (state.map[req.id]?.progress === RequestProgress.Error) {
map[req.id] = state.map[req.id];
} else {
map[req.id] = req;
}
return map;
},
{},
);
return {
...state,
map: payload.reduce((map: { [id: string]: RequestHistory }, req) => {
map[req.id] = req;
return map;
}, {}),
map: newMap,
order: payload.map(({ id }) => id),
};
}

View File

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

View File

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

View File

@@ -225,11 +225,12 @@ export const makePlugin = async (
if (meta?.p2p) {
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,
@@ -246,7 +247,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);
});
},

View File

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