Skip to content

Commit

Permalink
feat: implement plugin system and add demo plugin for twitter profile (
Browse files Browse the repository at this point in the history
…#59)

* wip: testing extism host function

* wip

* wip

* wip: adding ui for plugin

* feat: add cache for headers and cookies by host

* feat: add plugin stores

* feat: add a plugin and render plugin list

* feat: add plugin config db

* feat: add basic plugin steps ui

* feat: add completion status to steps ui

* fix: refactor twitter profile plugin

* fix: refactor steps execution

* fix: testing plugin

* Added README to plugins folder

* feat: fix twitter profile plugin

* improved README: document how to run the twitter plugin example

* remove ddos

* WIP: Reddit plugin

* fix: steps circular reference

* feat: open popup

* Reddit plugin (result too big)

* feat: add view proof

* chore: add plugins to eslint ignore

* fix: twitter plugin

* fix: remove logs

* feat: add permission for approved request, notary, and proxy url in plugin

---------

Co-authored-by: Hendrik Eeckhaut <[email protected]>
  • Loading branch information
0xtsukino and heeckhau authored May 17, 2024
1 parent 217824f commit 53ba6f6
Show file tree
Hide file tree
Showing 41 changed files with 1,666 additions and 236 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"wasm",
"tlsn",
"util",
"plugins",
"webpack.config.js"
]
}
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"lint:fix": "eslint . --fix"
},
"dependencies": {
"@extism/extism": "^1.0.2",
"@fortawesome/fontawesome-free": "^6.4.2",
"async-mutex": "^0.4.0",
"buffer": "^6.0.3",
Expand Down
48 changes: 48 additions & 0 deletions plugins/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Plugin Development for the TLSNotary Browser Extension

This folder is dedicated to the development of plugins for the TLSNotary browser extension, utilizing the Extism framework. Currently, the folder includes a TypeScript-based plugin example, `twitter_profile`, with plans to add more plugins showcasing different programming languages and functionalities.

## Installation of Extism-js

1. **Download and Install Extism-js**: Begin by setting up `extism-js`, which enables you to compile and manage your plugins. Run these commands to download and install it:

```sh
curl -O https://raw.githubusercontent.com/extism/js-pdk/main/install.sh
sh install.sh
```

This script installs the Extism JavaScript Plugin Development Kit from its GitHub repository, preparing your environment for plugin compilation.

## Building the Twitter Profile Plugin

Navigate to the `twitter_profile` directory within this folder and run the following command to build the plugin:

```sh
extism-js index.js -i index.d.ts -o index.wasm
```
This command compiles the TypeScript code in index.js into a WebAssembly module, ready for integration with the TLSNotary extension.

### Running the Twitter Plugin Example:

1. Build the `twitter_profile` plugin as explained above.
2. Build and install the `tlsn-extension` as documented in the [main README.md](../README.md).
3. [Run a local notary server](https://github.com/tlsnotary/tlsn/blob/main/notary-server/README.md), ensuring `TLS` is disabled in the [config file](https://github.com/tlsnotary/tlsn/blob/main/notary-server/config/config.yaml#L18).
4. Install the plugin: Click the **Add a Plugin (+)** button and select the `index.wasm` file you built in step 1. A **Twitter Profile** button should then appear below the default buttons.
5. Click the **Twitter Profile** button. This action opens the Twitter webpage along with a TLSNotary sidebar.
6. Follow the steps in the TLSNotary sidebar.
7. Access the TLSNotary results by clicking the **History** button in the TLSNotary extension.

## Future Plugins

This directory will be expanded with more plugins designed to demonstrate the functionality of the TLSNotary extension. Plugins enable flexible use of the TLSNotary across a broad range of applications. The use of Extism facilitates plugin development in various languages, further enhancing flexibility.

## Create an icon

1. resize to 320x320 pixels:
```sh
convert icon.png -resize 320x320! icon_320.png
```
2. convert to base64
```sh
base64 -i icon_320.png -o icon_320.txt
```
4 changes: 4 additions & 0 deletions plugins/hello/hello.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module 'main' {
// Extism exports take no params and return an I32
export function hello(): I32;
}
6 changes: 6 additions & 0 deletions plugins/hello/hello.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function hello() {
const name = Host.inputString();
Host.outputString(`Hello, ${name}`);
}

module.exports = { hello };
Binary file added plugins/hello/hello.wasm
Binary file not shown.
14 changes: 14 additions & 0 deletions plugins/reddit/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
declare module 'main' {
// Extism exports take no params and return an I32
export function start(): I32;
export function two(): I32;
export function three(): I32;
export function config(): I32;
}

declare module 'extism:host' {
interface user {
redirect(ptr: I64): void;
notarize(ptr: I64): I64;
}
}
94 changes: 94 additions & 0 deletions plugins/reddit/index.js

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions plugins/twitter_profile/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
declare module 'main' {
// Extism exports take no params and return an I32
export function start(): I32;
export function two(): I32;
export function three(): I32;
export function config(): I32;
}

declare module 'extism:host' {
interface user {
redirect(ptr: I64): void;
notarize(ptr: I64): I64;
}
}
105 changes: 105 additions & 0 deletions plugins/twitter_profile/index.js

Large diffs are not rendered by default.

Binary file added plugins/twitter_profile/index.wasm
Binary file not shown.
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added src/assets/img/default-plugin-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions src/components/ErrorModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, { ReactElement } from 'react';
import Modal, { ModalContent } from '../Modal/Modal';

export function ErrorModal(props: {
onClose: () => void;
message: string;
}): ReactElement {
const { onClose, message } = props;

return (
<Modal
className="flex flex-col gap-4 items-center text-base cursor-default justify-center !w-auto mx-4 my-[50%] min-h-24 p-4 border border-red-500 !bg-red-100"
onClose={onClose}
>
<ModalContent className="flex justify-center items-center text-red-500">
{message || 'Something went wrong :('}
</ModalContent>
<button
className="m-0 w-24 bg-red-200 text-red-400 hover:bg-red-200 hover:text-red-500"
onClick={onClose}
>
OK
</button>
</Modal>
);
}
23 changes: 23 additions & 0 deletions src/components/PluginList/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.plugin-box {
&__remove-icon {
opacity: 0;
height: 0;
width: 0;
padding: 0;
overflow: hidden;
transition: 200ms opacity;
}

&:hover {
.plugin-box__remove-icon {
height: 1.25rem;
width: 1.25rem;
padding: .5rem;
opacity: .5;

&:hover {
opacity: 1;
}
}
}
}
113 changes: 113 additions & 0 deletions src/components/PluginList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React, {
ChangeEvent,

Check warning on line 2 in src/components/PluginList/index.tsx

View workflow job for this annotation

GitHub Actions / build

'ChangeEvent' is defined but never used
MouseEventHandler,
ReactElement,
useCallback,
useEffect,
useState,
} from 'react';
import {
fetchPluginHashes,
removePlugin,
fetchPluginConfigByHash,
runPlugin,
} from '../../utils/rpc';
import { usePluginHashes } from '../../reducers/plugins';
import { PluginConfig } from '../../utils/misc';
import DefaultPluginIcon from '../../assets/img/default-plugin-icon.png';
import classNames from 'classnames';
import Icon from '../Icon';
import './index.scss';
import browser from 'webextension-polyfill';
import { ErrorModal } from '../ErrorModal';

export function PluginList(props: { className?: string }): ReactElement {
const hashes = usePluginHashes();

useEffect(() => {
fetchPluginHashes();
}, []);

return (
<div className={classNames('flex flex-col flex-nowrap', props.className)}>
{!hashes.length && (
<div className="flex flex-col items-center justify-center text-slate-400 cursor-default select-none">
<div>No available plugins</div>
</div>
)}
{hashes.map((hash) => (
<Plugin key={hash} hash={hash} />
))}
</div>
);
}

export function Plugin(props: {
hash: string;
onClick?: () => void;
}): ReactElement {
const [error, showError] = useState('');
const [config, setConfig] = useState<PluginConfig | null>(null);

const onClick = useCallback(async () => {
if (!config) return;

try {
await runPlugin(props.hash, 'start');

const [tab] = await browser.tabs.query({
active: true,
currentWindow: true,
});

await browser.storage.local.set({ plugin_hash: props.hash });

// @ts-ignore
if (chrome.sidePanel) await chrome.sidePanel.open({ tabId: tab.id });

window.close();
} catch (e: any) {

Check warning on line 69 in src/components/PluginList/index.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
showError(e.message);
}
}, [props.hash, config]);

useEffect(() => {
(async function () {
setConfig(await fetchPluginConfigByHash(props.hash));
})();
}, [props.hash]);

const onRemove: MouseEventHandler = useCallback(
(e) => {
e.stopPropagation();
removePlugin(props.hash);
},
[props.hash],
);

if (!config) return <></>;

return (
<button
className={classNames(
'flex flex-row border rounded border-slate-300 p-2 gap-2 plugin-box',
'cursor-pointer hover:bg-slate-100 hover:border-slate-400 active:bg-slate-200',
)}
onClick={onClick}
>
{!!error && <ErrorModal onClose={() => showError('')} message={error} />}
<img className="w-12 h-12" src={config.icon || DefaultPluginIcon} />
<div className="flex flex-col w-full items-start">
<div className="font-bold flex flex-row h-6 items-center justify-between w-full">
{config.title}
<Icon
fa="fa-solid fa-xmark"
className="flex flex-row items-center justify-center cursor-pointer text-red-500 bg-red-200 rounded-full plugin-box__remove-icon"
onClick={onRemove}
/>
</div>
<div>{config.description}</div>
</div>
</button>
);
}
1 change: 0 additions & 1 deletion src/components/RequestDetail/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export default function RequestDetail(props: Props): ReactElement {
const notarize = useCallback(async () => {
if (!request) return;

console.log('/notary/' + props.requestId);
navigate('/notary/' + request.requestId);
}, [request, props.requestId]);

Expand Down
Loading

0 comments on commit 53ba6f6

Please sign in to comment.