mirror of
https://github.com/tlsnotary/tlsn-plugin-demo.git
synced 2026-01-06 20:14:01 -05:00
Interactive verifier demo (#35)
* Add interactive verifier demo * Serve and load plugin from local file * Show screenname at the end + removed attestation code * No more need for tlsn-js * Updated Docker container * Use environment variable to enable/disable POAPs * Improved UI * use LRU for tracking sessions Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
This commit is contained in:
33
.dockerignore
Normal file
33
.dockerignore
Normal file
@@ -0,0 +1,33 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
|
||||
# Build outputs
|
||||
build/
|
||||
|
||||
# Development files
|
||||
.git/
|
||||
.gitignore
|
||||
.github/
|
||||
|
||||
README.md
|
||||
.env.local
|
||||
.env.development
|
||||
*.md
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Docker files
|
||||
Dockerfile*
|
||||
docker-compose*.yml
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
3
.github/scripts/build.sh
vendored
3
.github/scripts/build.sh
vendored
@@ -6,7 +6,8 @@ aws s3 cp s3://tlsn-plugin-demo/firebase-admin.json server/util/
|
||||
|
||||
aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin 490752553772.dkr.ecr.eu-central-1.amazonaws.com
|
||||
|
||||
docker build -t tlsn-plugin-demo .
|
||||
docker build --build-arg ENABLE_POAP=true --build-arg VERIFIER_URL="http://demo.tlsnotary.org:7047" -t tlsn-demo .
|
||||
|
||||
docker tag tlsn-plugin-demo:latest 490752553772.dkr.ecr.eu-central-1.amazonaws.com/tlsn-plugin-demo:latest
|
||||
docker push 490752553772.dkr.ecr.eu-central-1.amazonaws.com/tlsn-plugin-demo:latest
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,5 +4,4 @@
|
||||
.idea
|
||||
build
|
||||
.env
|
||||
rs/**/target
|
||||
firebase-admin.json
|
||||
|
||||
107
Dockerfile
107
Dockerfile
@@ -1,16 +1,103 @@
|
||||
FROM node:latest
|
||||
###########################################
|
||||
# Build Stage 1: Build the Verifier
|
||||
###########################################
|
||||
FROM ubuntu:24.04 as verifier-builder
|
||||
|
||||
ARG VERIFIER_URL="http://localhost:7047"
|
||||
|
||||
# Install build dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
curl \
|
||||
git \
|
||||
bash \
|
||||
ca-certificates \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
build-essential \
|
||||
npm \
|
||||
sudo \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Rust and Cargo
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y
|
||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||
|
||||
# Install Node.js 18.x
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
|
||||
apt-get install -y nodejs
|
||||
|
||||
# Install xtp and Extism
|
||||
RUN curl https://static.dylibso.com/cli/install.sh -s | bash
|
||||
RUN curl https://raw.githubusercontent.com/extism/js-pdk/main/install.sh -s | bash
|
||||
ENV PATH="/root/.xtp/bin:$PATH"
|
||||
|
||||
# Clone and build TLSNotary (interactive-verifier branch)
|
||||
WORKDIR /
|
||||
RUN git clone --branch interactive-verifier --single-branch https://github.com/tlsnotary/tlsn.git
|
||||
|
||||
# Build the Verifier plugin
|
||||
WORKDIR /tlsn/crates/notary/plugin/js
|
||||
RUN chmod +x prepare.sh && ./prepare.sh && xtp plugin build
|
||||
|
||||
# Build the Verifier server
|
||||
WORKDIR /tlsn/crates/notary/server
|
||||
RUN cargo build --release
|
||||
|
||||
# Build the prover plugin
|
||||
WORKDIR /
|
||||
RUN git clone --branch interactive-verifier --single-branch https://github.com/tlsnotary/tlsn-plugin-boilerplate.git
|
||||
WORKDIR /tlsn-plugin-boilerplate
|
||||
# replace "http://localhost:7047" with $VERIFIER_URL in config.json and src/index.ts
|
||||
RUN sed -i "s|http://localhost:7047|$VERIFIER_URL|g" config.json src/index.ts
|
||||
RUN npm ci; npm run build
|
||||
|
||||
###########################################
|
||||
# Build stage 2: Build the demo application
|
||||
###########################################
|
||||
FROM node:18-slim AS demo-builder
|
||||
|
||||
ARG ENABLE_POAP=false
|
||||
|
||||
ENV POAP=${ENABLE_POAP}
|
||||
|
||||
ARG PORT=3030
|
||||
WORKDIR /app
|
||||
ENV PATH="${PATH}:/root/.cargo/bin"
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
|
||||
RUN apt-get update && apt-get install -y curl && apt-get install netcat-openbsd -y
|
||||
RUN curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
RUN npm install
|
||||
RUN npm i --prefix rs/0.1.0-alpha.12/
|
||||
COPY --from=verifier-builder /tlsn-plugin-boilerplate/dist/*.tlsn.wasm ./static
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE ${PORT}
|
||||
CMD ["node", "build/server/index.bundle.js"]
|
||||
###########################################
|
||||
# Build Stage 3: Runtime
|
||||
###########################################
|
||||
FROM node:18-slim AS production
|
||||
|
||||
ARG ENABLE_POAP=false
|
||||
ENV POAP=${ENABLE_POAP}
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
RUN addgroup --gid 1001 nodejs && \
|
||||
useradd --system --uid 1001 --gid 1001 --no-create-home nodejs
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production && npm cache clean --force
|
||||
COPY --from=demo-builder --chown=nodejs:nodejs /app/build ./build
|
||||
|
||||
WORKDIR /verifier
|
||||
COPY --from=verifier-builder /tlsn/target/release/notary-server ./verifier
|
||||
COPY --from=verifier-builder /tlsn/crates/notary/plugin/wasm/*.wasm /plugin/wasm/
|
||||
|
||||
# Install PM2 for process management
|
||||
RUN npm install -g pm2
|
||||
COPY ecosystem.config.js /ecosystem.config.js
|
||||
|
||||
EXPOSE 3030 7047
|
||||
|
||||
ENV VERIFIER_SECRET_KEY="TMBak7!!66ku$kRQ"
|
||||
|
||||
CMD ["pm2-runtime", "/ecosystem.config.js"]
|
||||
19
README.md
19
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
Welcome to the **TLSNotary Plugin Demo**! This demo showcases how TLSNotary can be used to verify private user data in web applications.
|
||||
|
||||
In this demo, you'll prove that you own a Twitter account to the web server. The website will verify your attestation and, as a reward, you'll receive a POAP (Proof of Attendance Protocol) token — while stocks last!
|
||||
In this demo, you'll prove that you own a Twitter account to the web server. The website will verify your proof and, as a reward, you'll receive a POAP (Proof of Attendance Protocol) token — while stocks last!
|
||||
|
||||
---
|
||||
|
||||
@@ -25,8 +25,15 @@ This demo works by leveraging the [Provider API]((https://github.com/tlsnotary/t
|
||||
|
||||
## Installing and Running
|
||||
|
||||
1. Clone this repository
|
||||
2. Run `npm install`
|
||||
3. Insert a `poaps.txt` of POAP mint links in `server/util`
|
||||
4. Run `npm run dev`
|
||||
5. Enjoy the demo
|
||||
1. Build:
|
||||
```sh
|
||||
docker build -t tlsn-demo .
|
||||
```
|
||||
* To build with POAPS enabled, add `--build-arg ENABLE_POAP=true`
|
||||
* To set the verifier url, add `--build-arg VERIFIER_URL="http://demo.tlsnotary.org:7047"`
|
||||
E.g. `docker build --build-arg ENABLE_POAP=true -t tlsn-demo .`
|
||||
2. Run:
|
||||
```sh
|
||||
docker run --rm -p 3030:3030 -p 7047:7047 -it tlsn-demo
|
||||
```
|
||||
3. Visit <http://localhost:3030>
|
||||
15
ecosystem.config.js
Normal file
15
ecosystem.config.js
Normal file
@@ -0,0 +1,15 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: "verifier",
|
||||
cwd: "/verifier",
|
||||
script: "./verifier"
|
||||
},
|
||||
{
|
||||
name: "demo",
|
||||
cwd: "/app",
|
||||
script: "node",
|
||||
args: "build/server/index.bundle.js"
|
||||
}
|
||||
]
|
||||
};
|
||||
1879
package-lock.json
generated
1879
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
39
package.json
39
package.json
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "tlsn-content",
|
||||
"name": "tlsn-plugin-demo",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"main": "",
|
||||
@@ -24,55 +24,43 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.13.3",
|
||||
"@emotion/styled": "^11.13.0",
|
||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||
"@mui/material": "^6.1.2",
|
||||
"async-mutex": "^0.5.0",
|
||||
"buffer": "^6.0.3",
|
||||
"charwise": "^3.0.1",
|
||||
"classnames": "^2.5.1",
|
||||
"comlink": "^4.4.1",
|
||||
"concurrently": "^9.0.1",
|
||||
"crypto-browserify": "^3.12.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.21.1",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"firebase-admin": "^13.1.0",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"node-loader": "^2.0.0",
|
||||
"nodemon": "^3.1.7",
|
||||
"react": "^18.2.0",
|
||||
"lru-cache": "^11.1.0",
|
||||
"react": "^18.3.1",
|
||||
"react-confetti-explosion": "^2.1.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "^8.1.2",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router": "^6.15.0",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"redux": "^4.2.1",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-thunk": "^2.4.2",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"tlsn-js": "^0.1.0-alpha.12.0"
|
||||
"stream-browserify": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/preset-env": "^7.25.4",
|
||||
"@babel/preset-react": "^7.24.7",
|
||||
"@mui/material": "^6.1.2",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
|
||||
"@types/chrome": "^0.0.273",
|
||||
"@tailwindcss/postcss": "^4.0.0-alpha.29",
|
||||
"@types/node": "^22.7.4",
|
||||
"@types/react": "^18.3.10",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/redux-logger": "^3.0.13",
|
||||
"@types/webextension-polyfill": "^0.12.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^9.2.1",
|
||||
"babel-preset-react-app": "^10.0.1",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"compression-webpack-plugin": "^11.1.0",
|
||||
"concurrently": "^9.0.1",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"css-loader": "^7.1.2",
|
||||
"eslint": "8.1.0",
|
||||
@@ -89,19 +77,18 @@
|
||||
"html-loader": "^4.2.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"image-webpack-loader": "^8.1.0",
|
||||
"node-loader": "^2.0.0",
|
||||
"nodemon": "^3.1.7",
|
||||
"postcss-loader": "^7.3.3",
|
||||
"postcss-preset-env": "^9.1.1",
|
||||
"prettier": "^3.3.3",
|
||||
"react-refresh": "^0.14.2",
|
||||
"sass": "^1.80.6",
|
||||
"sass-loader": "^16.0.3",
|
||||
"source-map-loader": "^3.0.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"terser-webpack-plugin": "^5.3.6",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"ts-loader": "^9.4.2",
|
||||
"type-fest": "^3.5.2",
|
||||
"typescript": "^4.9.4",
|
||||
"wasm-loader": "^1.3.0",
|
||||
"typescript": "^5.8.3",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.11.1"
|
||||
|
||||
1299
rs/0.1.0-alpha.12/Cargo.lock
generated
1299
rs/0.1.0-alpha.12/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,22 +0,0 @@
|
||||
[package]
|
||||
name = "verifier"
|
||||
version = "0.1.0"
|
||||
license = "ISC"
|
||||
edition = "2021"
|
||||
exclude = ["index.node"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
neon = "1"
|
||||
elliptic-curve = { version = "0.13.5", features = ["pkcs8"] }
|
||||
k256 = { version = "0.13", features = ["pem", "ecdsa"] }
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
hex = "0.4"
|
||||
bincode = { version = "1.3" }
|
||||
tlsn-core = { git = "https://github.com/tlsnotary/tlsn.git", tag = "v0.1.0-alpha.12", package = "tlsn-core" }
|
||||
@@ -1,119 +0,0 @@
|
||||
# verifier
|
||||
|
||||
This project was bootstrapped by [create-neon](https://www.npmjs.com/package/create-neon).
|
||||
|
||||
## Installing verifier
|
||||
|
||||
Installing verifier requires a [supported version of Node and Rust](https://github.com/neon-bindings/neon#platform-support).
|
||||
|
||||
You can install the project with npm. In the project directory, run:
|
||||
|
||||
```sh
|
||||
$ npm install
|
||||
```
|
||||
|
||||
This fully installs the project, including installing any dependencies and running the build.
|
||||
|
||||
## Building verifier
|
||||
|
||||
If you have already installed the project and only want to run the build, run:
|
||||
|
||||
```sh
|
||||
$ npm run build
|
||||
```
|
||||
|
||||
This command uses the [cargo-cp-artifact](https://github.com/neon-bindings/cargo-cp-artifact) utility to run the Rust build and copy the built library into `./index.node`.
|
||||
|
||||
## Exploring verifier
|
||||
|
||||
After building verifier, you can explore its exports at the Node REPL:
|
||||
|
||||
```sh
|
||||
$ npm install
|
||||
$ node
|
||||
> require('.').hello()
|
||||
"hello node"
|
||||
```
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm install`
|
||||
|
||||
Installs the project, including running `npm run build`.
|
||||
|
||||
### `npm build`
|
||||
|
||||
Builds the Node addon (`index.node`) from source.
|
||||
|
||||
Additional [`cargo build`](https://doc.rust-lang.org/cargo/commands/cargo-build.html) arguments may be passed to `npm build` and `npm build-*` commands. For example, to enable a [cargo feature](https://doc.rust-lang.org/cargo/reference/features.html):
|
||||
|
||||
```
|
||||
npm run build -- --feature=beetle
|
||||
```
|
||||
|
||||
#### `npm build-debug`
|
||||
|
||||
Alias for `npm build`.
|
||||
|
||||
#### `npm build-release`
|
||||
|
||||
Same as [`npm build`](#npm-build) but, builds the module with the [`release`](https://doc.rust-lang.org/cargo/reference/profiles.html#release) profile. Release builds will compile slower, but run faster.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Runs the unit tests by calling `cargo test`. You can learn more about [adding tests to your Rust code](https://doc.rust-lang.org/book/ch11-01-writing-tests.html) from the [Rust book](https://doc.rust-lang.org/book/).
|
||||
|
||||
## Project Layout
|
||||
|
||||
The directory structure of this project is:
|
||||
|
||||
```
|
||||
verifier/
|
||||
├── Cargo.toml
|
||||
├── README.md
|
||||
├── index.node
|
||||
├── package.json
|
||||
├── src/
|
||||
| └── lib.rs
|
||||
└── target/
|
||||
```
|
||||
|
||||
### Cargo.toml
|
||||
|
||||
The Cargo [manifest file](https://doc.rust-lang.org/cargo/reference/manifest.html), which informs the `cargo` command.
|
||||
|
||||
### README.md
|
||||
|
||||
This file.
|
||||
|
||||
### index.node
|
||||
|
||||
The Node addon—i.e., a binary Node module—generated by building the project. This is the main module for this package, as dictated by the `"main"` key in `package.json`.
|
||||
|
||||
Under the hood, a [Node addon](https://nodejs.org/api/addons.html) is a [dynamically-linked shared object](https://en.wikipedia.org/wiki/Library_(computing)#Shared_libraries). The `"build"` script produces this file by copying it from within the `target/` directory, which is where the Rust build produces the shared object.
|
||||
|
||||
### package.json
|
||||
|
||||
The npm [manifest file](https://docs.npmjs.com/cli/v7/configuring-npm/package-json), which informs the `npm` command.
|
||||
|
||||
### src/
|
||||
|
||||
The directory tree containing the Rust source code for the project.
|
||||
|
||||
### src/lib.rs
|
||||
|
||||
The Rust library's main module.
|
||||
|
||||
### target/
|
||||
|
||||
Binary artifacts generated by the Rust build.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Neon, see the [Neon documentation](https://neon-bindings.com).
|
||||
|
||||
To learn more about Rust, see the [Rust documentation](https://www.rust-lang.org).
|
||||
|
||||
To learn more about Node, see the [Node documentation](https://nodejs.org).
|
||||
Binary file not shown.
27
rs/0.1.0-alpha.12/package-lock.json
generated
27
rs/0.1.0-alpha.12/package-lock.json
generated
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"name": "verifier",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "verifier",
|
||||
"version": "0.1.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"cargo-cp-artifact": "^0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/cargo-cp-artifact": {
|
||||
"version": "0.1.9",
|
||||
"resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.9.tgz",
|
||||
"integrity": "sha512-6F+UYzTaGB+awsTXg0uSJA1/b/B3DDJzpKVRu0UmyI7DmNeaAl2RFHuTGIN6fEgpadRxoXGb7gbC1xo4C3IdyA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"cargo-cp-artifact": "bin/cargo-cp-artifact.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "verifier",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"main": "index.node",
|
||||
"scripts": {
|
||||
"build": "cargo-cp-artifact -nc index.node -- cargo build --message-format=json-render-diagnostics",
|
||||
"build-debug": "npm run build --",
|
||||
"build-release": "npm run build -- --release",
|
||||
"install": "npm run build-release",
|
||||
"test": "cargo test"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"cargo-cp-artifact": "^0.1"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,159 +0,0 @@
|
||||
use k256::pkcs8::DecodePublicKey;
|
||||
use k256::pkcs8::EncodePublicKey;
|
||||
use neon::prelude::*;
|
||||
use tlsn_core::{
|
||||
presentation::{Presentation, PresentationOutput},
|
||||
signing::VerifyingKey,
|
||||
CryptoProvider,
|
||||
};
|
||||
|
||||
fn verify(mut cx: FunctionContext) -> JsResult<JsObject> {
|
||||
let presentation_cx = cx.argument::<JsString>(0)?.value(&mut cx);
|
||||
let notary_key_cx = cx.argument::<JsString>(1)?.value(&mut cx);
|
||||
|
||||
let (sent, recv, time) =
|
||||
verify_presentation(presentation_cx, notary_key_cx).or_else(|e| cx.throw_error(e))?;
|
||||
|
||||
let obj: Handle<JsObject> = cx.empty_object();
|
||||
let sent_str = cx.string(sent);
|
||||
obj.set(&mut cx, "sent", sent_str)?;
|
||||
let recv_str = cx.string(recv);
|
||||
obj.set(&mut cx, "recv", recv_str)?;
|
||||
let session_time = cx.number(time as f64);
|
||||
obj.set(&mut cx, "time", session_time)?;
|
||||
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
fn verify_presentation(
|
||||
presentation_cx: String,
|
||||
notary_key_cx: String,
|
||||
) -> Result<(String, String, u64), String> {
|
||||
let bytes: Vec<u8> = hex::decode(presentation_cx.as_str()).map_err(|e| e.to_string())?;
|
||||
let presentation: Presentation = bincode::deserialize(&bytes).map_err(|e| e.to_string())?;
|
||||
|
||||
let VerifyingKey {
|
||||
alg: _,
|
||||
data: key_data,
|
||||
} = presentation.verifying_key();
|
||||
|
||||
let notary_key = k256::PublicKey::from_public_key_pem(notary_key_cx.as_str())
|
||||
.map_err(|x| format!("Invalid notary key: {}", x))?;
|
||||
let verifying_key = k256::PublicKey::from_sec1_bytes(key_data)
|
||||
.map_err(|x| format!("Invalid verifying key: {}", x))?;
|
||||
|
||||
if notary_key != verifying_key {
|
||||
Err("The verifying key does not match the notary key")?;
|
||||
}
|
||||
|
||||
let provider = CryptoProvider::default();
|
||||
let PresentationOutput {
|
||||
connection_info,
|
||||
transcript,
|
||||
..
|
||||
} = presentation
|
||||
.verify(&provider)
|
||||
.map_err(|x| format!("Presentation verification failed: {}", x))?;
|
||||
|
||||
let (sent, recv) = transcript
|
||||
.map(|mut partial_transcript| {
|
||||
partial_transcript.set_unauthed(b'X');
|
||||
let sent = String::from_utf8_lossy(partial_transcript.sent_unsafe()).to_string();
|
||||
let recv = String::from_utf8_lossy(partial_transcript.received_unsafe()).to_string();
|
||||
(sent, recv)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok((sent, recv, connection_info.time))
|
||||
}
|
||||
|
||||
#[neon::main]
|
||||
fn main(mut cx: ModuleContext) -> NeonResult<()> {
|
||||
cx.export_function("verify", verify)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Presentation {
|
||||
version: String,
|
||||
data: String,
|
||||
}
|
||||
|
||||
fn example_presentation() -> String {
|
||||
let example = include_str!("example.json");
|
||||
let presentation: Presentation = serde_json::from_str(&example).unwrap();
|
||||
assert_eq!("0.1.0-alpha.12", presentation.version);
|
||||
presentation.data
|
||||
}
|
||||
|
||||
fn extract_verifying_key_as_pem(presentation_data: String) -> Result<String, String> {
|
||||
let bytes: Vec<u8> = hex::decode(presentation_data.as_str()).map_err(|e| e.to_string())?;
|
||||
let presentation: tlsn_core::presentation::Presentation = bincode::deserialize(&bytes).map_err(|e| e.to_string())?;
|
||||
|
||||
let VerifyingKey {
|
||||
alg: _,
|
||||
data: key_data,
|
||||
} = presentation.verifying_key();
|
||||
|
||||
let verifying_key = k256::PublicKey::from_sec1_bytes(&key_data)
|
||||
.map_err(|x| format!("Invalid verifying key: {}", x))?;
|
||||
|
||||
let pem = verifying_key.to_public_key_pem(k256::pkcs8::LineEnding::LF)
|
||||
.map_err(|x| format!("Failed to convert to PEM: {}", x))?;
|
||||
|
||||
Ok(pem)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_with_correct_key() {
|
||||
let correct_key = extract_verifying_key_as_pem(example_presentation())
|
||||
.expect("Failed to extract verifying key from presentation");
|
||||
|
||||
let (sent, recv, time) =
|
||||
verify_presentation(example_presentation(), correct_key).unwrap();
|
||||
|
||||
assert_eq!(1748415894, time);
|
||||
assert!(sent.contains("host: raw.githubusercontent.com"));
|
||||
assert!(sent.contains("XXXXXXXXXXXXXXXXXX"));
|
||||
assert!(recv.contains("HTTP/1.1 200 OK"));
|
||||
assert!(recv.contains("Content-Type: text/plain"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_with_your_current_key() {
|
||||
let your_notary_key = String::from(
|
||||
"-----BEGIN PUBLIC KEY-----
|
||||
MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgACWq2qrz9HJbTB32D4WowdXQfnCaBS5eas
|
||||
rPwHd4svpUo=
|
||||
-----END PUBLIC KEY-----"
|
||||
);
|
||||
|
||||
let res = verify_presentation(example_presentation(), your_notary_key);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
Err("The verifying key does not match the notary key".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_wrong_key() {
|
||||
let invalid_key = String::from(
|
||||
"-----BEGIN PUBLIC KEY-----
|
||||
MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgABm3AS+GGr3gEwbDOWNJTR7oWF/xJ6LBf+
|
||||
z9KxqnGiW9o=
|
||||
-----END PUBLIC KEY-----"
|
||||
);
|
||||
|
||||
let res = verify_presentation(example_presentation(), invalid_key);
|
||||
|
||||
assert!(res.is_err());
|
||||
let error_msg = res.unwrap_err();
|
||||
assert!(error_msg.contains("Invalid notary key") || error_msg.contains("does not match"));
|
||||
}
|
||||
}
|
||||
178
server/index.tsx
178
server/index.tsx
@@ -1,15 +1,11 @@
|
||||
import express from 'express';
|
||||
import React from 'react';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import { StaticRouter } from 'react-router-dom/server';
|
||||
import App from '../web/pages/App';
|
||||
import configureAppStore, { AppRootState } from '../web/store';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Mutex } from 'async-mutex';
|
||||
//@ts-ignore
|
||||
import { verify } from '../rs/0.1.0-alpha.12/index.node';
|
||||
import { convertNotaryWsToHttp, fetchPublicKeyFromNotary } from './util/index';
|
||||
import { assignPoapToUser } from './util/index';
|
||||
import { LRUCache } from 'lru-cache'
|
||||
|
||||
const app = express();
|
||||
const port = 3030;
|
||||
@@ -38,90 +34,73 @@ app.use(express.static('build/ui'));
|
||||
app.use(express.json());
|
||||
const mutex = new Mutex();
|
||||
|
||||
app.get('*', (req, res) => {
|
||||
try {
|
||||
const storeConfig: AppRootState = {
|
||||
attestation: {
|
||||
raw: {
|
||||
version: '0.1.0-alpha.12',
|
||||
data: '',
|
||||
meta: {
|
||||
notaryUrl: '',
|
||||
websocketProxyUrl: '',
|
||||
pluginUrl: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const sessions = new LRUCache<string, string>({
|
||||
max: 10000,
|
||||
ttl: 1000 * 60 * 60, // 60 minutes
|
||||
});
|
||||
|
||||
const store = configureAppStore(storeConfig);
|
||||
|
||||
const html = renderToString(
|
||||
<Provider store={store}>
|
||||
<StaticRouter location={req.url}>
|
||||
<App />
|
||||
</StaticRouter>
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
const preloadedState = store.getState();
|
||||
|
||||
res.send(`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<title>TLSN Plugin Demo</title>
|
||||
<script>
|
||||
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState)};
|
||||
</script>
|
||||
<script defer src="/index.bundle.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">${html}</div>
|
||||
<div id="modal-root"></div>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
} catch (e) {
|
||||
res.send(`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<title>TLSN Plugin Demo</title>
|
||||
<script>
|
||||
window.__PRELOADED_STATE__ = {};
|
||||
</script>
|
||||
<script defer src="/index.bundle.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div id="modal-root"></div>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
app.post('/update-session', async (req, res) => {
|
||||
if (
|
||||
req.headers['x-verifier-secret-key'] !== process.env.VERIFIER_SECRET_KEY
|
||||
) {
|
||||
return res.status(403).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
console.log('Update session:', req.body);
|
||||
|
||||
const { screen_name, session_id } = req.body;
|
||||
|
||||
if (!screen_name || !session_id) {
|
||||
return res.status(400).json({ error: 'Missing screen_name or session_id' });
|
||||
}
|
||||
|
||||
sessions.set(session_id, screen_name);
|
||||
console.log('Updated session:', session_id, screen_name);
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
app.post('/check-session', async (req, res) => {
|
||||
const { session_id } = req.body;
|
||||
if (!session_id) {
|
||||
return res.status(400).json({ error: 'Missing session_id' });
|
||||
}
|
||||
let screen_name: string | undefined;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
screen_name = sessions.get(session_id);
|
||||
if (screen_name) break;
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
console.log('Check session:', session_id, screen_name);
|
||||
if (!screen_name) {
|
||||
return res.status(404).json({ error: 'Session not found' });
|
||||
}
|
||||
res.json({ screen_name });
|
||||
});
|
||||
|
||||
app.post('/poap-claim', async (req, res) => {
|
||||
const { screenName } = req.body;
|
||||
if (!screenName) {
|
||||
return res.status(400).json({ error: 'Missing screen_name' });
|
||||
const { sessionId } = req.body;
|
||||
const sn = sessions.get(sessionId);
|
||||
|
||||
console.log('Checking Poap claim:', sessionId, sn);
|
||||
|
||||
if (!sn) {
|
||||
return res.status(400).json({ error: 'Invalid screen_name or sessionId' });
|
||||
}
|
||||
|
||||
try {
|
||||
await mutex.runExclusive(async () => {
|
||||
const poapLink = await assignPoapToUser(screenName);
|
||||
if (process.env.POAP !== 'true') {
|
||||
return res.status(404).json({ error: 'No POAPs available in development mode' });
|
||||
}
|
||||
|
||||
const poapLink = await assignPoapToUser(sn);
|
||||
|
||||
if (!poapLink) {
|
||||
return res.status(404).json({ error: 'No POAPs available' });
|
||||
}
|
||||
|
||||
console.log('Serving POAP link:', poapLink);
|
||||
|
||||
return res.json({ poapLink });
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -130,26 +109,41 @@ app.post('/poap-claim', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/verify-attestation', async (req, res) => {
|
||||
const { attestation } = req.body;
|
||||
if (!attestation) {
|
||||
return res.status(400).send('Missing attestation');
|
||||
}
|
||||
function renderHTML(html: string = '', preloadedState: any = {}) {
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<title>TLSN Plugin Demo</title>
|
||||
<script>
|
||||
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState)};
|
||||
</script>
|
||||
<script defer src="/index.bundle.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">${html}</div>
|
||||
<div id="modal-root"></div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
app.get('*', (req, res) => {
|
||||
try {
|
||||
const notaryUrl = attestation.meta.notaryUrl;
|
||||
const notaryPem = await fetchPublicKeyFromNotary(notaryUrl);
|
||||
const html = renderToString(
|
||||
<StaticRouter location={req.url}>
|
||||
<App />
|
||||
</StaticRouter>
|
||||
);
|
||||
|
||||
const presentation = await verify(attestation.data, notaryPem);
|
||||
|
||||
const presentationObj = {
|
||||
sent: presentation.sent,
|
||||
recv: presentation.recv,
|
||||
};
|
||||
res.json({ presentationObj });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.status(500).send('Error verifying attestation');
|
||||
res.send(renderHTML(html));
|
||||
} catch (error) {
|
||||
console.error('SSR Error:', error);
|
||||
// Send client-only version on SSR error
|
||||
res.send(renderHTML());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -2,35 +2,27 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import admin from 'firebase-admin';
|
||||
|
||||
const serviceAccount = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, 'util/firebase-admin.json'), 'utf8'),
|
||||
);
|
||||
let serviceAccount: any;
|
||||
|
||||
admin.initializeApp({
|
||||
credential: admin.credential.cert(serviceAccount),
|
||||
});
|
||||
const db = admin.firestore();
|
||||
let db: any;
|
||||
|
||||
export function convertNotaryWsToHttp(notaryWs: string) {
|
||||
const { protocol, pathname, hostname, port } = new URL(notaryWs);
|
||||
const p = protocol === 'wss:' ? 'https:' : 'http:';
|
||||
const pt = port ? `:${port}` : '';
|
||||
const path = pathname === '/' ? '' : pathname.replace('/notarize', '');
|
||||
const h = hostname === 'localhost' ? '127.0.0.1' : hostname;
|
||||
return p + '//' + h + pt + path;
|
||||
}
|
||||
if (process.env.POAP !== 'true') {
|
||||
console.log(`POAP feature is disabled`);
|
||||
db = {};
|
||||
} else {
|
||||
console.log(`POAP feature is enabled`);
|
||||
|
||||
// Use the real config in production
|
||||
serviceAccount = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, 'util/firebase-admin.json'), 'utf8')
|
||||
);
|
||||
|
||||
admin.initializeApp({
|
||||
credential: admin.credential.cert(serviceAccount),
|
||||
});
|
||||
|
||||
db = admin.firestore();
|
||||
|
||||
export async function fetchPublicKeyFromNotary(notaryUrl: string) {
|
||||
try {
|
||||
const url = new URL(notaryUrl);
|
||||
const res = await fetch(notaryUrl + '/info');
|
||||
const json: any = await res.json();
|
||||
if (!json.publicKey) throw new Error('invalid response');
|
||||
return json.publicKey;
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch public key from notary', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const getUserPoap = async (
|
||||
|
||||
458
static/overview_prover_verifier.svg
Normal file
458
static/overview_prover_verifier.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 43 KiB |
BIN
static/twitter_profile.tlsn.wasm
Normal file
BIN
static/twitter_profile.tlsn.wasm
Normal file
Binary file not shown.
@@ -13,7 +13,7 @@
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"noEmit": false,
|
||||
"jsx": "react"
|
||||
"jsx": "react-jsx",
|
||||
},
|
||||
"include": [
|
||||
"web"
|
||||
|
||||
@@ -37,7 +37,10 @@ export default function Button(props: Props): ReactElement {
|
||||
{...btnProps}
|
||||
>
|
||||
{loading ? (
|
||||
<Icon className="animate-spin" fa="fa-solid fa-spinner" size={2} />
|
||||
<>
|
||||
<span>Running TLSNotary plugin...</span>
|
||||
<div className="animate-spin rounded-full h-5 w-5 border-2 border-white border-t-transparent"></div>
|
||||
</>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
|
||||
@@ -5,10 +5,9 @@ import Step from '@mui/material/Step';
|
||||
import Box from '@mui/material/Box';
|
||||
import StepLabel from '@mui/material/StepLabel';
|
||||
import classNames from 'classnames';
|
||||
import type { PresentationJSON } from 'tlsn-js/build/types';
|
||||
import Button from '../Button';
|
||||
import ConfettiExplosion, { ConfettiProps } from 'react-confetti-explosion';
|
||||
import { formatDataPreview } from '../../utils/utils';
|
||||
import OverviewSvg from '../../../static/overview_prover_verifier.svg';
|
||||
|
||||
const steps = ['Connect Extension', 'Run Plugin'];
|
||||
|
||||
@@ -16,9 +15,8 @@ export default function Steps(): ReactElement {
|
||||
const [extensionInstalled, setExtensionInstalled] = useState(false);
|
||||
const [step, setStep] = useState<number>(0);
|
||||
const [client, setClient] = useState<any>(null);
|
||||
const [pluginData, setPluginData] = useState<PresentationJSON | null>(null);
|
||||
const [sessionId, setSessionId] = useState<string>('');
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [transcript, setTranscript] = useState<any>(null);
|
||||
const [screenName, setScreenName] = useState<string>('');
|
||||
const [exploding, setExploding] = useState<boolean>(false);
|
||||
|
||||
@@ -53,17 +51,6 @@ export default function Steps(): ReactElement {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (transcript) {
|
||||
const match = transcript.recv.match(/"screen_name":"([^"]+)"/);
|
||||
const screenName = match ? match[1] : null;
|
||||
setScreenName(screenName);
|
||||
if (screenName) {
|
||||
setExploding(true);
|
||||
}
|
||||
}
|
||||
}, [transcript]);
|
||||
|
||||
async function handleConnect() {
|
||||
try {
|
||||
//@ts-ignore
|
||||
@@ -77,21 +64,24 @@ export default function Steps(): ReactElement {
|
||||
async function handleRunPlugin() {
|
||||
try {
|
||||
setLoading(true);
|
||||
const pluginData = await client.runPlugin(
|
||||
'https://raw.githubusercontent.com/tlsnotary/tlsn-extension/cc3264f058ad2ebb0791830a1217fdd8bffd543f/src/assets/plugins/twitter_profile.wasm',
|
||||
const _sessionId = await client.runPlugin(
|
||||
window.location.origin + '/twitter_profile.tlsn.wasm',
|
||||
);
|
||||
setPluginData(pluginData);
|
||||
console.log(pluginData);
|
||||
const response = await fetch('/verify-attestation', {
|
||||
setSessionId(_sessionId);
|
||||
console.log('Session ID:', _sessionId);
|
||||
|
||||
const response = await fetch('/check-session', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ attestation: pluginData }),
|
||||
body: JSON.stringify({ session_id: _sessionId }),
|
||||
});
|
||||
console.log('Check session response:', response);
|
||||
if (response.status === 200) {
|
||||
const data = await response.json();
|
||||
setTranscript(data.presentationObj);
|
||||
console.log('Response: Plugin data:', data);
|
||||
setScreenName(data.screen_name);
|
||||
setStep(1);
|
||||
} else {
|
||||
console.log(await response.text());
|
||||
@@ -108,7 +98,7 @@ export default function Steps(): ReactElement {
|
||||
{extensionInstalled ? (
|
||||
<>
|
||||
<div className="flex flex-row items-center gap-2 text-slate-600 font-bold pb-2">
|
||||
Connected{' '}
|
||||
Extension Connected{' '}
|
||||
<div
|
||||
className={classNames(
|
||||
'rounded-full h-[10px] w-[10px] border-[2px]',
|
||||
@@ -119,61 +109,197 @@ export default function Steps(): ReactElement {
|
||||
)}
|
||||
></div>
|
||||
</div>
|
||||
<Box className="w-full max-w-3xl mt-6 pb-4">
|
||||
<Stepper activeStep={step} alternativeLabel>
|
||||
{steps.map((label) => (
|
||||
<Step key={label}>
|
||||
<StepLabel>{label}</StepLabel>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
</Box>
|
||||
<div className="flex gap-3">
|
||||
{step === 0 && (
|
||||
<button onClick={handleConnect} className="button">
|
||||
Connect
|
||||
</button>
|
||||
)}
|
||||
{step === 1 && !pluginData && (
|
||||
<div className="flex flex-col items-center justify-center gap-2">
|
||||
<ul className="flex flex-col items-center justify-center gap-1">
|
||||
<li className="text-base font-light">
|
||||
This will open a new tab to Twitter/X and the sidebar for
|
||||
the extension
|
||||
</li>
|
||||
<li className="text-base font-light">
|
||||
Click through the steps in the sidebar
|
||||
</li>
|
||||
<li className="text-base font-light">
|
||||
Don't close the sidebar or refresh the page until
|
||||
notarization is finished
|
||||
</li>
|
||||
<li className="text-base font-light">
|
||||
If successful, the attestation and verified data will be
|
||||
displayed below
|
||||
</li>
|
||||
</ul>
|
||||
<Button onClick={handleRunPlugin} loading={loading}>
|
||||
Run Plugin
|
||||
</Button>
|
||||
{step === 1 && !sessionId && (
|
||||
<div className="flex flex-col items-center justify-center gap-6 max-w-4xl">
|
||||
<div className="text-center space-y-4 w-full flex flex-col items-center">
|
||||
<h2 className="text-2xl font-semibold text-gray-900">
|
||||
Ready to Prove Your Twitter Identity
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 max-w-2xl">
|
||||
Click the button below to start the verification process
|
||||
</p>
|
||||
|
||||
<Button
|
||||
onClick={handleRunPlugin}
|
||||
loading={loading}
|
||||
className="bg-blue-600 hover:bg-blue-700 !text-white px-12 py-4 text-xl font-semibold min-w-[300px] shadow-lg rounded-lg transition-all duration-200"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-2 border-white border-t-transparent"></div>
|
||||
<span>Processing...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
🔐 Prove Twitter Screen Name
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-6 w-full">
|
||||
|
||||
<h3 className="text-lg font-semibold text-blue-900 mb-4 text-center">
|
||||
What happens when you click "Prove Twitter Screen Name"?
|
||||
</h3>
|
||||
|
||||
<div className="space-y-4 text-gray-700">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm font-semibold flex-shrink-0 mt-0.5">
|
||||
1
|
||||
</div>
|
||||
<p className="text-base leading-relaxed">
|
||||
The TLSNotary extension will open a popup, asking permission to run the plugin and send the unredacted data (just the screen name) to the verifier server.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm font-semibold flex-shrink-0 mt-0.5">
|
||||
2
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-base leading-relaxed mb-2">
|
||||
If you accept, the extension will open X/Twitter in a new tab with a sidebar showing these steps:
|
||||
</p>
|
||||
<div className="ml-4 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-blue-300 rounded-full"></div>
|
||||
<span className="text-sm text-gray-600">Go to your Twitter profile</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-blue-300 rounded-full"></div>
|
||||
<span className="text-sm text-gray-600">Log in if you haven't yet</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-blue-300 rounded-full"></div>
|
||||
<span className="text-sm text-gray-600">The extension proves your Twitter handle to the verifier server</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-base leading-relaxed mb-2">
|
||||
Click on the buttons in the sidebar to proceed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm font-semibold flex-shrink-0 mt-0.5">
|
||||
3
|
||||
</div>
|
||||
<p className="text-base leading-relaxed">
|
||||
{process.env.POAP === 'true' ? (
|
||||
<>
|
||||
If successful, your screen name will be shown here and you can claim a POAP.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
If successful, your screen name will be shown here.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 p-3 bg-yellow-50 border border-yellow-200 rounded">
|
||||
<p className="text-sm text-yellow-800">
|
||||
💡 <strong>Tip:</strong> When step 3 is running, you can close the Twitter/X tab, but don't close the sidebar.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{step === 1 && pluginData && screenName && (
|
||||
<div className="flex flex-col items-center justify-center gap-2">
|
||||
<h3 className="text-lg font-semibold text-center">
|
||||
Optional: Claim Your POAP
|
||||
</h3>
|
||||
<ClaimPoap screen_name={screenName} exploding={exploding} />
|
||||
{step === 1 && sessionId && screenName && (
|
||||
<div className="flex flex-col items-center justify-center gap-6 max-w-4xl w-full">
|
||||
|
||||
{/* Success Header with Animation */}
|
||||
<div className="text-center space-y-4 animate-fade-in">
|
||||
<div className="bg-green-100 border-2 border-green-300 rounded-full w-20 h-20 flex items-center justify-center mx-auto mb-4 animate-bounce">
|
||||
<span className="text-4xl">✅</span>
|
||||
</div>
|
||||
|
||||
<h2 className="text-3xl font-bold text-green-800 mb-2">
|
||||
🎉 Verification Successful! 🎉
|
||||
</h2>
|
||||
|
||||
<div className="bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-200 rounded-xl p-6 shadow-lg">
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">
|
||||
Successfully verified your Twitter identity
|
||||
</h3>
|
||||
<div className="bg-white rounded-lg p-4 border border-green-200">
|
||||
<p className="text-lg text-gray-700">
|
||||
Screen name: <span className="font-bold text-green-700 text-xl">@{screenName}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* POAP Section */}
|
||||
{process.env.POAP === 'true' && (
|
||||
<div className="bg-gradient-to-r from-yellow-50 to-amber-50 border-2 border-yellow-300 rounded-xl p-8 w-full shadow-lg">
|
||||
<div className="text-center space-y-4">
|
||||
<div className="bg-yellow-100 border-2 border-yellow-300 rounded-full w-16 h-16 flex items-center justify-center mx-auto">
|
||||
<span className="text-3xl">🎁</span>
|
||||
</div>
|
||||
|
||||
<h3 className="text-2xl font-bold text-yellow-800">
|
||||
Claim Your Reward!
|
||||
</h3>
|
||||
|
||||
<p className="text-lg text-yellow-700 max-w-2xl mx-auto">
|
||||
You've successfully proven your Twitter identity! Now claim your exclusive POAP token as proof of this achievement.
|
||||
</p>
|
||||
|
||||
<div className="pt-4">
|
||||
<ClaimPoap sessionId={sessionId} exploding={exploding} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* What's Next Section */}
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-xl p-6 w-full">
|
||||
<h4 className="text-lg font-semibold text-blue-900 mb-3 text-center">
|
||||
What just happened?
|
||||
</h4>
|
||||
<div className="grid md:grid-cols-2 gap-4 text-sm text-blue-800">
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="text-blue-600">🔒</span>
|
||||
<div>
|
||||
<p className="font-semibold">Privacy Preserved</p>
|
||||
<p>Your sensitive data stayed private - only your screen name was verified</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="text-blue-600">🛡️</span>
|
||||
<div>
|
||||
<p className="font-semibold">Cryptographic Proof</p>
|
||||
<p>TLSNotary created a verifiable proof without exposing your credentials to the verifier</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Try Again Button */}
|
||||
<div className="text-center">
|
||||
<button
|
||||
onClick={() => {
|
||||
setSessionId('');
|
||||
setScreenName('');
|
||||
setStep(1);
|
||||
}}
|
||||
className="bg-gray-500 hover:bg-gray-600 text-white px-8 py-3 rounded-lg font-semibold transition-colors"
|
||||
>
|
||||
🔄 Try Again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{pluginData && (
|
||||
<DisplayPluginData
|
||||
step={step}
|
||||
pluginData={pluginData}
|
||||
transcript={transcript}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<InstallExtensionPrompt />
|
||||
@@ -182,66 +308,12 @@ export default function Steps(): ReactElement {
|
||||
);
|
||||
}
|
||||
|
||||
function DisplayPluginData({
|
||||
step,
|
||||
pluginData,
|
||||
transcript,
|
||||
}: {
|
||||
step: number;
|
||||
pluginData: any;
|
||||
transcript: any;
|
||||
}): ReactElement {
|
||||
const [tab, setTab] = useState<'sent' | 'recv'>('sent');
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center space-x-4 mt-8">
|
||||
<div className="w-96">
|
||||
<div className="p-2 bg-gray-200 border-t rounded-t-md text-center text-lg font-semibold">
|
||||
Attestation
|
||||
</div>
|
||||
<div className="p-4 bg-gray-100 border rounded-b-md h-96 text-left overflow-auto">
|
||||
<pre className="text-sm text-gray-700 whitespace-pre-wrap text-[12px]">
|
||||
{formatDataPreview(pluginData)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-96">
|
||||
<div className="p-2 bg-gray-200 border-t rounded-t-md text-center text-lg font-semibold">
|
||||
Presentation
|
||||
</div>
|
||||
<div className="bg-gray-100 border rounded-b-md h-96 overflow-auto">
|
||||
<div className="flex border-b">
|
||||
<button
|
||||
onClick={() => setTab('sent')}
|
||||
className={`p-2 w-1/2 text-center ${tab === 'sent' ? 'bg-slate-500 text-white' : 'bg-white text-black'}`}
|
||||
>
|
||||
Sent
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setTab('recv')}
|
||||
className={`p-2 w-1/2 text-center ${tab === 'recv' ? 'bg-slate-500 text-white' : 'bg-white text-black'}`}
|
||||
>
|
||||
Received
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4 text-left">
|
||||
<pre className="text-[10px] text-gray-700 whitespace-pre-wrap">
|
||||
{transcript &&
|
||||
(tab === 'sent' ? transcript.sent : transcript.recv)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ClaimPoap({
|
||||
screen_name,
|
||||
exploding,
|
||||
sessionId,
|
||||
}: {
|
||||
screen_name: string;
|
||||
exploding: boolean;
|
||||
sessionId?: string;
|
||||
}): ReactElement {
|
||||
const [poapLink, setPoapLink] = useState<string>('');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -251,13 +323,13 @@ function ClaimPoap({
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
if (!screen_name) return;
|
||||
if (!sessionId) return;
|
||||
const response = await fetch('/poap-claim', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ screenName: screen_name }),
|
||||
body: JSON.stringify({ sessionId }),
|
||||
});
|
||||
if (response.status === 200) {
|
||||
const data = await response.json();
|
||||
@@ -301,36 +373,131 @@ function ClaimPoap({
|
||||
}
|
||||
|
||||
function InstallExtensionPrompt() {
|
||||
const handleRefresh = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-center items-center gap-2">
|
||||
<div className="flex flex-col justify center items-center gap-2 pb-4">
|
||||
<h1 className="text-base font-light">
|
||||
<div className="flex flex-col items-center gap-8 max-w-4xl mx-auto p-6">
|
||||
{/* Header Section */}
|
||||
<div className="text-center space-y-4">
|
||||
<h1 className="text-3xl font-bold text-gray-900">
|
||||
Welcome to the TLSNotary Plugin Demo!
|
||||
</h1>
|
||||
<p className="text-base font-light">
|
||||
This demo shows how TLSNotary can be used to verify private user data
|
||||
in a webapp.
|
||||
</p>
|
||||
<p className="text-base font-light">
|
||||
In this demo you'll prove that you own a Twitter/X account to the
|
||||
webserver.
|
||||
</p>
|
||||
<p className="text-base font-light">
|
||||
The webserver will verify your attestation and give a POAP in return (
|
||||
<span className="font-semibold">while supplies last</span>)
|
||||
<p className="text-xl text-gray-600 leading-relaxed">
|
||||
Verify private user data in web applications using zero-knowledge proofs
|
||||
</p>
|
||||
<img className="mx-auto max-w-full h-auto mt-4" src={OverviewSvg} alt="TLSNotary Prover-Verifier Overview" />
|
||||
</div>
|
||||
|
||||
{/* Demo Description */}
|
||||
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-200 rounded-xl p-8 w-full">
|
||||
<h2 className="text-2xl font-semibold text-blue-900 mb-6 text-center">
|
||||
How this demo works
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<div className="text-center">
|
||||
<div className="bg-blue-500 text-white rounded-full w-12 h-12 flex items-center justify-center mx-auto mb-4 text-lg font-bold">
|
||||
1
|
||||
</div>
|
||||
<h3 className="font-semibold text-gray-900 mb-2">Connect Extension</h3>
|
||||
<p className="text-gray-600 text-sm">
|
||||
Install and connect the TLSNotary browser extension
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<div className="bg-blue-500 text-white rounded-full w-12 h-12 flex items-center justify-center mx-auto mb-4 text-lg font-bold">
|
||||
2
|
||||
</div>
|
||||
<h3 className="font-semibold text-gray-900 mb-2">Prove Ownership</h3>
|
||||
<p className="text-gray-600 text-sm">
|
||||
Prove you own a Twitter/X account without revealing sensitive data
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<div className="bg-blue-500 text-white rounded-full w-12 h-12 flex items-center justify-center mx-auto mb-4 text-lg font-bold">
|
||||
3
|
||||
</div>
|
||||
<h3 className="font-semibold text-gray-900 mb-2">
|
||||
{process.env.POAP === 'true' ? 'Get Rewarded' : 'Verification Complete'}
|
||||
</h3>
|
||||
<p className="text-gray-600 text-sm">
|
||||
{process.env.POAP === 'true'
|
||||
? 'Receive a POAP token as proof of verification'
|
||||
: 'Your Twitter screen name is verified'
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{process.env.POAP === 'true' && (
|
||||
<div className="mt-6 p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||
<p className="text-center text-yellow-800">
|
||||
🎁 <strong>Special offer:</strong> Get a POAP (Proof of Attendance Protocol) token after verification!{' '}<br />
|
||||
<span className="font-semibold">(while supplies last)</span>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Installation Section */}
|
||||
<div className="bg-white border-2 border-gray-200 rounded-xl p-8 w-full shadow-sm">
|
||||
<div className="text-center space-y-6">
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-xl font-semibold text-gray-900">
|
||||
Get Started
|
||||
</h2>
|
||||
<p className="text-gray-600">
|
||||
Install the TLSNotary extension to begin the verification process
|
||||
</p>
|
||||
{/* Add the manual refresh notice */}
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3 mt-4">
|
||||
<p className="text-sm text-blue-800">
|
||||
ℹ️ <strong>Note:</strong> This page cannot automatically detect when the extension is installed.
|
||||
You'll need to refresh the page after installation. We've added a refresh button below for your convenience.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<a
|
||||
href="https://chromewebstore.google.com/detail/tlsn-extension/gcfkkledipjbgdbimfpijgbkhajiaaph"
|
||||
target="_blank"
|
||||
className="button bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 text-lg font-semibold min-w-[200px]"
|
||||
>
|
||||
📥 Install Extension
|
||||
</a>
|
||||
|
||||
<button
|
||||
onClick={handleRefresh}
|
||||
className="button bg-gray-100 hover:bg-gray-200 text-gray-700 px-8 py-3 text-lg font-semibold min-w-[200px]"
|
||||
>
|
||||
🔄 Refresh Page
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Additional Info */}
|
||||
<div className="text-center space-y-4 text-gray-600 max-w-2xl">
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
What is TLSNotary?
|
||||
</h3>
|
||||
<p className="text-sm leading-relaxed">
|
||||
TLSNotary enables privacy-preserving verification of web data. Instead of sharing your actual data,
|
||||
you can prove specific facts about it using cryptographic proofs, keeping your sensitive information private.
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-center gap-6 text-xs text-gray-500 pt-4">
|
||||
<a href="https://tlsnotary.org" className="hover:text-blue-600 transition-colors">Learn More</a>
|
||||
<a href="https://tlsnotary.org/docs/intro" className="hover:text-blue-600 transition-colors">Documentation</a>
|
||||
<a href="https://github.com/tlsnotary" className="hover:text-blue-600 transition-colors">GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
<p className="font-bold">Please install the extension to proceed </p>
|
||||
<p className="font-bold">
|
||||
You will need to refresh your browser after installing the extension
|
||||
</p>
|
||||
<a
|
||||
href="https://chromewebstore.google.com/detail/tlsn-extension/gcfkkledipjbgdbimfpijgbkhajiaaph"
|
||||
target="_blank"
|
||||
className="button"
|
||||
>
|
||||
Install TLSN Extension
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import 'isomorphic-fetch';
|
||||
import type {} from 'redux-thunk/extend-redux';
|
||||
import * as React from 'react';
|
||||
import { hydrateRoot } from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import App from './pages/App';
|
||||
import { Provider } from 'react-redux';
|
||||
import configureAppStore from './store';
|
||||
|
||||
// @ts-ignore
|
||||
const store = configureAppStore(window.__PRELOADED_STATE__);
|
||||
|
||||
// @ts-ignore
|
||||
delete window.__PRELOADED_STATE__;
|
||||
@@ -16,11 +10,14 @@ delete window.__PRELOADED_STATE__;
|
||||
(async () => {
|
||||
hydrateRoot(
|
||||
document.getElementById('root')!,
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</Provider>,
|
||||
<BrowserRouter
|
||||
future={{
|
||||
v7_startTransition: true,
|
||||
v7_relativeSplatPath: true,
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
);
|
||||
})();
|
||||
|
||||
|
||||
@@ -2,9 +2,4 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
$fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
|
||||
|
||||
@import "../../../node_modules/@fortawesome/fontawesome-free/scss/fontawesome";
|
||||
@import "../../../node_modules/@fortawesome/fontawesome-free/scss/brands";
|
||||
@import "../../../node_modules/@fortawesome/fontawesome-free/scss/solid";
|
||||
@import "../../../node_modules/@fortawesome/fontawesome-free/scss/regular";
|
||||
@import "~@fortawesome/fontawesome-free/css/all.css";
|
||||
@@ -1,51 +0,0 @@
|
||||
import { Attestation, AttestedData } from '../utils/types';
|
||||
|
||||
enum ActionType {
|
||||
SET_ATTESTATION = 'attestation/SET_ATTESTATION',
|
||||
}
|
||||
|
||||
export type Action<payload = any> = {
|
||||
type: ActionType;
|
||||
payload: payload;
|
||||
error?: boolean;
|
||||
meta?: any;
|
||||
};
|
||||
|
||||
type AttestationData = {
|
||||
raw: Attestation;
|
||||
};
|
||||
|
||||
export type State = {
|
||||
raw: Attestation;
|
||||
};
|
||||
|
||||
export const initState: State = {
|
||||
raw: {
|
||||
version: '0.1.0-alpha.12',
|
||||
data: '',
|
||||
meta: {
|
||||
notaryUrl: '',
|
||||
websocketProxyUrl: '',
|
||||
pluginUrl: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const setAttestation = (
|
||||
attestation: AttestationData,
|
||||
): Action<AttestationData> => ({
|
||||
type: ActionType.SET_ATTESTATION,
|
||||
payload: attestation,
|
||||
});
|
||||
|
||||
export default function attestation(state = initState, action: Action): State {
|
||||
switch (action.type) {
|
||||
case ActionType.SET_ATTESTATION:
|
||||
return {
|
||||
...state,
|
||||
raw: action.payload,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { applyMiddleware, combineReducers, createStore } from 'redux';
|
||||
import thunk from 'redux-thunk';
|
||||
import { createLogger } from 'redux-logger';
|
||||
import attestation from './attestation';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
attestation,
|
||||
});
|
||||
|
||||
export type AppRootState = ReturnType<typeof rootReducer>;
|
||||
|
||||
const createStoreWithMiddleware =
|
||||
process.env.NODE_ENV === 'development'
|
||||
? applyMiddleware(
|
||||
thunk,
|
||||
createLogger({
|
||||
collapsed: true,
|
||||
}),
|
||||
)(createStore)
|
||||
: applyMiddleware(thunk)(createStore);
|
||||
|
||||
function configureAppStore(preloadedState?: AppRootState) {
|
||||
const { attestation } = preloadedState || {};
|
||||
return createStoreWithMiddleware(rootReducer, {
|
||||
attestation,
|
||||
});
|
||||
}
|
||||
|
||||
export default configureAppStore;
|
||||
@@ -1,20 +0,0 @@
|
||||
export interface AttestedData {
|
||||
version: '0.1.0-alpha.12';
|
||||
time: number;
|
||||
sent: string;
|
||||
recv: string;
|
||||
notaryUrl: string;
|
||||
notaryKey: string;
|
||||
websocketProxyUrl?: string;
|
||||
verifierKey?: string;
|
||||
}
|
||||
|
||||
export type Attestation = {
|
||||
version: '0.1.0-alpha.12';
|
||||
data: string;
|
||||
meta: {
|
||||
notaryUrl: string;
|
||||
websocketProxyUrl: string;
|
||||
pluginUrl?: string;
|
||||
};
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { PresentationJSON } from 'tlsn-js/build/types';
|
||||
|
||||
export const formatDataPreview = (data: PresentationJSON) => {
|
||||
if (!data) return '';
|
||||
return Object.entries(data)
|
||||
.map(([key, value]) => {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
return `${key}: ${JSON.stringify(value, null, 2)}`;
|
||||
} else if (key === 'data') {
|
||||
const maxLength = 160;
|
||||
const previewData = value.toString().substring(0, maxLength);
|
||||
const formattedData = previewData.match(/.{1,20}/g)?.join('\n');
|
||||
return `${key}: ${formattedData}... ${value.length} more`;
|
||||
} else {
|
||||
return `${key}: ${value}`;
|
||||
}
|
||||
})
|
||||
.join('\n');
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import * as Comlink from 'comlink';
|
||||
import init, { Presentation } from 'tlsn-js';
|
||||
|
||||
Comlink.expose({
|
||||
init,
|
||||
Presentation,
|
||||
});
|
||||
@@ -8,6 +8,8 @@ var { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
const ASSET_PATH = process.env.ASSET_PATH || '/';
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
const isPOAP = process.env.POAP === 'true';
|
||||
|
||||
|
||||
const options = {
|
||||
target: 'node',
|
||||
@@ -24,6 +26,9 @@ const options = {
|
||||
clean: true,
|
||||
publicPath: ASSET_PATH,
|
||||
},
|
||||
externals: {
|
||||
express: 'commonjs express',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
@@ -42,7 +47,6 @@ const options = {
|
||||
},
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
exclude: /node_modules\/(?!(tlsn-js|tlsn-js-v5)\/).*/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve("ts-loader"),
|
||||
@@ -85,11 +89,13 @@ const options = {
|
||||
new CleanWebpackPlugin({ verbose: false }),
|
||||
new webpack.ProgressPlugin(),
|
||||
new webpack.EnvironmentPlugin(['NODE_ENV']),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{ from: 'server/util/firebase-admin.json', to: 'util/' },
|
||||
],
|
||||
}),
|
||||
isPOAP
|
||||
? new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{ from: 'server/util/firebase-admin.json', to: 'util/' },
|
||||
],
|
||||
})
|
||||
: null,
|
||||
new CompressionPlugin({
|
||||
algorithm: 'gzip',
|
||||
test: /\.(js|css|html|svg)$/,
|
||||
|
||||
@@ -78,7 +78,6 @@ var options = {
|
||||
},
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
exclude: /node_modules\/(?!(tlsn-js|tlsn-js-v5)\/).*/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve("ts-loader"),
|
||||
@@ -137,7 +136,7 @@ var options = {
|
||||
new CleanWebpackPlugin({ verbose: false }),
|
||||
new webpack.ProgressPlugin(),
|
||||
// expose and write the allowed env vars on the compiled bundle
|
||||
new webpack.EnvironmentPlugin(['NODE_ENV']),
|
||||
new webpack.EnvironmentPlugin({ NODE_ENV: 'development', POAP: 'false' }),
|
||||
// new HtmlWebpackPlugin({
|
||||
// template: path.join(__dirname, 'static', 'index.html'),
|
||||
// filename: 'index.html',
|
||||
@@ -146,11 +145,6 @@ var options = {
|
||||
// }),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: 'node_modules/tlsn-js/build',
|
||||
to: path.join(__dirname, 'build', 'ui'),
|
||||
force: true,
|
||||
},
|
||||
{
|
||||
from: "static/favicon.png",
|
||||
to: path.join(__dirname, "build", "ui"),
|
||||
@@ -161,6 +155,11 @@ var options = {
|
||||
to: path.join(__dirname, "build", "ui"),
|
||||
force: true,
|
||||
},
|
||||
{
|
||||
from: "static/twitter_profile.tlsn.wasm",
|
||||
to: path.join(__dirname, "build", "ui"),
|
||||
force: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
new webpack.ProvidePlugin({
|
||||
@@ -169,21 +168,7 @@ var options = {
|
||||
].filter(Boolean),
|
||||
infrastructureLogging: {
|
||||
level: 'info',
|
||||
},
|
||||
// Required by wasm-bindgen-rayon, in order to use SharedArrayBuffer on the Web
|
||||
// Ref:
|
||||
// - https://github.com/GoogleChromeLabs/wasm-bindgen-rayon#setting-up
|
||||
// - https://web.dev/i18n/en/coop-coep/
|
||||
devServer: {
|
||||
headers: {
|
||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||
},
|
||||
},
|
||||
// Enable WebAssembly support
|
||||
experiments: {
|
||||
asyncWebAssembly: true,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
|
||||
Reference in New Issue
Block a user