fix: squash

This commit is contained in:
Codetrauma
2024-10-30 10:48:03 -07:00
parent 1dcf07db5e
commit a01d130bdc
34 changed files with 7084 additions and 1307 deletions

View File

@@ -26,11 +26,9 @@
},
"ignorePatterns": [
"node_modules",
"zip",
"build",
"wasm",
"tlsn",
"util",
"webpack.config.js"
"webpack.web.config.js",
"webpack.server.config.js",
"static"
]
}
}

3
.gitignore vendored
View File

@@ -2,3 +2,6 @@
**/.DS_Store
.idea
build
.env
poaps.txt
assignments.json

6956
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,18 @@
"name": "tlsn-content",
"version": "0.0.0",
"description": "",
"main": "index.js",
"main": "",
"scripts": {
"build": "NODE_ENV=production webpack --config webpack.config.js",
"dev": "NODE_ENV=development webpack-dev-server --config webpack.config.js --hot",
"build:ui": "NODE_ENV=production webpack --config webpack.web.config.js",
"watch:ui": "NODE_ENV=development webpack --config webpack.web.config.js --watch",
"dev:ui": "NODE_ENV=development webpack-dev-server --config webpack.web.config.js --hot",
"build:server": "NODE_ENV=production webpack --config webpack.server.config.js",
"watch:server": "NODE_ENV=development webpack --config webpack.server.config.js --watch",
"nodemon:server": "NODE_ENV=development nodemon build/server/index.bundle.js",
"dev:server": "NODE_ENV=development concurrently npm:watch:server npm:nodemon:server",
"start": "node build/server/index.bundle.js",
"build": "concurrently npm:build:ui npm:build:server",
"dev": "concurrently npm:watch:ui npm:dev:server",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
@@ -19,21 +27,33 @@
"url": ""
},
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@fortawesome/fontawesome-free": "^6.6.0",
"@mui/material": "^6.1.2",
"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",
"isomorphic-fetch": "^3.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^9.1.2",
"react-router": "^6.26.2",
"react-router-dom": "^6.26.2",
"redux": "^5.0.1",
"nodemon": "^3.1.7",
"react": "^18.2.0",
"react-confetti-explosion": "^2.1.2",
"react-dom": "^18.2.0",
"react-redux": "^8.1.2",
"react-router": "^6.15.0",
"react-router-dom": "^6.15.0",
"redux": "^4.2.1",
"redux-logger": "^3.0.6",
"redux-thunk": "^3.1.0",
"tailwindcss": "^3.4.13"
"redux-thunk": "^2.4.2",
"stream-browserify": "^3.0.0",
"tailwindcss": "^3.4.13",
"tlsn-js": "^0.1.0-alpha.7.1"
},
"devDependencies": {
"@babel/core": "^7.25.2",
@@ -65,24 +85,26 @@
"eslint-plugin-react-hooks": "^4.6.2",
"file-loader": "^6.2.0",
"fs-extra": "^11.2.0",
"html-loader": "^5.1.0",
"html-webpack-plugin": "^5.6.0",
"postcss-loader": "^8.1.1",
"postcss-preset-env": "^10.0.5",
"html-loader": "^4.2.0",
"html-webpack-plugin": "^5.5.0",
"image-webpack-loader": "^8.1.0",
"postcss-loader": "^7.3.3",
"postcss-preset-env": "^9.1.1",
"prettier": "^3.3.3",
"react-refresh": "^0.14.2",
"react-refresh-typescript": "^2.0.9",
"sass": "^1.79.4",
"sass-loader": "^16.0.2",
"source-map-loader": "^5.0.0",
"style-loader": "^4.0.0",
"terser-webpack-plugin": "^5.3.10",
"ts-loader": "^9.5.1",
"type-fest": "^4.26.1",
"typescript": "^5.6.2",
"webpack": "^5.95.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.1.0"
"sass": "^1.57.1",
"sass-loader": "^13.2.0",
"source-map-loader": "^3.0.1",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.6",
"ts-loader": "^9.4.2",
"type-fest": "^3.5.2",
"typescript": "^4.9.4",
"wasm-loader": "^1.3.0",
"webpack": "^5.75.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1"
},
"homepage": ""
}

