mirror of
https://github.com/tlsnotary/tlsn-plugin-demo.git
synced 2026-01-08 04:54:07 -05:00
fix: squash
This commit is contained in:
10
.eslintrc
10
.eslintrc
@@ -26,11 +26,9 @@
|
|||||||
},
|
},
|
||||||
"ignorePatterns": [
|
"ignorePatterns": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"zip",
|
|
||||||
"build",
|
"build",
|
||||||
"wasm",
|
"webpack.web.config.js",
|
||||||
"tlsn",
|
"webpack.server.config.js",
|
||||||
"util",
|
"static"
|
||||||
"webpack.config.js"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,3 +2,6 @@
|
|||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
.idea
|
.idea
|
||||||
build
|
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",
|
"name": "tlsn-content",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "NODE_ENV=production webpack --config webpack.config.js",
|
"build:ui": "NODE_ENV=production webpack --config webpack.web.config.js",
|
||||||
"dev": "NODE_ENV=development webpack-dev-server --config webpack.config.js --hot",
|
"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": "eslint .",
|
||||||
"lint:fix": "eslint . --fix"
|
"lint:fix": "eslint . --fix"
|
||||||
},
|
},
|
||||||
@@ -19,21 +27,33 @@
|
|||||||
"url": ""
|
"url": ""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.13.3",
|
||||||
|
"@emotion/styled": "^11.13.0",
|
||||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||||
|
"@mui/material": "^6.1.2",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"charwise": "^3.0.1",
|
"charwise": "^3.0.1",
|
||||||
"classnames": "^2.5.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",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"react": "^18.3.1",
|
"nodemon": "^3.1.7",
|
||||||
"react-dom": "^18.3.1",
|
"react": "^18.2.0",
|
||||||
"react-redux": "^9.1.2",
|
"react-confetti-explosion": "^2.1.2",
|
||||||
"react-router": "^6.26.2",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "^6.26.2",
|
"react-redux": "^8.1.2",
|
||||||
"redux": "^5.0.1",
|
"react-router": "^6.15.0",
|
||||||
|
"react-router-dom": "^6.15.0",
|
||||||
|
"redux": "^4.2.1",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"redux-thunk": "^3.1.0",
|
"redux-thunk": "^2.4.2",
|
||||||
"tailwindcss": "^3.4.13"
|
"stream-browserify": "^3.0.0",
|
||||||
|
"tailwindcss": "^3.4.13",
|
||||||
|
"tlsn-js": "^0.1.0-alpha.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.2",
|
||||||
@@ -65,24 +85,26 @@
|
|||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
"html-loader": "^5.1.0",
|
"html-loader": "^4.2.0",
|
||||||
"html-webpack-plugin": "^5.6.0",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
"postcss-loader": "^8.1.1",
|
"image-webpack-loader": "^8.1.0",
|
||||||
"postcss-preset-env": "^10.0.5",
|
"postcss-loader": "^7.3.3",
|
||||||
|
"postcss-preset-env": "^9.1.1",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"react-refresh": "^0.14.2",
|
"react-refresh": "^0.14.2",
|
||||||
"react-refresh-typescript": "^2.0.9",
|
"react-refresh-typescript": "^2.0.9",
|
||||||
"sass": "^1.79.4",
|
"sass": "^1.57.1",
|
||||||
"sass-loader": "^16.0.2",
|
"sass-loader": "^13.2.0",
|
||||||
"source-map-loader": "^5.0.0",
|
"source-map-loader": "^3.0.1",
|
||||||
"style-loader": "^4.0.0",
|
"style-loader": "^3.3.1",
|
||||||
"terser-webpack-plugin": "^5.3.10",
|
"terser-webpack-plugin": "^5.3.6",
|
||||||
"ts-loader": "^9.5.1",
|
"ts-loader": "^9.4.2",
|
||||||
"type-fest": "^4.26.1",
|
"type-fest": "^3.5.2",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^4.9.4",
|
||||||
"webpack": "^5.95.0",
|
"wasm-loader": "^1.3.0",
|
||||||
"webpack-cli": "^5.1.4",
|
"webpack": "^5.75.0",
|
||||||
"webpack-dev-server": "^5.1.0"
|
"webpack-cli": "^4.10.0",
|
||||||
|
"webpack-dev-server": "^4.11.1"
|
||||||
},
|
},
|
||||||
"homepage": ""
|
"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 charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" type="image/png" href="/static/favicon.png" />
|
<link rel="icon" type="image/png" href="/static/favicon.png" />
|
||||||
<title>TLSN Provider Demo</title>
|
<title>TLSN Plugin Demo</title>
|
||||||
<favicon></favicon>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
content: ['./web/**/*.{js,jsx,ts,tsx}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
"jsx": "react"
|
"jsx": "react"
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": [
|
||||||
|
"web"
|
||||||
|
],
|
||||||
"exclude": ["build", "node_modules"]
|
"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 'isomorphic-fetch';
|
||||||
|
import type {} from 'redux-thunk/extend-redux';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as ReactDOM from 'react-dom';
|
import { hydrateRoot } from 'react-dom/client';
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import store from './store';
|
|
||||||
import App from './pages/App';
|
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 () => {
|
(async () => {
|
||||||
ReactDOM.render(
|
hydrateRoot(
|
||||||
|
document.getElementById('root')!,
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<App />
|
<App />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</Provider>,
|
</Provider>,
|
||||||
document.getElementById('root')
|
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if ((module as any).hot) {
|
if ((module as any).hot) {
|
||||||
(module as any).hot.accept();
|
(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