mirror of
https://github.com/tlsnotary/tlsn-plugin-demo.git
synced 2026-01-06 20:14:01 -05:00
fix: squash
This commit is contained in:
10
.eslintrc
10
.eslintrc
@@ -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
3
.gitignore
vendored
@@ -2,3 +2,6 @@
|
||||
**/.DS_Store
|
||||
.idea
|
||||
build
|
||||
.env
|
||||
poaps.txt
|
||||
assignments.json
|
||||
|
||||
6956
package-lock.json
generated
6956
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
74
package.json
74
package.json
@@ -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
100
server/index.tsx
Normal 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
40
server/util/index.ts
Normal 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;
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
"noEmit": false,
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": [
|
||||
"web"
|
||||
],
|
||||
"exclude": ["build", "node_modules"]
|
||||
}
|
||||
|
||||
16
web/components/Body/index.tsx
Normal file
16
web/components/Body/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
46
web/components/Button/index.tsx
Normal file
46
web/components/Button/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
45
web/components/Header/index.scss
Normal file
45
web/components/Header/index.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
0
web/components/Icon/index.scss
Normal file
0
web/components/Icon/index.scss
Normal file
0
web/components/Steps/index.scss
Normal file
0
web/components/Steps/index.scss
Normal file
365
web/components/Steps/index.tsx
Normal file
365
web/components/Steps/index.tsx
Normal 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
45
web/components/index.scss
Normal 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
4
web/declarations.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare module '*.svg' {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
@@ -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
16
web/pages/App/index.tsx
Normal 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
51
web/store/attestation.tsx
Normal 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
29
web/store/index.tsx
Normal 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
20
web/utils/types.tsx
Normal 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
7
web/utils/worker.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import * as Comlink from 'comlink';
|
||||
import init, { Presentation } from 'tlsn-js';
|
||||
|
||||
Comlink.expose({
|
||||
init,
|
||||
Presentation,
|
||||
});
|
||||
@@ -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
109
webpack.server.config.js
Normal 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
203
webpack.web.config.js
Executable 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;
|
||||
Reference in New Issue
Block a user