100
server/index.tsx Normal file
View File

@@ -0,0 +1,100 @@
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 { getPoapLink } from './util/index';
const app = express();
const port = 3000;
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content, Accept, Content-Type, Authorization',
);
res.setHeader(
'Access-Control-Allow-Methods',
'GET, POST, PUT, DELETE, PATCH, OPTIONS',
);
res.setHeader('Cross-origin-Embedder-Policy', 'require-corp');
res.setHeader('Cross-origin-Opener-Policy', 'same-origin');
if (req.method === 'OPTIONS') {
res.sendStatus(200);
} else {
next();
}
});
app.use(express.static('build/ui'));
app.use(express.json());
app.get('*', (req, res) => {
const storeConfig: AppRootState = {
attestation: {
raw: {
version: '0.1.0-alpha.7',
data: '',
meta: {
notaryUrl: '',
websocketProxyUrl: '',
pluginUrl: '',
},
},
},
};
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>
`);
});
app.post('/poap-claim', (req, res) => {
const { screenName } = req.body;
if (!screenName) {
return res.status(400).send('Missing screen_name');
}
const poapLink = getPoapLink(screenName);
if (poapLink) {
res.json({ poapLink });
} else {
res.status(404).send('No POAP links available');
}
});
app.listen(port, () => {
console.log(`Plugin demo listening on port ${port}`);
});

40
server/util/index.ts Normal file
View File

@@ -0,0 +1,40 @@
import path from 'path';
import fs from 'fs';
const poapLinksPath = path.join(__dirname, 'util', 'poaps.txt');
const assignmentsFilePath = path.join(__dirname, 'util', 'assignments.json');
export const loadPoapLinks = (): string[] => {
const fileContent = fs.readFileSync(poapLinksPath, 'utf-8');
return fileContent.trim().split('\n');
};
export const loadAssignments = (): Record<string, string> => {
if (fs.existsSync(assignmentsFilePath)) {
const fileContent = fs.readFileSync(assignmentsFilePath, 'utf-8');
return JSON.parse(fileContent);
}
return {};
};
export const saveAssignments = (assignments: Record<string, string>) => {
fs.writeFileSync(assignmentsFilePath, JSON.stringify(assignments, null, 2));
};
export const getPoapLink = (screenName: string): string | null => {
const poapLinks = loadPoapLinks();
const assignments = loadAssignments();
if (assignments[screenName]) {
return assignments[screenName];
}
if (poapLinks.length === 0) {
return null;
}
const newLink = poapLinks.shift();
assignments[screenName] = newLink as string;
saveAssignments(assignments);
fs.writeFileSync(poapLinksPath, poapLinks.join('\n'));
return newLink || null;
};

View File

@@ -1,23 +0,0 @@
import React, { ReactElement } from 'react';
import "./index.scss";
import { useDispatch } from 'react-redux';
import './index.scss';
import Header from '../../components/Header';
export default function App(): ReactElement {
const dispatch = useDispatch();
async function handleConnect() {
//@ts-ignore
await window.tlsn.connect();
}
return (
<div className="app flex flex-col gap-4">
<Header />
<button onClick={handleConnect} className="button">
TLSN Connect
</button>
</div>
);
}

View File

@@ -1,32 +0,0 @@
import { applyMiddleware, combineReducers, createStore } from 'redux';
import { thunk } from 'redux-thunk';
import { createLogger } from 'redux-logger';
const rootReducer = combineReducers({
// Add your reducers here
});
export type AppRootState = ReturnType<typeof rootReducer>;
const createStoreWithMiddleware =
process.env.NODE_ENV === 'development'
? applyMiddleware(
thunk,
createLogger({
collapsed: true,
}),
)(createStore)
: applyMiddleware(
thunk,
)(createStore);
function configureAppStore() {
return createStoreWithMiddleware(
rootReducer,
);
}
const store = configureAppStore();
export default store;

View File

@@ -4,8 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/png" href="/static/favicon.png" />
<title>TLSN Provider Demo</title>
<favicon></favicon>
<title>TLSN Plugin Demo</title>
</head>
<body>
<div id="root"></div>

View File

@@ -1,6 +1,6 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
content: ['./web/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {
colors: {

View File

@@ -15,6 +15,8 @@
"noEmit": false,
"jsx": "react"
},
"include": ["src"],
"include": [
"web"
],
"exclude": ["build", "node_modules"]
}

View File

@@ -0,0 +1,16 @@
import React, { ReactElement } from 'react';
import './index.scss';
import Steps from '../Steps';
import Logo from '../../../static/logo.svg';
export default function Body(): ReactElement {
return (
<div className="w-full">
<div className="flex flex-row w-full justify-center items-center gap-4 pb-12">
<img className="w-11 h-11" src={Logo} alt="Logo" />
<span className="font-bold text-slate-700 text-3xl">Plugin Demo</span>
</div>
<Steps />
</div>
);
}

View File

@@ -0,0 +1,46 @@
import React, { ButtonHTMLAttributes, ReactElement } from 'react';
import classNames from 'classnames';
import './index.scss';
import Icon from '../Icon';
type Props = {
className?: string;
btnType?: 'primary' | 'secondary' | '';
loading?: boolean;
} & ButtonHTMLAttributes<HTMLButtonElement>;
export default function Button(props: Props): ReactElement {
const {
className,
btnType = '',
children,
onClick,
disabled,
loading,
// Must select all non-button props here otherwise react-dom will show warning
...btnProps
} = props;
return (
<button
className={classNames(
'flex flex-row flex-nowrap items-center',
'h-10 px-4 button transition-colors',
{
'button--primary': btnType === 'primary',
'button--secondary': btnType === 'secondary',
'cursor-default': disabled || loading,
},
className,
)}
onClick={!disabled && !loading ? onClick : undefined}
disabled={disabled || loading}
{...btnProps}
>
{loading ? (
<Icon className="animate-spin" fa="fa-solid fa-spinner" size={2} />
) : (
children
)}
</button>
);
}

View File

@@ -0,0 +1,45 @@
.button {
@apply bg-slate-100;
@apply text-slate-500;
@apply font-bold;
@apply px-2 py-0.5;
user-select: none;
&:hover {
@apply text-slate-600;
@apply bg-slate-200;
}
&:active {
@apply text-slate-700;
@apply bg-slate-300;
}
&--primary {
@apply bg-primary/[0.8];
@apply text-white;
&:hover {
@apply bg-primary/[0.9];
@apply text-white;
}
&:active {
@apply bg-primary;
@apply text-white;
}
}
&:disabled {
@apply opacity-50;
@apply select-none;
&:hover {
@apply text-slate-400;
}
&:active {
@apply text-slate-400;
}
}
}

View File

View File

View File

@@ -0,0 +1,365 @@
import React, { ReactElement, useEffect, useState } from 'react';
import './index.scss';
import Stepper from '@mui/material/Stepper';
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';
const steps = [
'Connect Extension',
'Install Plugin',
'Run Plugin',
'🎉 Claim POAP 🎉',
];
export default function Steps(): ReactElement {
const [extensionInstalled, setExtensionInstalled] = useState(false);
const [pluginID, setPluginID] = useState('');
const [step, setStep] = useState<number>(0);
const [client, setClient] = useState<any>(null);
const [pluginData, setPluginData] = useState<PresentationJSON | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [pluginInstalled, setPluginInstalled] = useState<boolean>(false);
const [transcript, setTranscript] = useState<any>(null);
const [screenName, setScreenName] = useState<string>('');
const [exploding, setExploding] = useState<boolean>(false);
useEffect(() => {
const checkExtension = () => {
//@ts-ignore
if (typeof window.tlsn !== 'undefined') {
setExtensionInstalled(true);
setTimeout(async () => {
// temporary fix until extension events added
// @ts-ignore
setClient(await window.tlsn.connect());
setStep(1);
}, 200);
} else {
return;
}
};
window.onload = () => {
checkExtension();
};
(async () => {
const { default: init } = await import('tlsn-js');
await init();
})();
return () => {
window.onload = null;
};
}, []);
useEffect(() => {
if (transcript) {
const match = transcript.recv.match(/"screen_name":"([^"]+)"/);
const screenName = match ? match[1] : null;
setScreenName(screenName);
setExploding(true);
}
}, [transcript]);
async function handleConnect() {
try {
//@ts-ignore
setClient(await window.tlsn.connect());
setStep(1);
} catch (error) {
console.log(error);
}
}
async function handleGetPlugins() {
try {
const plugins = await client.getPlugins('**', '**', {
id: 'twitter-plugin',
});
if (plugins.length > 0) {
setPluginID(plugins[0].hash);
setStep(2);
} else {
setPluginInstalled(true);
}
} catch (error) {
console.log(error);
}
}
async function handlePluginInstall() {
try {
const plugin = await client.installPlugin(
'https://github.com/tlsnotary/tlsn-extension/raw/main/src/assets/plugins/twitter_profile.wasm',
{ id: 'twitter-plugin' },
);
setPluginID(plugin);
setStep(2);
} catch (error) {
console.log(error);
}
}
async function handleRunPlugin() {
try {
setLoading(true);
const pluginData = await client.runPlugin(pluginID);
setLoading(false);
setPluginData(pluginData);
setStep(3);
} catch (error) {
setLoading(false);
console.log(error);
}
}
return (
<div className="flex flex-col items-center gap-4">
{extensionInstalled ? (
<>
<div className="flex flex-row items-center gap-2 text-slate-600 font-bold pb-2">
Connected{' '}
<div
className={classNames(
'rounded-full h-[10px] w-[10px] border-[2px]',
{
'bg-green-500': step >= 1,
'bg-red-500': step === 0,
},
)}
></div>
</div>
<Box className="w-full max-w-xl 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 && (
<div className="flex flex-col gap-2">
<button className="button" onClick={handleGetPlugins}>
Check Plugins
</button>
<button
onClick={handlePluginInstall}
disabled={!pluginInstalled}
className="button"
>
Install Plugin
</button>
</div>
)}
{step === 2 && (
<div className="flex flex-col items-center justify-center gap-2">
<Button onClick={handleRunPlugin} loading={loading}>
Run Plugin
</Button>
<span className="font-bold">
Please keep the sidebar open during the notarization process
</span>
</div>
)}
{step === 4 && (
<>
<ClaimPoap screen_name={screenName} exploding={exploding} />
</>
)}
</div>
<DisplayPluginData
step={step}
pluginData={pluginData}
transcript={transcript}
setTranscript={setTranscript}
setStep={setStep}
/>
</>
) : (
<div className="flex flex-col justify-center items-center gap-2">
<a
href="https://chromewebstore.google.com/detail/tlsn-extension/gcfkkledipjbgdbimfpijgbkhajiaaph"
target="_blank"
className="button"
>
Install TLSN Extension
</a>
<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>
</div>
)}
</div>
);
}
function DisplayPluginData({
step,
pluginData,
transcript,
setTranscript,
setStep,
}: {
step: number;
pluginData: any;
transcript: any;
setTranscript: any;
setStep: any;
}): ReactElement {
const [tab, setTab] = useState<'sent' | 'recv'>('sent');
async function handleVerify() {
try {
const { Presentation, Transcript } = await import('tlsn-js');
const presentation = await new Presentation(pluginData.data);
const proof = await presentation.verify();
const transcript = new Transcript({
sent: proof.transcript.sent,
recv: proof.transcript.recv,
});
const verifiedData = {
sent: transcript.sent(),
recv: transcript.recv(),
};
setTranscript(verifiedData);
setStep(4);
} catch (error) {
console.log(error);
}
}
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');
};
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>
<button disabled={step !== 3} onClick={handleVerify} className="button">
Verify
</button>
<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,
}: {
screen_name: string;
exploding: boolean;
}): ReactElement {
const [screenName, setScreenName] = useState('');
const [poapLink, setPoapLink] = useState<string>('');
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const handleClaimPoap = async () => {
try {
if (!screen_name) return;
const response = await fetch('/poap-claim', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ screenName: screen_name }),
});
if (response.status === 200) {
const data = await response.json();
setPoapLink(data.poapLink);
} else {
setError(await response.text());
}
} catch (error) {
console.log(error);
}
};
handleClaimPoap();
}, [screen_name]);
const mediumProps: ConfettiProps = {
force: 0.6,
duration: 4000,
particleCount: 150,
width: 1500,
colors: ['#F0FFF', '#F0F8FF', '#483D8B', '#E0FFF', '#778899'],
};
return (
<div>
{poapLink !== '' && (
<a className="button" href={poapLink} target="_blank">
Claim POAP!
</a>
)}
{exploding && <ConfettiExplosion {...mediumProps} />}
</div>
);
}

45
web/components/index.scss Normal file
View File

@@ -0,0 +1,45 @@
.button {
@apply bg-slate-100;
@apply text-slate-500;
@apply font-bold;
@apply px-2 py-0.5;
user-select: none;
&:hover {
@apply text-slate-600;
@apply bg-slate-200;
}
&:active {
@apply text-slate-700;
@apply bg-slate-300;
}
&--primary {
@apply bg-primary/[0.8];
@apply text-white;
&:hover {
@apply bg-primary/[0.9];
@apply text-white;
}
&:active {
@apply bg-primary;
@apply text-white;
}
}
&:disabled {
@apply opacity-50;
@apply select-none;
&:hover {
@apply text-slate-400;
}
&:active {
@apply text-slate-400;
}
}
}

4
web/declarations.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
declare module '*.svg' {
const content: any;
export default content;
}

View File

@@ -1,22 +1,29 @@
import 'isomorphic-fetch';
import type {} from 'redux-thunk/extend-redux';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { hydrateRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import store from './store';
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__;
(async () => {
ReactDOM.render(
hydrateRoot(
document.getElementById('root')!,
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
})();
if ((module as any).hot) {
(module as any).hot.accept();
}
}

16
web/pages/App/index.tsx Normal file
View File

@@ -0,0 +1,16 @@
import React, { ReactElement } from 'react';
import './index.scss';
import Header from '../../components/Header';
import Body from '../../components/Body';
import { Routes, Route } from 'react-router-dom';
export default function App(): ReactElement {
return (
<div className="app flex flex-col gap-4">
<Header />
<Routes>
<Route path="/" element={<Body />} />
</Routes>
</div>
);
}

51
web/store/attestation.tsx Normal file
View File

@@ -0,0 +1,51 @@
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.7',
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;
}
}

29
web/store/index.tsx Normal file
View File

@@ -0,0 +1,29 @@
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;

20
web/utils/types.tsx Normal file
View File

@@ -0,0 +1,20 @@
export interface AttestedData {
version: '0.1.0-alpha.7' | '0.1.0-alpha.5';
time: number;
sent: string;
recv: string;
notaryUrl: string;
notaryKey: string;
websocketProxyUrl?: string;
verifierKey?: string;
}
export type Attestation = {
version: '0.1.0-alpha.7';
data: string;
meta: {
notaryUrl: string;
websocketProxyUrl: string;
pluginUrl?: string;
};
};

7
web/utils/worker.ts Normal file
View File

@@ -0,0 +1,7 @@
import * as Comlink from 'comlink';
import init, { Presentation } from 'tlsn-js';
Comlink.expose({
init,
Presentation,
});

View File

@@ -1,169 +0,0 @@
var webpack = require("webpack"),
path = require("path"),
CopyWebpackPlugin = require("copy-webpack-plugin"),
HtmlWebpackPlugin = require("html-webpack-plugin"),
TerserPlugin = require("terser-webpack-plugin");
var { CleanWebpackPlugin } = require("clean-webpack-plugin");
var ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
var ReactRefreshTypeScript = require("react-refresh-typescript");
const ASSET_PATH = process.env.ASSET_PATH || "/";
var alias = {};
var fileExtensions = [
"jpg",
"jpeg",
"png",
"gif",
"eot",
"otf",
"svg",
"ttf",
"woff",
"woff2",
];
const isDevelopment = process.env.NODE_ENV !== "production";
var options = {
mode: process.env.NODE_ENV || "development",
ignoreWarnings: [
/Circular dependency between chunks with runtime/,
/ResizeObserver loop completed with undelivered notifications/
],
entry: {
index: path.join(__dirname, "src", "index.tsx"),
},
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "build"),
clean: true,
publicPath: ASSET_PATH,
},
module: {
rules: [
{
// look for .css or .scss files
test: /\.(css|scss)$/,
// in the `src` directory
use: [
{
loader: "style-loader",
},
{
loader: "css-loader",
options: { importLoaders: 1 },
},
{
loader: "postcss-loader",
},
{
loader: "sass-loader",
options: {
sourceMap: true,
},
},
],
},
{
test: new RegExp(".(" + fileExtensions.join("|") + ")$"),
type: "asset/resource",
exclude: /node_modules/,
// loader: 'file-loader',
// options: {
// name: '[name].[ext]',
// },
},
{
test: /\.html$/,
loader: "html-loader",
exclude: /node_modules/,
},
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve("ts-loader"),
options: {
getCustomTransformers: () => ({
before: [isDevelopment && ReactRefreshTypeScript()].filter(
Boolean
),
}),
transpileOnly: isDevelopment,
},
},
],
},
{
test: /\.(js|jsx)$/,
use: [
{
loader: "source-map-loader",
},
{
loader: require.resolve("babel-loader"),
options: {
plugins: [
isDevelopment && require.resolve("react-refresh/babel"),
].filter(Boolean),
},
},
],
exclude: /node_modules/,
},
],
},
resolve: {
alias: alias,
extensions: fileExtensions
.map((extension) => "." + extension)
.concat([".js", ".jsx", ".ts", ".tsx", ".css"]),
},
plugins: [
isDevelopment && new ReactRefreshWebpackPlugin(),
new CleanWebpackPlugin({ verbose: false }),
new webpack.ProgressPlugin(),
// expose and write the allowed env vars on the compiled bundle
new webpack.EnvironmentPlugin(["NODE_ENV"]),
new HtmlWebpackPlugin({
template: path.join(__dirname, "static", "index.html"),
filename: "index.html",
chunks: ["index"],
cache: false,
}),
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
}),
].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',
}
},
};
if (process.env.NODE_ENV === "development") {
options.devtool = "cheap-module-source-map";
} else {
options.optimization = {
minimize: true,
minimizer: [
new TerserPlugin({
extractComments: false,
}),
],
};
}
module.exports = options;

109
webpack.server.config.js Normal file
View File

@@ -0,0 +1,109 @@
var webpack = require('webpack'),
path = require('path'),
CopyWebpackPlugin = require('copy-webpack-plugin'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
TerserPlugin = require('terser-webpack-plugin');
var { CleanWebpackPlugin } = require('clean-webpack-plugin');
const ASSET_PATH = process.env.ASSET_PATH || '/';
const isDevelopment = process.env.NODE_ENV !== 'production';
const options = {
target: 'node',
mode: process.env.NODE_ENV || 'development',
entry: {
index: path.join(__dirname, 'server', 'index.tsx'),
},
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.css', '.scss', '.png', '.svg'],
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'build', 'server'),
clean: true,
publicPath: ASSET_PATH,
},
module: {
rules: [
{
test: /\.(gif|png|jpe?g|svg)$/i,
use: [
'file-loader',
{
loader: 'image-webpack-loader',
options: {
publicPath: 'assets',
bypassOnDebug: true, // webpack@1.x
disable: true, // webpack@2.x and newer
},
},
],
},
{
test: /\.(ts|tsx)$/,
exclude: /node_modules\/(?!(tlsn-js|tlsn-js-v5)\/).*/,
use: [
{
loader: require.resolve("ts-loader"),
options: {
transpileOnly: isDevelopment,
},
},
],
},
{
// look for .css or .scss files
test: /\.(css|scss)$/,
// in the `web` directory
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: { importLoaders: 1 },
},
{
loader: 'postcss-loader',
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
},
},
],
},
],
},
plugins: [
new CleanWebpackPlugin({ verbose: false }),
new webpack.ProgressPlugin(),
new webpack.EnvironmentPlugin(['NODE_ENV']),
new CopyWebpackPlugin({
patterns: [
{ from: 'server/util/poaps.txt', to: 'util/' },
{ from: 'server/util/assignments.json', to: 'util/'}
],
}),
].filter(Boolean),
infrastructureLogging: {
level: 'info',
},
};
if (process.env.NODE_ENV === 'development') {
options.devtool = 'cheap-module-source-map';
} else {
options.optimization = {
minimize: true,
minimizer: [
new TerserPlugin({
extractComments: false,
}),
],
};
}
module.exports = options;

203
webpack.web.config.js Executable file
View File

@@ -0,0 +1,203 @@
var webpack = require('webpack'),
path = require('path'),
CopyWebpackPlugin = require('copy-webpack-plugin'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
TerserPlugin = require('terser-webpack-plugin');
var { CleanWebpackPlugin } = require('clean-webpack-plugin');
var ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
var ReactRefreshTypeScript = require('react-refresh-typescript');
const ASSET_PATH = process.env.ASSET_PATH || '/';
var alias = {};
var fileExtensions = [
'jpg',
'jpeg',
'png',
'gif',
'eot',
'otf',
'svg',
'ttf',
'woff',
'woff2',
];
const isDevelopment = process.env.NODE_ENV !== 'production';
var options = {
mode: process.env.NODE_ENV || 'development',
ignoreWarnings: [
/Circular dependency between chunks with runtime/,
/ResizeObserver loop completed with undelivered notifications/,
],
entry: {
index: path.join(__dirname, 'web', 'index.tsx'),
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'build', 'ui'),
clean: true,
publicPath: ASSET_PATH,
},
module: {
rules: [
{
// look for .css or .scss files
test: /\.(css|scss)$/,
// in the `src` directory
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: { importLoaders: 1 },
},
{
loader: 'postcss-loader',
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
},
},
],
},
// {
// test: new RegExp('.(' + fileExtensions.join('|') + ')$'),
// type: 'asset/resource',
// exclude: /node_modules/,
// },
{
test: /\.html$/,
loader: 'html-loader',
exclude: /node_modules/,
},
{
test: /\.(ts|tsx)$/,
exclude: /node_modules\/(?!(tlsn-js|tlsn-js-v5)\/).*/,
use: [
{
loader: require.resolve("ts-loader"),
options: {
getCustomTransformers: () => ({
before: [isDevelopment && ReactRefreshTypeScript()].filter(
Boolean
),
}),
transpileOnly: isDevelopment,
allowTsInNodeModules: true,
},
},
],
},
{
test: /\.(js|jsx)$/,
use: [
{
loader: 'source-map-loader',
},
{
loader: require.resolve('babel-loader'),
options: {
plugins: [
isDevelopment && require.resolve('react-refresh/babel'),
].filter(Boolean),
},
},
],
exclude: /node_modules/,
},
{
test: /\.(gif|png|jpe?g|svg)$/i,
use: [
'file-loader',
{
loader: 'image-webpack-loader',
options: {
publicPath: 'assets',
bypassOnDebug: true, // webpack@1.x
disable: true, // webpack@2.x and newer
},
},
],
},
{
test: /\.wasm$/,
type: 'webassembly/async',
},
],
},
resolve: {
alias: alias,
extensions: fileExtensions
.map((extension) => '.' + extension)
.concat(['.js', '.jsx', '.ts', '.tsx', '.css']),
},
plugins: [
isDevelopment && new ReactRefreshWebpackPlugin(),
new CleanWebpackPlugin({ verbose: false }),
new webpack.ProgressPlugin(),
// expose and write the allowed env vars on the compiled bundle
new webpack.EnvironmentPlugin(['NODE_ENV']),
// new HtmlWebpackPlugin({
// template: path.join(__dirname, 'static', 'index.html'),
// filename: 'index.html',
// chunks: ['index'],
// cache: false,
// }),
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"),
force: true,
},
],
}),
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
}),
].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') {
options.devtool = 'cheap-module-source-map';
} else {
options.optimization = {
minimize: true,
minimizer: [
new TerserPlugin({
extractComments: false,
}),
],
};
}
module.exports = options;