Merge pull request #932 from status-im/remove-examples

chore: remove examples folder
This commit is contained in:
fryorcraken.eth 2022-09-08 11:12:23 +10:00 committed by GitHub
commit 0c7ce367a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
171 changed files with 4 additions and 185499 deletions

View File

@ -1,65 +0,0 @@
name: Examples CI
on:
push:
branches:
- "master"
- "staging"
- "trying"
pull_request:
jobs:
examples_build_and_test:
strategy:
matrix:
example:
[
web-chat,
eth-pm,
eth-pm-wallet-encryption,
relay-reactjs-chat,
store-reactjs-chat,
relay-angular-chat,
]
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install NodeJS
uses: actions/setup-node@v2
with:
node-version: "16"
- name: Check if `yarn` or `npm` is used.
id: use-yarn
shell: bash
run: echo "::set-output name=lockfile::$(ls yarn.lock 2> /dev/null)"
working-directory: examples/${{ matrix.example }}
- name: (npm) Cache npm cache
if: steps.use-yarn.outputs.lockfile != 'yarn.lock'
uses: actions/cache@v2
with:
path: ~/.npm
key: examples-node-v1-${{ hashFiles('examples/*/package-lock.json') }}
- name: (npm) ${{ matrix.example }} install using npm ci
if: steps.use-yarn.outputs.lockfile != 'yarn.lock'
run: npm ci
working-directory: examples/${{ matrix.example }}
- name: (npm) ${{ matrix.example }} test
if: steps.use-yarn.outputs.lockfile != 'yarn.lock'
run: npm run test
working-directory: examples/${{ matrix.example }}
- name: (yarn) ${{ matrix.example }} install using yarn
if: steps.use-yarn.outputs.lockfile == 'yarn.lock'
run: yarn install --frozen-lockfile
working-directory: examples/${{ matrix.example }}
- name: (yarn) ${{ matrix.example }} test
if: steps.use-yarn.outputs.lockfile == 'yarn.lock'
run: echo "test skipped" # yarn test; tracked with https://github.com/status-im/js-waku/issues/563
working-directory: examples/${{ matrix.example }}

View File

@ -13,7 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- **breaking**: `DecryptionParams` may be passed when using `queryHistory` instead of just keys.
- **breaking**: `DecryptionParams` may be passed when using `queryHistory` instead of just keys.
- Examples have been moved to https://github.com/waku-org/js-waku-examples.
## [0.25.0] - 2022-09-5

View File

@ -4,12 +4,6 @@ status = [
"proto",
"browser",
"node",
"examples_build_and_test (eth-pm)",
"examples_build_and_test (eth-pm-wallet-encryption)",
"examples_build_and_test (relay-angular-chat)",
"examples_build_and_test (relay-reactjs-chat)",
"examples_build_and_test (store-reactjs-chat)",
"examples_build_and_test (web-chat)",
]
block_labels = ["work-in-progress"]
delete_merged_branches = true

View File

@ -1,79 +0,0 @@
pipeline {
agent { label 'linux' }
options {
disableConcurrentBuilds()
/* manage how many builds we keep */
buildDiscarder(logRotator(
numToKeepStr: '20',
daysToKeepStr: '30',
))
}
environment {
GIT_AUTHOR_NAME = 'status-im-auto'
GIT_AUTHOR_EMAIL = 'auto@status.im'
PUPPETEER_SKIP_DOWNLOAD = 'true'
}
stages {
stage('Deps') {
steps {
sh 'npm ci'
}
}
stage('Build') {
steps {
sh 'npm run build'
sh 'npm run doc'
}
}
stage('Examples') {
parallel {
stage('eth-pm') { steps { script { buildExample() } } }
stage('eth-pm-wallet-encryption') { steps { script { buildExample() } } }
stage('relay-reactjs-chat') { steps { script { buildExample() } } }
stage('store-reactjs-chat') { steps { script { buildExample() } } }
stage('web-chat') { steps { script { buildExample() } } }
}
}
stage('HTML Examples') {
parallel {
stage('relay-js') { steps { script { copyExample() } } }
stage('store-js') { steps { script { copyExample() } } }
}
}
stage('Publish') {
when { expression { GIT_BRANCH.endsWith('master') } }
steps { script {
sshagent(credentials: ['status-im-auto-ssh']) {
sh 'npm run deploy'
}
} }
}
}
post {
always { cleanWs() }
}
}
def buildExample(example=STAGE_NAME) {
def dest = "${WORKSPACE}/build/docs/examples/${example}"
dir("examples/${example}") {
sh 'npm ci'
sh 'npm run build'
sh "mkdir -p ${dest}"
sh "cp -r build/. ${dest}"
}
}
def copyExample(example=STAGE_NAME) {
def dest = "${WORKSPACE}/build/docs/examples/${example}"
dir("examples/${example}") {
sh "cp index.html ${dest}"
}
}

View File

@ -1,11 +0,0 @@
# Description
Configuration of CI builds executed under a Jenkins instance at https://ci.status.im/.
# Website
The `Jenkinsfile.gh-pages` file builds the documentation site with this job:
https://ci.status.im/job/website/job/js-waku.wakuconnect.dev/
And deploys it via `gh-pages` branch and [GitHub Pages](https://pages.github.com/) to:
https://js-waku.wakuconnect.dev/

View File

@ -1,39 +0,0 @@
const { promisify } = require('util')
const { publish } = require('gh-pages')
const ghpublish = promisify(publish)
/* fix for "Unhandled promise rejections" */
process.on('unhandledRejection', err => { throw err })
const Args = process.argv.slice(2)
const USE_HTTPS = Args[0] && Args[0].toUpperCase() === 'HTTPS'
const branch = 'gh-pages'
const org = 'status-im'
const repo = 'js-waku'
/* use SSH auth by default */
let repoUrl = USE_HTTPS
? `https://github.com/${org}/${repo}.git`
: `git@github.com:${org}/${repo}.git`
/* alternative auth using GitHub user and API token */
if (process.env.GH_USER != undefined) {
repoUrl = (
'https://' + process.env.GH_USER +
':' + process.env.GH_TOKEN +
'@' + `github.com/${org}/${repo}.git`
)
}
const main = async (url, branch)=> {
console.log(`Pushing to: ${url}`)
console.log(`On branch: ${branch}`)
await ghpublish('build/docs', {
repo: url,
branch: branch,
dotfiles: true,
silent: false
})
}
main(repoUrl, branch)

View File

@ -1,18 +1,3 @@
## Examples
Here is the list of the code examples and the features they demonstrate:
- [Web Chat App](web-chat): Group chat, React/TypeScript, Relay, Store.
- [Ethereum Private Message Web App](eth-pm): Private Messaging, React/TypeScript, Light Push, Signature with Web3, Asymmetric Encryption.
- [Minimal ReactJS Waku Relay App](relay-reactjs-chat):
Group chat,
React/JavaScript,
`create-react-app`/`react-scripts` 5.0.0,
Waku Relay
Protobuf using `protons`,
No async/await syntax.
- [Minimal ReactJS Waku Store App](store-reactjs-chat): Waku Store, React/JavaScript, Protobuf using `protons`.
- [Using Waku Store in JavaScript](store-js): Stop retrieving results from Waku Store on condition, Use minified bundle from Unpkg.com, JavaScript.
- [Using Waku Relay in JavaScript](relay-js): Receive and send text messages with Waku Relay, Use minified bundle from Unpkg.com, JavaScript.
See https://docs.wakuconnect.dev/docs/examples/ for more examples.
Moved to https://github.com/waku-org/js-waku-examples.

View File

@ -1,3 +0,0 @@
# Remove ReactJS warning about webpack
# because this is not a monorepo, ReactJS projects are examples
SKIP_PREFLIGHT_CHECK=true

View File

@ -1,24 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/cache/

View File

@ -1,36 +0,0 @@
# Ethereum Private Message Using Wallet Encryption Web App
**Demonstrates**:
- Private Messaging
- React/TypeScript
- Waku Light Push
- Signature with Web3 using [EIP-712: `eth_signTypedData_v4`](https://eips.ethereum.org/EIPS/eip-712)
- Asymmetric Encryption
- Usage of [`eth_decrypt`](https://docs.metamask.io/guide/rpc-api.html#eth-decrypt) Wallet API
This dApp demonstrates how to send and received end-to-end encrypted messages
using the encryption API provided by some Web3 Wallet provider such as [MetaMask](https://metamask.io/).
The sender only needs to know the Ethereum address of the recipient.
The recipient must broadcast his encryption public key as a first step.
The `master` branch's HEAD is deployed at https://js-waku.wakuconnect.dev/examples/eth-pm-wallet-encryption/.
To run a development version locally, do:
```shell
git clone https://github.com/status-im/js-waku/ ; cd js-waku
npm install # Install dependencies for js-waku
npm run build # Build js-waku
cd examples/eth-pm-wallet-encryption
npm install # Install dependencies for the web app
npm run start # Start development server to serve the web app on http://localhost:3000/js-waku/eth-pm-wallet
```
## Caveats
This is a PoC with some obvious UX caveats:
- As the message payload is fully encrypted, the dApp asks MetaMask who in turns ask the user to decrypt every received message (even if we are the sender).
- This only uses Relay protocol to receive messages, meaning that participants must have the dApp open at the same time to receive private messages or public keys from each other.

View File

@ -1,60 +0,0 @@
const webpack = require('webpack');
module.exports = {
dev: (config) => {
// Override webpack 5 config from react-scripts to load polyfills
if (!config.resolve) config.resolve = {};
if (!config.resolve.fallback) config.resolve.fallback = {};
Object.assign(config.resolve.fallback, {
buffer: require.resolve('buffer'),
crypto: false,
stream: require.resolve('stream-browserify'),
});
if (!config.plugins) config.plugins = [];
config.plugins.push(
new webpack.DefinePlugin({
'process.env.ENV': JSON.stringify('dev'),
})
);
config.plugins.push(
new webpack.ProvidePlugin({
process: 'process/browser.js',
Buffer: ['buffer', 'Buffer'],
})
);
if (!config.ignoreWarnings) config.ignoreWarnings = [];
config.ignoreWarnings.push(/Failed to parse source map/);
return config;
},
prod: (config) => {
// Override webpack 5 config from react-scripts to load polyfills
if (!config.resolve) config.resolve = {};
if (!config.resolve.fallback) config.resolve.fallback = {};
Object.assign(config.resolve.fallback, {
buffer: require.resolve('buffer'),
crypto: false,
stream: require.resolve('stream-browserify'),
});
if (!config.plugins) config.plugins = [];
config.plugins.push(
new webpack.DefinePlugin({
'process.env.ENV': JSON.stringify('prod'),
})
);
config.plugins.push(
new webpack.ProvidePlugin({
process: 'process/browser.js',
Buffer: ['buffer', 'Buffer'],
})
);
if (!config.ignoreWarnings) config.ignoreWarnings = [];
config.ignoreWarnings.push(/Failed to parse source map/);
return config;
},
};

File diff suppressed because it is too large Load Diff

View File

@ -1,68 +0,0 @@
{
"name": "eth-pm-wallet-encryption",
"version": "0.1.0",
"private": true,
"homepage": "/examples/eth-pm-wallet-encryption",
"dependencies": {
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
"eth-sig-util": "^3.0.1",
"ethers": "^5.5.4",
"fontsource-roboto": "^4.0.0",
"js-waku": "^0.24.0",
"protobufjs": "^6.11.2",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"scripts": {
"start": "cra-webpack-rewired start",
"build": "run-s build:*",
"build:react": "cra-webpack-rewired build",
"eject": "cra-webpack-rewired eject",
"fix": "run-s fix:*",
"test": "run-s build test:*",
"test:lint": "eslint src --ext .ts --ext .tsx",
"test:prettier": "prettier \"src/**/*.{ts,tsx}\" \"./*.json\" --list-different",
"test:spelling": "cspell \"{README.md,src/**/*.{ts,tsx},public/**/*.html}\" -c ../../.cspell.json",
"fix:prettier": "prettier \"src/**/*.{ts,tsx}\" \"./*.json\" --write",
"fix:lint": "eslint src --ext .ts --ext .tsx --fix"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not ie <= 99",
"not android <= 4.4.4",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@ethersproject/shims": "^5.5.0",
"@types/jest": "^27.4.0",
"@types/node": "^17.0.19",
"@types/react": "^17.0.39",
"@types/react-dom": "^17.0.11",
"assert": "^2.0.0",
"buffer": "^6.0.3",
"cra-webpack-rewired": "^1.0.1",
"cspell": "^6.0.0",
"eslint": "^8.9.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.5.1",
"process": "^0.11.10",
"react-scripts": "5.0.0",
"stream-browserify": "^3.0.0",
"typescript": "^4.5.5"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -1,38 +0,0 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #dddddd;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: black;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@ -1,231 +0,0 @@
import "@ethersproject/shims";
import React, { useEffect, useState } from "react";
import "./App.css";
import { Waku } from "js-waku";
import { Message } from "./messaging/Messages";
import "fontsource-roboto";
import { AppBar, IconButton, Toolbar, Typography } from "@material-ui/core";
import {
createMuiTheme,
ThemeProvider,
makeStyles,
} from "@material-ui/core/styles";
import { lightBlue, orange, teal } from "@material-ui/core/colors";
import WifiIcon from "@material-ui/icons/Wifi";
import BroadcastPublicKey from "./BroadcastPublicKey";
import Messaging from "./messaging/Messaging";
import {
PrivateMessageContentTopic,
handlePrivateMessage,
handlePublicKeyMessage,
initWaku,
PublicKeyContentTopic,
} from "./waku";
import { Web3Provider } from "@ethersproject/providers/src.ts/web3-provider";
import GetEncryptionPublicKey from "./GetEncryptionPublicKey";
import ConnectWallet from "./ConnectWallet";
const theme = createMuiTheme({
palette: {
primary: {
main: orange[500],
},
secondary: {
main: lightBlue[600],
},
},
});
const useStyles = makeStyles({
root: {
textAlign: "center",
display: "flex",
flexDirection: "column",
minHeight: "100vh",
},
appBar: {
// height: '200p',
},
container: {
display: "flex",
flex: 1,
},
main: {
flex: 1,
margin: "10px",
},
wakuStatus: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
},
peers: {},
});
function App() {
const [waku, setWaku] = useState<Waku>();
const [provider, setProvider] = useState<Web3Provider>();
const [encPublicKey, setEncPublicKey] = useState<Uint8Array>();
const [publicKeys, setPublicKeys] = useState<Map<string, Uint8Array>>(
new Map()
);
const [messages, setMessages] = useState<Message[]>([]);
const [address, setAddress] = useState<string>();
const [peerStats, setPeerStats] = useState<{
relayPeers: number;
lightPushPeers: number;
}>({
relayPeers: 0,
lightPushPeers: 0,
});
const classes = useStyles();
// Waku initialization
useEffect(() => {
if (waku) return;
initWaku()
.then((_waku) => {
console.log("waku: ready");
setWaku(_waku);
})
.catch((e) => {
console.error("Failed to initiate Waku", e);
});
}, [waku]);
useEffect(() => {
if (!waku) return;
const observerPublicKeyMessage = handlePublicKeyMessage.bind(
{},
address,
setPublicKeys
);
waku.relay.addObserver(observerPublicKeyMessage, [PublicKeyContentTopic]);
return function cleanUp() {
if (!waku) return;
waku.relay.deleteObserver(observerPublicKeyMessage, [
PublicKeyContentTopic,
]);
};
}, [waku, address]);
useEffect(() => {
if (!waku) return;
if (!address) return;
if (!provider?.provider?.request) return;
const observerPrivateMessage = handlePrivateMessage.bind(
{},
setMessages,
address,
provider.provider.request
);
waku.relay.addObserver(observerPrivateMessage, [
PrivateMessageContentTopic,
]);
return function cleanUp() {
if (!waku) return;
if (!observerPrivateMessage) return;
waku.relay.deleteObserver(observerPrivateMessage, [
PrivateMessageContentTopic,
]);
};
}, [waku, address, provider?.provider?.request]);
useEffect(() => {
if (!waku) return;
const interval = setInterval(async () => {
let lightPushPeers = 0;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for await (const _peer of waku.store.peers) {
lightPushPeers++;
}
setPeerStats({
relayPeers: waku.relay.getPeers().size,
lightPushPeers,
});
}, 1000);
return () => clearInterval(interval);
}, [waku]);
let addressDisplay = "";
if (address) {
addressDisplay =
address.substr(0, 6) + "..." + address.substr(address.length - 4, 4);
}
return (
<ThemeProvider theme={theme}>
<div className={classes.root}>
<AppBar className={classes.appBar} position="static">
<Toolbar>
<IconButton
edge="start"
className={classes.wakuStatus}
aria-label="waku-status"
>
<WifiIcon
color={waku ? undefined : "disabled"}
style={waku ? { color: teal[500] } : {}}
/>
</IconButton>
<Typography className={classes.peers} aria-label="connected-peers">
Peers: {peerStats.relayPeers} relay, {peerStats.lightPushPeers}{" "}
light push
</Typography>
<Typography variant="h6" className={classes.title}>
Ethereum Private Message with Wallet Encryption
</Typography>
<Typography>{addressDisplay}</Typography>
</Toolbar>
</AppBar>
<div className={classes.container}>
<main className={classes.main}>
<fieldset>
<legend>Wallet</legend>
<ConnectWallet
setProvider={setProvider}
setAddress={setAddress}
/>
</fieldset>
<fieldset>
<legend>Encryption Keys</legend>
<GetEncryptionPublicKey
setEncPublicKey={setEncPublicKey}
providerRequest={provider?.provider?.request}
address={address}
/>
<BroadcastPublicKey
address={address}
encryptionPublicKey={encPublicKey}
waku={waku}
providerRequest={provider?.provider?.request}
/>
</fieldset>
<fieldset>
<legend>Messaging</legend>
<Messaging
recipients={publicKeys}
waku={waku}
messages={messages}
/>
</fieldset>
</main>
</div>
</div>
</ThemeProvider>
);
}
export default App;

View File

@ -1,69 +0,0 @@
import { Button } from "@material-ui/core";
import React from "react";
import { createPublicKeyMessage } from "./crypto";
import { PublicKeyMessage } from "./messaging/wire";
import { WakuMessage, Waku } from "js-waku";
import { PublicKeyContentTopic } from "./waku";
interface Props {
encryptionPublicKey: Uint8Array | undefined;
waku: Waku | undefined;
address: string | undefined;
providerRequest:
| ((request: { method: string; params?: Array<any> }) => Promise<any>)
| undefined;
}
export default function BroadcastPublicKey({
encryptionPublicKey,
address,
waku,
providerRequest,
}: Props) {
const broadcastPublicKey = () => {
if (!encryptionPublicKey) return;
if (!address) return;
if (!waku) return;
if (!providerRequest) return;
console.log("Creating Public Key Message");
createPublicKeyMessage(address, encryptionPublicKey, providerRequest)
.then((msg) => {
console.log("Public Key Message created");
encodePublicKeyWakuMessage(msg)
.then((wakuMsg) => {
console.log("Public Key Message encoded");
waku.lightPush
.push(wakuMsg)
.then((res) => console.log("Public Key Message pushed", res))
.catch((e) => {
console.error("Failed to send Public Key Message", e);
});
})
.catch(() => {
console.log("Failed to encode Public Key Message in Waku Message");
});
})
.catch((e) => {
console.error("Failed to create public key message", e);
});
};
return (
<Button
variant="contained"
color="primary"
onClick={broadcastPublicKey}
disabled={!encryptionPublicKey || !waku || !address || !providerRequest}
>
Broadcast Encryption Public Key
</Button>
);
}
async function encodePublicKeyWakuMessage(
publicKeyMessage: PublicKeyMessage
): Promise<WakuMessage> {
const payload = publicKeyMessage.encode();
return await WakuMessage.fromBytes(payload, PublicKeyContentTopic);
}

View File

@ -1,33 +0,0 @@
import { Button } from "@material-ui/core";
import React from "react";
import { ethers } from "ethers";
import { Web3Provider } from "@ethersproject/providers/src.ts/web3-provider";
declare let window: any;
interface Props {
setAddress: (address: string) => void;
setProvider: (provider: Web3Provider) => void;
}
export default function ConnectWallet({ setAddress, setProvider }: Props) {
const connectWallet = () => {
try {
window.ethereum
.request({ method: "eth_requestAccounts" })
.then((accounts: string[]) => {
const _provider = new ethers.providers.Web3Provider(window.ethereum);
setAddress(accounts[0]);
setProvider(_provider);
});
} catch (e) {
console.error("No web3 provider available");
}
};
return (
<Button variant="contained" color="primary" onClick={connectWallet}>
Connect Wallet
</Button>
);
}

View File

@ -1,56 +0,0 @@
import { Button } from "@material-ui/core";
import React from "react";
interface Props {
setEncPublicKey: (key: Uint8Array) => void;
providerRequest:
| ((request: { method: string; params?: Array<any> }) => Promise<any>)
| undefined;
address: string | undefined;
}
export default function GetEncryptionPublicKey({
setEncPublicKey,
providerRequest,
address,
}: Props) {
const requestPublicKey = () => {
if (!providerRequest) return;
if (!address) return;
console.log("Getting Encryption Public Key from Wallet");
providerRequest({
method: "eth_getEncryptionPublicKey",
params: [address],
})
.then((key: string | undefined) => {
console.log("Encryption Public key:", key);
if (typeof key !== "string") {
console.error("Could not get encryption key");
return;
}
setEncPublicKey(Buffer.from(key, "base64"));
})
.catch((error) => {
if (error.code === 4001) {
// EIP-1193 userRejectedRequest error
console.log("We can't encrypt anything without the key.");
} else {
console.error(error);
}
});
};
return (
<Button
variant="contained"
color="primary"
onClick={requestPublicKey}
disabled={!providerRequest || !address}
>
Get Encryption Public Key from Wallet
</Button>
);
}

View File

@ -1,105 +0,0 @@
import "@ethersproject/shims";
import { PublicKeyMessage } from "./messaging/wire";
import { utils } from "js-waku";
import * as sigUtil from "eth-sig-util";
import { equals } from "uint8arrays/equals";
/**
* Sign the encryption public key with Web3. This can then be published to let other
* users know to use this encryption public key to encrypt messages for the
* Ethereum Address holder.
*/
export async function createPublicKeyMessage(
address: string,
encryptionPublicKey: Uint8Array,
providerRequest: (request: {
method: string;
params?: Array<any>;
}) => Promise<any>
): Promise<PublicKeyMessage> {
const signature = await signEncryptionKey(
encryptionPublicKey,
address,
providerRequest
);
console.log("Asking wallet to sign Public Key Message");
console.log("Public Key Message signed");
return new PublicKeyMessage({
encryptionPublicKey: encryptionPublicKey,
ethAddress: utils.hexToBytes(address),
signature: utils.hexToBytes(signature),
});
}
function buildMsgParams(encryptionPublicKey: Uint8Array, fromAddress: string) {
return JSON.stringify({
domain: {
name: "Ethereum Private Message over Waku",
version: "1",
},
message: {
message:
"By signing this message you certify that messages addressed to `ownerAddress` must be encrypted with `encryptionPublicKey`",
encryptionPublicKey: utils.bytesToHex(encryptionPublicKey),
ownerAddress: fromAddress,
},
// Refers to the keys of the *types* object below.
primaryType: "PublishEncryptionPublicKey",
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
],
PublishEncryptionPublicKey: [
{ name: "message", type: "string" },
{ name: "encryptionPublicKey", type: "string" },
{ name: "ownerAddress", type: "string" },
],
},
});
}
export async function signEncryptionKey(
encryptionPublicKey: Uint8Array,
fromAddress: string,
providerRequest: (request: {
method: string;
params?: Array<any>;
from?: string;
}) => Promise<any>
): Promise<Uint8Array> {
const msgParams = buildMsgParams(encryptionPublicKey, fromAddress);
const result = await providerRequest({
method: "eth_signTypedData_v4",
params: [fromAddress, msgParams],
from: fromAddress,
});
console.log("TYPED SIGNED:" + JSON.stringify(result));
return utils.hexToBytes(result);
}
/**
* Validate that the Encryption Public Key was signed by the holder of the given Ethereum address.
*/
export function validatePublicKeyMessage(msg: PublicKeyMessage): boolean {
const recovered = sigUtil.recoverTypedSignature_v4({
data: JSON.parse(
buildMsgParams(
msg.encryptionPublicKey,
"0x" + utils.bytesToHex(msg.ethAddress)
)
),
sig: "0x" + utils.bytesToHex(msg.signature),
});
console.log("Recovered", recovered);
console.log("ethAddress", "0x" + utils.bytesToHex(msg.ethAddress));
return equals(utils.hexToBytes(recovered), msg.ethAddress);
}

View File

@ -1,13 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -1,11 +0,0 @@
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1,40 +0,0 @@
import React from "react";
import { List, ListItem, ListItemText } from "@material-ui/core";
/**
* Clear text message
*/
export interface Message {
text: string;
timestamp: Date;
}
export interface Props {
messages: Message[];
}
export default function Messages({ messages }: Props) {
return <List dense={true}>{generate(messages)}</List>;
}
function generate(messages: Message[]) {
return messages.map((msg) => {
const text = `<${formatDisplayDate(msg.timestamp)}> ${msg.text}`;
return (
<ListItem>
<ListItemText key={formatDisplayDate(msg.timestamp)} primary={text} />
</ListItem>
);
});
}
function formatDisplayDate(timestamp: Date): string {
return timestamp.toLocaleString([], {
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
hour12: false,
});
}

View File

@ -1,30 +0,0 @@
import Messages, { Message } from "./Messages";
import { Waku } from "js-waku";
import SendMessage from "./SendMessage";
import { makeStyles } from "@material-ui/core";
const useStyles = makeStyles({
root: {
display: "flex",
alignItems: "left",
flexDirection: "column",
margin: "5px",
},
});
interface Props {
waku: Waku | undefined;
recipients: Map<string, Uint8Array>;
messages: Message[];
}
export default function Messaging({ waku, recipients, messages }: Props) {
const classes = useStyles();
return (
<div className={classes.root}>
<SendMessage recipients={recipients} waku={waku} />
<Messages messages={messages} />
</div>
);
}

View File

@ -1,154 +0,0 @@
import {
FormControl,
InputLabel,
makeStyles,
MenuItem,
Select,
TextField,
} from "@material-ui/core";
import React, { ChangeEvent, useState, KeyboardEvent } from "react";
import { utils, Waku, WakuMessage } from "js-waku";
import { PrivateMessage } from "./wire";
import { PrivateMessageContentTopic } from "../waku";
import * as sigUtil from "eth-sig-util";
const useStyles = makeStyles((theme) => ({
formControl: {
margin: theme.spacing(1),
minWidth: 120,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
}));
export interface Props {
waku: Waku | undefined;
// address, public key
recipients: Map<string, Uint8Array>;
}
export default function SendMessage({ waku, recipients }: Props) {
const classes = useStyles();
const [recipient, setRecipient] = useState<string>("");
const [message, setMessage] = useState<string>();
const handleRecipientChange = (
event: ChangeEvent<{ name?: string; value: unknown }>
) => {
setRecipient(event.target.value as string);
};
const handleMessageChange = (event: ChangeEvent<HTMLInputElement>) => {
setMessage(event.target.value);
};
const items = Array.from(recipients.keys()).map((recipient) => {
return (
<MenuItem key={recipient} value={recipient}>
{recipient}
</MenuItem>
);
});
const keyDownHandler = async (event: KeyboardEvent<HTMLInputElement>) => {
if (
event.key === "Enter" &&
!event.altKey &&
!event.ctrlKey &&
!event.shiftKey
) {
if (!waku) return;
if (!recipient) return;
if (!message) return;
const publicKey = recipients.get(recipient);
if (!publicKey) return;
sendMessage(waku, recipient, publicKey, message, (res) => {
if (res) {
console.log("callback called with", res);
setMessage("");
}
});
}
};
return (
<div
style={{
display: "flex",
alignItems: "center",
flexWrap: "wrap",
}}
>
<FormControl className={classes.formControl}>
<InputLabel id="select-recipient-label">Recipient</InputLabel>
<Select
labelId="select-recipient"
id="select-recipient"
value={recipient}
onChange={handleRecipientChange}
>
{items}
</Select>
</FormControl>
<TextField
id="message-input"
label="Message"
variant="filled"
onChange={handleMessageChange}
onKeyDown={keyDownHandler}
value={message}
/>
</div>
);
}
async function encodeEncryptedWakuMessage(
message: string,
publicKey: Uint8Array,
address: string
): Promise<WakuMessage> {
const privateMessage = new PrivateMessage({
toAddress: utils.hexToBytes(address),
message: message,
});
const payload = privateMessage.encode();
const encObj = sigUtil.encrypt(
Buffer.from(publicKey).toString("base64"),
{ data: utils.bytesToHex(payload) },
"x25519-xsalsa20-poly1305"
);
const encryptedPayload = Buffer.from(JSON.stringify(encObj), "utf8");
return WakuMessage.fromBytes(encryptedPayload, PrivateMessageContentTopic);
}
function sendMessage(
waku: Waku,
recipientAddress: string,
recipientPublicKey: Uint8Array,
message: string,
callback: (res: boolean) => void
) {
encodeEncryptedWakuMessage(message, recipientPublicKey, recipientAddress)
.then((msg) => {
console.log("pushing");
waku.lightPush
.push(msg)
.then((res) => {
console.log("Message sent", res);
callback(res ? res.isSuccess : false);
})
.catch((e) => {
console.error("Failed to send message", e);
callback(false);
});
})
.catch((e) => {
console.error("Cannot encode & encrypt message", e);
callback(false);
});
}

View File

@ -1,101 +0,0 @@
import * as protobuf from "protobufjs/light";
export interface PublicKeyMessagePayload {
encryptionPublicKey: Uint8Array;
ethAddress: Uint8Array;
signature: Uint8Array;
}
const Root = protobuf.Root,
Type = protobuf.Type,
Field = protobuf.Field;
/**
* Message used to communicate the encryption public key linked to a given Ethereum account
*/
export class PublicKeyMessage {
private static Type = new Type("PublicKeyMessage")
.add(new Field("encryptionPublicKey", 1, "bytes"))
.add(new Field("ethAddress", 2, "bytes"))
.add(new Field("signature", 3, "bytes"));
private static Root = new Root()
.define("messages")
.add(PublicKeyMessage.Type);
constructor(public payload: PublicKeyMessagePayload) {}
public encode(): Uint8Array {
const message = PublicKeyMessage.Type.create(this.payload);
return PublicKeyMessage.Type.encode(message).finish();
}
public static decode(
bytes: Uint8Array | Buffer
): PublicKeyMessage | undefined {
const payload = PublicKeyMessage.Type.decode(
bytes
) as unknown as PublicKeyMessagePayload;
if (
!payload.signature ||
!payload.encryptionPublicKey ||
!payload.ethAddress
) {
console.log("Field missing on decoded Public Key Message", payload);
return;
}
return new PublicKeyMessage(payload);
}
get encryptionPublicKey(): Uint8Array {
return this.payload.encryptionPublicKey;
}
get ethAddress(): Uint8Array {
return this.payload.ethAddress;
}
get signature(): Uint8Array {
return this.payload.signature;
}
}
export interface PrivateMessagePayload {
toAddress: Uint8Array;
message: string;
}
/**
* Encrypted Message used for private communication over the Waku network.
*/
export class PrivateMessage {
private static Type = new Type("PrivateMessage")
.add(new Field("toAddress", 1, "bytes"))
.add(new Field("message", 2, "string"));
private static Root = new Root().define("messages").add(PrivateMessage.Type);
constructor(public payload: PrivateMessagePayload) {}
public encode(): Uint8Array {
const message = PrivateMessage.Type.create(this.payload);
return PrivateMessage.Type.encode(message).finish();
}
public static decode(bytes: Uint8Array | Buffer): PrivateMessage | undefined {
const payload = PrivateMessage.Type.decode(
bytes
) as unknown as PrivateMessagePayload;
if (!payload.toAddress || !payload.message) {
console.log("Field missing on decoded PrivateMessage", payload);
return;
}
return new PrivateMessage(payload);
}
get toAddress(): Uint8Array {
return this.payload.toAddress;
}
get message(): string {
return this.payload.message;
}
}

View File

@ -1 +0,0 @@
/// <reference types="react-scripts" />

View File

@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom";

View File

@ -1,94 +0,0 @@
import { Dispatch, SetStateAction } from "react";
import { utils, Waku, WakuMessage } from "js-waku";
import { PrivateMessage, PublicKeyMessage } from "./messaging/wire";
import { validatePublicKeyMessage } from "./crypto";
import { Message } from "./messaging/Messages";
import { equals } from "uint8arrays/equals";
export const PublicKeyContentTopic =
"/eth-pm-wallet/1/encryption-public-key/proto";
export const PrivateMessageContentTopic =
"/eth-pm-wallet/1/private-message/proto";
export async function initWaku(): Promise<Waku> {
const waku = await Waku.create({ bootstrap: { default: true } });
// Wait to be connected to at least one peer
await new Promise((resolve, reject) => {
// If we are not connected to any peer within 10sec let's just reject
// As we are not implementing connection management in this example
setTimeout(reject, 10000);
waku.libp2p.connectionManager.on("peer:connect", () => {
resolve(null);
});
});
return waku;
}
export function handlePublicKeyMessage(
myAddress: string | undefined,
setPublicKeys: Dispatch<SetStateAction<Map<string, Uint8Array>>>,
msg: WakuMessage
) {
console.log("Public Key Message received:", msg);
if (!msg.payload) return;
const publicKeyMsg = PublicKeyMessage.decode(msg.payload);
if (!publicKeyMsg) return;
if (myAddress && equals(publicKeyMsg.ethAddress, utils.hexToBytes(myAddress)))
return;
const res = validatePublicKeyMessage(publicKeyMsg);
console.log("Is Public Key Message valid?", res);
if (res) {
setPublicKeys((prevPks: Map<string, Uint8Array>) => {
prevPks.set(
utils.bytesToHex(publicKeyMsg.ethAddress),
publicKeyMsg.encryptionPublicKey
);
return new Map(prevPks);
});
}
}
export async function handlePrivateMessage(
setter: Dispatch<SetStateAction<Message[]>>,
address: string,
providerRequest: (request: {
method: string;
params?: Array<any>;
}) => Promise<any>,
wakuMsg: WakuMessage
) {
console.log("Private Message received:", wakuMsg);
if (!wakuMsg.payload) return;
const decryptedPayload = await providerRequest({
method: "eth_decrypt",
params: [wakuMsg.payloadAsUtf8, address],
}).catch((error) => console.log(error.message));
console.log("Decrypted Payload:", decryptedPayload);
const privateMessage = PrivateMessage.decode(
Buffer.from(decryptedPayload, "hex")
);
if (!privateMessage) {
console.log("Failed to decode Private Message");
return;
}
if (!equals(privateMessage.toAddress, utils.hexToBytes(address))) return;
const timestamp = wakuMsg.timestamp ? wakuMsg.timestamp : new Date();
console.log("Message decrypted:", privateMessage.message);
setter((prevMsgs: Message[]) => {
const copy = prevMsgs.slice();
copy.push({
text: privateMessage.message,
timestamp: timestamp,
});
return copy;
});
}

View File

@ -1,20 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
}

View File

@ -1,3 +0,0 @@
# Remove ReactJS warning about webpack
# because this is not a monorepo, ReactJS projects are examples
SKIP_PREFLIGHT_CHECK=true

View File

@ -1,24 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/cache/

View File

@ -1,31 +0,0 @@
# Ethereum Private Message Web App
**Demonstrates**:
- Private Messaging
- React/TypeScript
- Waku Light Push
- Signature with Web3
- Asymmetric Encryption
- Symmetric Encryption
A PoC implementation of [20/ETH-DM](https://rfc.vac.dev/spec/20/).
Ethereum Private Message, or Eth-PM, is a protocol that allows sending encrypted message to a recipient,
only knowing their Ethereum Address.
This protocol has been created to demonstrated how encryption and signature could be added to message
sent over the Waku v2 network.
The `master` branch's HEAD is deployed at https://js-waku.wakuconnect.dev/examples/eth-pm/.
To run a development version locally, do:
```shell
git clone https://github.com/status-im/js-waku/ ; cd js-waku
npm install # Install dependencies for js-waku
npm run build # Build js-waku
cd examples/eth-pm
npm install # Install dependencies for the web app
npm run start # Start development server to serve the web app on http://localhost:3000/js-waku/eth-pm
```

View File

@ -1,60 +0,0 @@
const webpack = require('webpack');
module.exports = {
dev: (config) => {
// Override webpack 5 config from react-scripts to load polyfills
if (!config.resolve) config.resolve = {};
if (!config.resolve.fallback) config.resolve.fallback = {};
Object.assign(config.resolve.fallback, {
buffer: require.resolve('buffer'),
crypto: false,
stream: require.resolve('stream-browserify'),
});
if (!config.plugins) config.plugins = [];
config.plugins.push(
new webpack.DefinePlugin({
'process.env.ENV': JSON.stringify('dev'),
})
);
config.plugins.push(
new webpack.ProvidePlugin({
process: 'process/browser.js',
Buffer: ['buffer', 'Buffer'],
})
);
if (!config.ignoreWarnings) config.ignoreWarnings = [];
config.ignoreWarnings.push(/Failed to parse source map/);
return config;
},
prod: (config) => {
// Override webpack 5 config from react-scripts to load polyfills
if (!config.resolve) config.resolve = {};
if (!config.resolve.fallback) config.resolve.fallback = {};
Object.assign(config.resolve.fallback, {
buffer: require.resolve('buffer'),
crypto: false,
stream: require.resolve('stream-browserify'),
});
if (!config.plugins) config.plugins = [];
config.plugins.push(
new webpack.DefinePlugin({
'process.env.ENV': JSON.stringify('prod'),
})
);
config.plugins.push(
new webpack.ProvidePlugin({
process: 'process/browser.js',
Buffer: ['buffer', 'Buffer'],
})
);
if (!config.ignoreWarnings) config.ignoreWarnings = [];
config.ignoreWarnings.push(/Failed to parse source map/);
return config;
},
};

File diff suppressed because it is too large Load Diff

View File

@ -1,68 +0,0 @@
{
"name": "eth-pm",
"version": "0.1.0",
"private": true,
"homepage": "/examples/eth-pm",
"dependencies": {
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
"eth-sig-util": "^3.0.1",
"ethers": "^5.5.4",
"fontsource-roboto": "^4.0.0",
"js-waku": "^0.24.0",
"protobufjs": "^6.11.2",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"scripts": {
"start": "cra-webpack-rewired start",
"build": "run-s build:*",
"build:react": "cra-webpack-rewired build",
"eject": "cra-webpack-rewired eject",
"fix": "run-s fix:*",
"test": "run-s build test:*",
"test:lint": "eslint src --ext .ts --ext .tsx",
"test:prettier": "prettier \"src/**/*.{ts,tsx}\" \"./*.json\" --list-different",
"test:spelling": "cspell \"{README.md,src/**/*.{ts,tsx},public/**/*.html}\" -c ../../.cspell.json",
"fix:prettier": "prettier \"src/**/*.{ts,tsx}\" \"./*.json\" --write",
"fix:lint": "eslint src --ext .ts --ext .tsx --fix"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not ie <= 99",
"not android <= 4.4.4",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@ethersproject/shims": "^5.5.0",
"@types/jest": "^27.4.0",
"@types/node": "^17.0.19",
"@types/react": "^17.0.39",
"@types/react-dom": "^17.0.11",
"assert": "^2.0.0",
"buffer": "^6.0.3",
"cra-webpack-rewired": "^1.0.1",
"cspell": "^6.0.0",
"eslint": "^8.9.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.5.1",
"process": "^0.11.10",
"react-scripts": "5.0.0",
"stream-browserify": "^3.0.0",
"typescript": "^4.5.5"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -1,38 +0,0 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #dddddd;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: black;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@ -1,249 +0,0 @@
import "@ethersproject/shims";
import React, { useEffect, useState } from "react";
import "./App.css";
import { Waku } from "js-waku";
import { KeyPair, PublicKeyMessageEncryptionKey } from "./crypto";
import { Message } from "./messaging/Messages";
import "fontsource-roboto";
import { AppBar, IconButton, Toolbar, Typography } from "@material-ui/core";
import KeyPairHandling from "./key_pair_handling/KeyPairHandling";
import {
createMuiTheme,
ThemeProvider,
makeStyles,
} from "@material-ui/core/styles";
import { teal, purple, green } from "@material-ui/core/colors";
import WifiIcon from "@material-ui/icons/Wifi";
import BroadcastPublicKey from "./BroadcastPublicKey";
import Messaging from "./messaging/Messaging";
import {
PrivateMessageContentTopic,
handlePrivateMessage,
handlePublicKeyMessage,
initWaku,
PublicKeyContentTopic,
} from "./waku";
import { Web3Provider } from "@ethersproject/providers/src.ts/web3-provider";
import ConnectWallet from "./ConnectWallet";
const theme = createMuiTheme({
palette: {
primary: {
main: purple[500],
},
secondary: {
main: teal[600],
},
},
});
const useStyles = makeStyles({
root: {
textAlign: "center",
display: "flex",
flexDirection: "column",
minHeight: "100vh",
},
appBar: {
// height: '200p',
},
container: {
display: "flex",
flex: 1,
},
main: {
flex: 1,
margin: "10px",
},
wakuStatus: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
},
peers: {},
});
function App() {
const [waku, setWaku] = useState<Waku>();
const [provider, setProvider] = useState<Web3Provider>();
const [encryptionKeyPair, setEncryptionKeyPair] = useState<
KeyPair | undefined
>();
const [publicKeys, setPublicKeys] = useState<Map<string, Uint8Array>>(
new Map()
);
const [messages, setMessages] = useState<Message[]>([]);
const [address, setAddress] = useState<string>();
const [peerStats, setPeerStats] = useState<{
relayPeers: number;
lightPushPeers: number;
}>({
relayPeers: 0,
lightPushPeers: 0,
});
const classes = useStyles();
// Waku initialization
useEffect(() => {
if (waku) return;
initWaku()
.then((_waku) => {
console.log("waku: ready");
setWaku(_waku);
})
.catch((e) => {
console.error("Failed to initiate Waku", e);
});
}, [waku]);
useEffect(() => {
if (!waku) return;
const observerPublicKeyMessage = handlePublicKeyMessage.bind(
{},
address,
setPublicKeys
);
waku.relay.addDecryptionKey(PublicKeyMessageEncryptionKey);
waku.relay.addObserver(observerPublicKeyMessage, [PublicKeyContentTopic]);
return function cleanUp() {
if (!waku) return;
waku.relay.deleteDecryptionKey(PublicKeyMessageEncryptionKey);
waku.relay.deleteObserver(observerPublicKeyMessage, [
PublicKeyContentTopic,
]);
};
}, [waku, address]);
useEffect(() => {
if (!waku) return;
if (!encryptionKeyPair) return;
waku.relay.addDecryptionKey(encryptionKeyPair.privateKey);
return function cleanUp() {
if (!waku) return;
if (!encryptionKeyPair) return;
waku.relay.deleteDecryptionKey(encryptionKeyPair.privateKey);
};
}, [waku, encryptionKeyPair]);
useEffect(() => {
if (!waku) return;
if (!encryptionKeyPair) return;
if (!address) return;
const observerPrivateMessage = handlePrivateMessage.bind(
{},
setMessages,
address
);
waku.relay.addObserver(observerPrivateMessage, [
PrivateMessageContentTopic,
]);
return function cleanUp() {
if (!waku) return;
if (!observerPrivateMessage) return;
waku.relay.deleteObserver(observerPrivateMessage, [
PrivateMessageContentTopic,
]);
};
}, [waku, address, encryptionKeyPair]);
useEffect(() => {
if (!waku) return;
const interval = setInterval(async () => {
let lightPushPeers = 0;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for await (const _peer of waku.store.peers) {
lightPushPeers++;
}
setPeerStats({
relayPeers: waku.relay.getPeers().size,
lightPushPeers,
});
}, 1000);
return () => clearInterval(interval);
}, [waku]);
let addressDisplay = "";
if (address) {
addressDisplay =
address.substr(0, 6) + "..." + address.substr(address.length - 4, 4);
}
return (
<ThemeProvider theme={theme}>
<div className={classes.root}>
<AppBar className={classes.appBar} position="static">
<Toolbar>
<IconButton
edge="start"
className={classes.wakuStatus}
aria-label="waku-status"
>
<WifiIcon
color={waku ? undefined : "disabled"}
style={waku ? { color: green[500] } : {}}
/>
</IconButton>
<Typography className={classes.peers} aria-label="connected-peers">
Peers: {peerStats.relayPeers} relay, {peerStats.lightPushPeers}{" "}
light push
</Typography>
<Typography variant="h6" className={classes.title}>
Ethereum Private Message
</Typography>
<Typography>{addressDisplay}</Typography>
</Toolbar>
</AppBar>
<div className={classes.container}>
<main className={classes.main}>
<fieldset>
<legend>Wallet</legend>
<ConnectWallet
setAddress={setAddress}
setProvider={setProvider}
/>
</fieldset>
<fieldset>
<legend>Encryption Key Pair</legend>
<KeyPairHandling
encryptionKeyPair={encryptionKeyPair}
setEncryptionKeyPair={setEncryptionKeyPair}
/>
<BroadcastPublicKey
address={address}
EncryptionKeyPair={encryptionKeyPair}
waku={waku}
providerRequest={provider?.provider?.request}
/>
</fieldset>
<fieldset>
<legend>Messaging</legend>
<Messaging
recipients={publicKeys}
waku={waku}
messages={messages}
/>
</fieldset>
</main>
</div>
</div>
</ThemeProvider>
);
}
export default App;

View File

@ -1,94 +0,0 @@
import { Button } from "@material-ui/core";
import React, { useState } from "react";
import {
createPublicKeyMessage,
KeyPair,
PublicKeyMessageEncryptionKey,
} from "./crypto";
import { PublicKeyMessage } from "./messaging/wire";
import { WakuMessage, Waku } from "js-waku";
import { PublicKeyContentTopic } from "./waku";
interface Props {
EncryptionKeyPair: KeyPair | undefined;
waku: Waku | undefined;
address: string | undefined;
providerRequest:
| ((request: { method: string; params?: Array<any> }) => Promise<any>)
| undefined;
}
export default function BroadcastPublicKey({
EncryptionKeyPair,
waku,
address,
providerRequest,
}: Props) {
const [publicKeyMsg, setPublicKeyMsg] = useState<PublicKeyMessage>();
const broadcastPublicKey = () => {
if (!EncryptionKeyPair) return;
if (!address) return;
if (!waku) return;
if (!providerRequest) return;
if (publicKeyMsg) {
encodePublicKeyWakuMessage(publicKeyMsg)
.then((wakuMsg) => {
waku.lightPush.push(wakuMsg).catch((e) => {
console.error("Failed to send Public Key Message", e);
});
})
.catch((e) => {
console.log("Failed to encode Public Key Message in Waku Message", e);
});
} else {
createPublicKeyMessage(
address,
EncryptionKeyPair.publicKey,
providerRequest
)
.then((msg) => {
setPublicKeyMsg(msg);
encodePublicKeyWakuMessage(msg)
.then((wakuMsg) => {
waku.lightPush
.push(wakuMsg)
.then((res) => console.log("Public Key Message pushed", res))
.catch((e) => {
console.error("Failed to send Public Key Message", e);
});
})
.catch((e) => {
console.log(
"Failed to encode Public Key Message in Waku Message",
e
);
});
})
.catch((e) => {
console.error("Failed to create public key message", e);
});
}
};
return (
<Button
variant="contained"
color="primary"
onClick={broadcastPublicKey}
disabled={!EncryptionKeyPair || !waku || !address || !providerRequest}
>
Broadcast Encryption Public Key
</Button>
);
}
async function encodePublicKeyWakuMessage(
publicKeyMessage: PublicKeyMessage
): Promise<WakuMessage> {
const payload = publicKeyMessage.encode();
return await WakuMessage.fromBytes(payload, PublicKeyContentTopic, {
symKey: PublicKeyMessageEncryptionKey,
});
}

View File

@ -1,33 +0,0 @@
import { Button } from "@material-ui/core";
import React from "react";
import { ethers } from "ethers";
import { Web3Provider } from "@ethersproject/providers/src.ts/web3-provider";
declare let window: any;
interface Props {
setAddress: (address: string) => void;
setProvider: (provider: Web3Provider) => void;
}
export default function ConnectWallet({ setAddress, setProvider }: Props) {
const connectWallet = () => {
try {
window.ethereum
.request({ method: "eth_requestAccounts" })
.then((accounts: string[]) => {
const _provider = new ethers.providers.Web3Provider(window.ethereum);
setAddress(accounts[0]);
setProvider(_provider);
});
} catch (e) {
console.error("No web3 provider available");
}
};
return (
<Button variant="contained" color="primary" onClick={connectWallet}>
Connect Wallet
</Button>
);
}

View File

@ -1,125 +0,0 @@
import "@ethersproject/shims";
import { PublicKeyMessage } from "./messaging/wire";
import { generatePrivateKey, getPublicKey, utils } from "js-waku";
import * as sigUtil from "eth-sig-util";
import { PublicKeyContentTopic } from "./waku";
import { keccak256 } from "ethers/lib/utils";
import { equals } from "uint8arrays/equals";
export const PublicKeyMessageEncryptionKey = utils.hexToBytes(
keccak256(Buffer.from(PublicKeyContentTopic, "utf-8"))
);
export interface KeyPair {
privateKey: Uint8Array;
publicKey: Uint8Array;
}
/**
* Generate new encryption keypair.
*/
export async function generateEncryptionKeyPair(): Promise<KeyPair> {
const privateKey = generatePrivateKey();
const publicKey = getPublicKey(privateKey);
return { privateKey, publicKey };
}
/**
* Sign the encryption public key with Web3. This can then be published to let other
* users know to use this encryption public key to encrypt messages for the
* Ethereum Address holder.
*/
export async function createPublicKeyMessage(
address: string,
encryptionPublicKey: Uint8Array,
providerRequest: (request: {
method: string;
params?: Array<any>;
}) => Promise<any>
): Promise<PublicKeyMessage> {
const signature = await signEncryptionKey(
encryptionPublicKey,
address,
providerRequest
);
console.log("Asking wallet to sign Public Key Message");
console.log("Public Key Message signed");
return new PublicKeyMessage({
encryptionPublicKey: encryptionPublicKey,
ethAddress: utils.hexToBytes(address),
signature: utils.hexToBytes(signature),
});
}
function buildMsgParams(encryptionPublicKey: Uint8Array, fromAddress: string) {
return JSON.stringify({
domain: {
name: "Ethereum Private Message over Waku",
version: "1",
},
message: {
message:
"By signing this message you certify that messages addressed to `ownerAddress` must be encrypted with `encryptionPublicKey`",
encryptionPublicKey: utils.bytesToHex(encryptionPublicKey),
ownerAddress: fromAddress,
},
// Refers to the keys of the *types* object below.
primaryType: "PublishEncryptionPublicKey",
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
],
PublishEncryptionPublicKey: [
{ name: "message", type: "string" },
{ name: "encryptionPublicKey", type: "string" },
{ name: "ownerAddress", type: "string" },
],
},
});
}
export async function signEncryptionKey(
encryptionPublicKey: Uint8Array,
fromAddress: string,
providerRequest: (request: {
method: string;
params?: Array<any>;
from?: string;
}) => Promise<any>
): Promise<Uint8Array> {
const msgParams = buildMsgParams(encryptionPublicKey, fromAddress);
const result = await providerRequest({
method: "eth_signTypedData_v4",
params: [fromAddress, msgParams],
from: fromAddress,
});
console.log("TYPED SIGNED:" + JSON.stringify(result));
return utils.hexToBytes(result);
}
/**
* Validate that the Encryption Public Key was signed by the holder of the given Ethereum address.
*/
export function validatePublicKeyMessage(msg: PublicKeyMessage): boolean {
const recovered = sigUtil.recoverTypedSignature_v4({
data: JSON.parse(
buildMsgParams(
msg.encryptionPublicKey,
"0x" + utils.bytesToHex(msg.ethAddress)
)
),
sig: "0x" + utils.bytesToHex(msg.signature),
});
console.log("Recovered", recovered);
console.log("ethAddress", "0x" + utils.bytesToHex(msg.ethAddress));
return equals(utils.hexToBytes(recovered), msg.ethAddress);
}

View File

@ -1,13 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -1,11 +0,0 @@
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);

View File

@ -1,90 +0,0 @@
import { Button } from "@material-ui/core";
import { LoadKeyPair } from "./LoadKeyPair";
import { SaveKeyPair } from "./SaveKeyPair";
import React, { useState } from "react";
import { generateEncryptionKeyPair, KeyPair } from "../crypto";
import { makeStyles } from "@material-ui/core/styles";
import PasswordInput from "./PasswordInput";
const useStyles = makeStyles({
root: {
textAlign: "center",
display: "flex",
alignItems: "center",
flexDirection: "column",
margin: "5px",
},
generate: { margin: "5px" },
storage: {
margin: "5px",
},
loadSave: {
display: "flex",
flexDirection: "row",
margin: "5px",
},
loadSaveButton: {
margin: "5px",
},
});
export interface Props {
encryptionKeyPair: KeyPair | undefined;
setEncryptionKeyPair: (keyPair: KeyPair) => void;
}
export default function KeyPairHandling({
encryptionKeyPair,
setEncryptionKeyPair,
}: Props) {
const classes = useStyles();
const [password, setPassword] = useState<string>();
const generateKeyPair = () => {
if (encryptionKeyPair) return;
generateEncryptionKeyPair()
.then((keyPair) => {
setEncryptionKeyPair(keyPair);
})
.catch((e) => {
console.error("Failed to generate Key Pair", e);
});
};
return (
<div className={classes.root}>
<Button
className={classes.generate}
variant="contained"
color="primary"
onClick={generateKeyPair}
disabled={!!encryptionKeyPair}
>
Generate Encryption Key Pair
</Button>
<div className={classes.storage}>
<PasswordInput
password={password}
setPassword={(p) => setPassword(p)}
/>
<div className={classes.loadSave}>
<div className={classes.loadSaveButton}>
<LoadKeyPair
setEncryptionKeyPair={(keyPair) => setEncryptionKeyPair(keyPair)}
disabled={!!encryptionKeyPair}
password={password}
/>
</div>
<div className={classes.loadSaveButton}>
<SaveKeyPair
EncryptionKeyPair={encryptionKeyPair}
password={password}
/>
</div>
</div>
</div>
</div>
);
}

View File

@ -1,37 +0,0 @@
import { Button } from "@material-ui/core";
import React from "react";
import { loadKeyPairFromStorage } from "./key_pair_storage";
import { KeyPair } from "../crypto";
export interface Props {
setEncryptionKeyPair: (keyPair: KeyPair) => void;
disabled: boolean;
password: string | undefined;
}
export function LoadKeyPair({
password,
disabled,
setEncryptionKeyPair,
}: Props) {
const loadKeyPair = () => {
if (disabled) return;
if (!password) return;
loadKeyPairFromStorage(password).then((keyPair: KeyPair | undefined) => {
if (!keyPair) return;
console.log("Encryption KeyPair loaded from storage");
setEncryptionKeyPair(keyPair);
});
};
return (
<Button
variant="contained"
color="primary"
onClick={loadKeyPair}
disabled={!password || disabled}
>
Load Encryption Key Pair from storage
</Button>
);
}

View File

@ -1,24 +0,0 @@
import { TextField } from "@material-ui/core";
import React, { ChangeEvent } from "react";
interface Props {
password: string | undefined;
setPassword: (password: string) => void;
}
export default function PasswordInput({ password, setPassword }: Props) {
const handlePasswordChange = (event: ChangeEvent<HTMLInputElement>) => {
setPassword(event.target.value);
};
return (
<TextField
id="password-input"
label="Password"
variant="filled"
type="password"
onChange={handlePasswordChange}
value={password}
/>
);
}

View File

@ -1,30 +0,0 @@
import { Button } from "@material-ui/core";
import React from "react";
import { KeyPair } from "../crypto";
import { saveKeyPairToStorage } from "./key_pair_storage";
export interface Props {
EncryptionKeyPair: KeyPair | undefined;
password: string | undefined;
}
export function SaveKeyPair({ password, EncryptionKeyPair }: Props) {
const saveKeyPair = () => {
if (!EncryptionKeyPair) return;
if (!password) return;
saveKeyPairToStorage(EncryptionKeyPair, password).then(() => {
console.log("Encryption KeyPair saved to storage");
});
};
return (
<Button
variant="contained"
color="primary"
onClick={saveKeyPair}
disabled={!password || !EncryptionKeyPair}
>
Save Encryption Key Pair to storage
</Button>
);
}

View File

@ -1,122 +0,0 @@
import { KeyPair } from "../crypto";
import { utils } from "js-waku";
/**
* Save keypair to storage, encrypted with password
*/
export async function saveKeyPairToStorage(
EncryptionKeyPair: KeyPair,
password: string
) {
const { salt, iv, cipher } = await encryptKey(EncryptionKeyPair, password);
const data = {
salt: utils.bytesToHex(salt),
iv: utils.bytesToHex(iv),
cipher: utils.bytesToHex(cipher),
};
localStorage.setItem("cipherEncryptionKeyPair", JSON.stringify(data));
}
/**
* Load keypair from storage, decrypted using password
*/
export async function loadKeyPairFromStorage(
password: string
): Promise<KeyPair | undefined> {
const str = localStorage.getItem("cipherEncryptionKeyPair");
if (!str) return;
const data = JSON.parse(str);
const salt = utils.hexToBytes(data.salt);
const iv = utils.hexToBytes(data.iv);
const cipher = utils.hexToBytes(data.cipher);
return await decryptKey(salt, iv, cipher, password);
}
/**
* Use password user as key material for wrap key.
*/
function getKeyMaterial(password: string): Promise<CryptoKey> {
const enc = new TextEncoder();
return window.crypto.subtle.importKey(
"raw",
enc.encode(password),
{ name: "PBKDF2" },
false,
["deriveBits", "deriveKey"]
);
}
/**
* get key to store password
*/
function getWrapKey(keyMaterial: CryptoKey, salt: Uint8Array) {
return window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: salt,
iterations: 100000,
hash: "SHA-256",
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"]
);
}
/**
* Encrypt encryption KeyPair using provided password.
*/
async function encryptKey(encryptionKeyPair: KeyPair, password: string) {
const keyMaterial = await getKeyMaterial(password);
const salt = window.crypto.getRandomValues(new Uint8Array(16));
const wrappingKey = await getWrapKey(keyMaterial, salt);
const enc = new TextEncoder();
const encodedKeyPair = enc.encode(JSON.stringify(encryptionKeyPair));
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const cipher = await window.crypto.subtle.encrypt(
{
name: "AES-GCM",
iv: iv,
},
wrappingKey,
encodedKeyPair
);
return { salt, iv, cipher };
}
/**
* Derive a key from a password, and use the key to decrypt the cipher key pair.
*/
async function decryptKey(
salt: Uint8Array,
iv: Uint8Array,
cipherKeyPair: Uint8Array,
password: string
): Promise<KeyPair | undefined> {
const keyMaterial = await getKeyMaterial(password);
const key = await getWrapKey(keyMaterial, salt);
try {
let decrypted = await window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: iv,
},
key,
cipherKeyPair
);
let dec = new TextDecoder();
return JSON.parse(dec.decode(decrypted));
} catch (e) {
return;
}
}

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1,40 +0,0 @@
import React from "react";
import { List, ListItem, ListItemText } from "@material-ui/core";
/**
* Clear text message
*/
export interface Message {
text: string;
timestamp: Date;
}
export interface Props {
messages: Message[];
}
export default function Messages({ messages }: Props) {
return <List dense={true}>{generate(messages)}</List>;
}
function generate(messages: Message[]) {
return messages.map((msg) => {
const text = `<${formatDisplayDate(msg.timestamp)}> ${msg.text}`;
return (
<ListItem>
<ListItemText key={formatDisplayDate(msg.timestamp)} primary={text} />
</ListItem>
);
});
}
function formatDisplayDate(timestamp: Date): string {
return timestamp.toLocaleString([], {
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
hour12: false,
});
}

View File

@ -1,30 +0,0 @@
import Messages, { Message } from "./Messages";
import { Waku } from "js-waku";
import SendMessage from "./SendMessage";
import { makeStyles } from "@material-ui/core";
const useStyles = makeStyles({
root: {
display: "flex",
alignItems: "left",
flexDirection: "column",
margin: "5px",
},
});
interface Props {
waku: Waku | undefined;
recipients: Map<string, Uint8Array>;
messages: Message[];
}
export default function Messaging({ waku, recipients, messages }: Props) {
const classes = useStyles();
return (
<div className={classes.root}>
<SendMessage recipients={recipients} waku={waku} />
<Messages messages={messages} />
</div>
);
}

View File

@ -1,147 +0,0 @@
import {
FormControl,
InputLabel,
makeStyles,
MenuItem,
Select,
TextField,
} from "@material-ui/core";
import React, { ChangeEvent, useState, KeyboardEvent } from "react";
import { utils, Waku, WakuMessage } from "js-waku";
import { PrivateMessage } from "./wire";
import { PrivateMessageContentTopic } from "../waku";
const useStyles = makeStyles((theme) => ({
formControl: {
margin: theme.spacing(1),
minWidth: 120,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
}));
export interface Props {
waku: Waku | undefined;
// address, public key
recipients: Map<string, Uint8Array>;
}
export default function SendMessage({ waku, recipients }: Props) {
const classes = useStyles();
const [recipient, setRecipient] = useState<string>("");
const [message, setMessage] = useState<string>();
const handleRecipientChange = (
event: ChangeEvent<{ name?: string; value: unknown }>
) => {
setRecipient(event.target.value as string);
};
const handleMessageChange = (event: ChangeEvent<HTMLInputElement>) => {
setMessage(event.target.value);
};
const items = Array.from(recipients.keys()).map((recipient) => {
return (
<MenuItem key={recipient} value={recipient}>
{recipient}
</MenuItem>
);
});
const keyDownHandler = async (event: KeyboardEvent<HTMLInputElement>) => {
if (
event.key === "Enter" &&
!event.altKey &&
!event.ctrlKey &&
!event.shiftKey
) {
if (!waku) return;
if (!recipient) return;
if (!message) return;
const publicKey = recipients.get(recipient);
if (!publicKey) return;
sendMessage(waku, recipient, publicKey, message, (res) => {
if (res) {
console.log("callback called with", res);
setMessage("");
}
});
}
};
return (
<div
style={{
display: "flex",
alignItems: "center",
flexWrap: "wrap",
}}
>
<FormControl className={classes.formControl}>
<InputLabel id="select-recipient-label">Recipient</InputLabel>
<Select
labelId="select-recipient"
id="select-recipient"
value={recipient}
onChange={handleRecipientChange}
>
{items}
</Select>
</FormControl>
<TextField
id="message-input"
label="Message"
variant="filled"
onChange={handleMessageChange}
onKeyDown={keyDownHandler}
value={message}
/>
</div>
);
}
async function encodeEncryptedWakuMessage(
message: string,
publicKey: Uint8Array,
address: string
): Promise<WakuMessage> {
const privateMessage = new PrivateMessage({
toAddress: utils.hexToBytes(address),
message: message,
});
const payload = privateMessage.encode();
return WakuMessage.fromBytes(payload, PrivateMessageContentTopic, {
encPublicKey: publicKey,
});
}
function sendMessage(
waku: Waku,
recipientAddress: string,
recipientPublicKey: Uint8Array,
message: string,
callback: (res: boolean) => void
) {
encodeEncryptedWakuMessage(message, recipientPublicKey, recipientAddress)
.then((msg) => {
console.log("pushing");
waku.lightPush
.push(msg)
.then((res) => {
console.log("Message sent", res);
callback(res ? res.isSuccess : false);
})
.catch((e) => {
console.error("Failed to send message", e);
callback(false);
});
})
.catch((e) => {
console.error("Cannot encode & encrypt message", e);
callback(false);
});
}

View File

@ -1,101 +0,0 @@
import * as protobuf from "protobufjs/light";
export interface PublicKeyMessagePayload {
encryptionPublicKey: Uint8Array;
ethAddress: Uint8Array;
signature: Uint8Array;
}
const Root = protobuf.Root,
Type = protobuf.Type,
Field = protobuf.Field;
/**
* Message used to communicate the encryption public key linked to a given Ethereum account
*/
export class PublicKeyMessage {
private static Type = new Type("PublicKeyMessage")
.add(new Field("encryptionPublicKey", 1, "bytes"))
.add(new Field("ethAddress", 2, "bytes"))
.add(new Field("signature", 3, "bytes"));
private static Root = new Root()
.define("messages")
.add(PublicKeyMessage.Type);
constructor(public payload: PublicKeyMessagePayload) {}
public encode(): Uint8Array {
const message = PublicKeyMessage.Type.create(this.payload);
return PublicKeyMessage.Type.encode(message).finish();
}
public static decode(
bytes: Uint8Array | Buffer
): PublicKeyMessage | undefined {
const payload = PublicKeyMessage.Type.decode(
bytes
) as unknown as PublicKeyMessagePayload;
if (
!payload.signature ||
!payload.encryptionPublicKey ||
!payload.ethAddress
) {
console.log("Field missing on decoded Public Key Message", payload);
return;
}
return new PublicKeyMessage(payload);
}
get encryptionPublicKey(): Uint8Array {
return this.payload.encryptionPublicKey;
}
get ethAddress(): Uint8Array {
return this.payload.ethAddress;
}
get signature(): Uint8Array {
return this.payload.signature;
}
}
export interface PrivateMessagePayload {
toAddress: Uint8Array;
message: string;
}
/**
* Encrypted Message used for private communication over the Waku network.
*/
export class PrivateMessage {
private static Type = new Type("PrivateMessage")
.add(new Field("toAddress", 1, "bytes"))
.add(new Field("message", 2, "string"));
private static Root = new Root().define("messages").add(PrivateMessage.Type);
constructor(public payload: PrivateMessagePayload) {}
public encode(): Uint8Array {
const message = PrivateMessage.Type.create(this.payload);
return PrivateMessage.Type.encode(message).finish();
}
public static decode(bytes: Uint8Array | Buffer): PrivateMessage | undefined {
const payload = PrivateMessage.Type.decode(
bytes
) as unknown as PrivateMessagePayload;
if (!payload.toAddress || !payload.message) {
console.log("Field missing on decoded Private Message", payload);
return;
}
return new PrivateMessage(payload);
}
get toAddress(): Uint8Array {
return this.payload.toAddress;
}
get message(): string {
return this.payload.message;
}
}

View File

@ -1 +0,0 @@
/// <reference types="react-scripts" />

View File

@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom";

View File

@ -1,79 +0,0 @@
import { Dispatch, SetStateAction } from "react";
import { utils, Waku, WakuMessage } from "js-waku";
import { PrivateMessage, PublicKeyMessage } from "./messaging/wire";
import { validatePublicKeyMessage } from "./crypto";
import { Message } from "./messaging/Messages";
import { equals } from "uint8arrays/equals";
export const PublicKeyContentTopic = "/eth-pm/1/public-key/proto";
export const PrivateMessageContentTopic = "/eth-pm/1/private-message/proto";
export async function initWaku(): Promise<Waku> {
const waku = await Waku.create({ bootstrap: { default: true } });
// Wait to be connected to at least one peer
await new Promise((resolve, reject) => {
// If we are not connected to any peer within 10sec let's just reject
// As we are not implementing connection management in this example
setTimeout(reject, 10000);
waku.libp2p.connectionManager.on("peer:connect", () => {
resolve(null);
});
});
return waku;
}
export function handlePublicKeyMessage(
myAddress: string | undefined,
setter: Dispatch<SetStateAction<Map<string, Uint8Array>>>,
msg: WakuMessage
) {
console.log("Public Key Message received:", msg);
if (!msg.payload) return;
const publicKeyMsg = PublicKeyMessage.decode(msg.payload);
if (!publicKeyMsg) return;
if (myAddress && equals(publicKeyMsg.ethAddress, utils.hexToBytes(myAddress)))
return;
const res = validatePublicKeyMessage(publicKeyMsg);
console.log("Is Public Key Message valid?", res);
if (res) {
setter((prevPks: Map<string, Uint8Array>) => {
prevPks.set(
utils.bytesToHex(publicKeyMsg.ethAddress),
publicKeyMsg.encryptionPublicKey
);
return new Map(prevPks);
});
}
}
export async function handlePrivateMessage(
setter: Dispatch<SetStateAction<Message[]>>,
address: string,
wakuMsg: WakuMessage
) {
console.log("Private Message received:", wakuMsg);
if (!wakuMsg.payload) return;
const privateMessage = PrivateMessage.decode(wakuMsg.payload);
if (!privateMessage) {
console.log("Failed to decode Private Message");
return;
}
if (!equals(privateMessage.toAddress, utils.hexToBytes(address))) return;
const timestamp = wakuMsg.timestamp ? wakuMsg.timestamp : new Date();
console.log("Message decrypted:", privateMessage.message);
setter((prevMsgs: Message[]) => {
const copy = prevMsgs.slice();
copy.push({
text: privateMessage.message,
timestamp: timestamp,
});
return copy;
});
}

View File

@ -1,20 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
}

View File

@ -1,26 +0,0 @@
# Minimal Angular (v13) Waku Relay App
**Demonstrates**:
- Group messaging
- Angular/JavaScript
- Waku Relay
- Protobuf using `protons`
- No async/await syntax
A barebones messaging app to illustrate the [Angular Relay guide](https://docs.wakuconnect.dev/docs/guides/10_angular_relay/).
To run a development version locally, do:
```shell
git clone https://github.com/status-im/js-waku/ ; cd js-waku
npm install # Install dependencies for js-waku
npm run build # Build js-waku
cd examples/relay-reactjs-chat
yarn # Install dependencies for the web app
yarn start # Start development server to serve the web app on http://localhost:4200/
```
### Known issues
There is a problem when using `npm` to install/run the Angular app.

View File

@ -1,130 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"relay-angular-chat": {
"projectType": "application",
"schematics": {
"@schematics/angular:application": {
"strict": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/relay-angular-chat",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": [],
"allowedCommonJsDependencies": [
"libp2p-gossipsub/src/utils",
"rlp",
"multiaddr/src/convert",
"varint",
"multihashes",
"@chainsafe/libp2p-noise/dist/src/noise",
"debug",
"libp2p",
"libp2p-bootstrap",
"libp2p-crypto",
"libp2p-websockets",
"libp2p-websockets/src/filters",
"libp2p/src/ping",
"multiaddr",
"peer-id",
"buffer",
"crypto",
"ecies-geth",
"secp256k1",
"libp2p-gossipsub",
"it-concat",
"protons"
]
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "relay-angular-chat:build:production"
},
"development": {
"browserTarget": "relay-angular-chat:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "relay-angular-chat:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
}
}
}
}
},
"defaultProject": "relay-angular-chat"
}

View File

@ -1,44 +0,0 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/relay-angular-chat'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

View File

@ -1,46 +0,0 @@
{
"name": "@waku/relay-angular-chat",
"version": "0.1.0",
"homepage": "/examples/relay-angular-chat",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"test-ci": "ng test --watch=false"
},
"private": true,
"dependencies": {
"@angular/animations": "~13.2.0",
"@angular/common": "~13.2.0",
"@angular/compiler": "~13.2.0",
"@angular/core": "~13.2.0",
"@angular/forms": "~13.2.0",
"@angular/platform-browser": "~13.2.0",
"@angular/platform-browser-dynamic": "~13.2.0",
"@angular/router": "~13.2.0",
"crypto-browserify": "^3.12.0",
"js-waku": "^0.19.2",
"protons": "^2.0.3",
"rxjs": "~7.5.0",
"stream-browserify": "^3.0.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "~13.2.2",
"@angular/cli": "~13.2.2",
"@angular/compiler-cli": "~13.2.0",
"@types/bl": "^5.0.2",
"@types/jasmine": "~4.0.3",
"@types/node": "^17.0.21",
"jasmine-core": "~4.0.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.1.0",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"typescript": "~4.5.2"
}
}

View File

@ -1 +0,0 @@
declare module 'protons';

View File

@ -1,15 +0,0 @@
declare module "time-cache" {
interface ITimeCache {
put(key: string, value: any, validity: number): void;
get(key: string): any;
has(key: string): boolean;
}
type TimeCache = ITimeCache;
function TimeCache(options: object): TimeCache;
export = TimeCache;
}

View File

@ -1,25 +0,0 @@
/* Application-wide Styles */
h1 {
color: #369;
font-family: Arial, Helvetica, sans-serif;
font-size: 250%;
}
h2,
h3 {
color: #444;
font-family: Arial, Helvetica, sans-serif;
font-weight: lighter;
}
body {
margin: 2em;
}
body,
input[type="text"],
button {
color: #333;
font-family: Cambria, Georgia, serif;
}
/* everywhere else */
* {
font-family: Arial, Helvetica, sans-serif;
}

View File

@ -1,3 +0,0 @@
<h1>{{ title }}</h1>
<p>Waku node's status: {{ wakuStatus }}</p>
<app-messages></app-messages>

View File

@ -1,33 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { MessagesComponent } from './messages/messages.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
AppComponent,
MessagesComponent
],
}).compileComponents();
});
xit('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
xit(`should have as title 'relay-angular-chat'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('relay-angular-chat');
});
xit('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('.h1')?.textContent).toContain('relay-angular-chat');
});
});

View File

@ -1,23 +0,0 @@
import { Component } from '@angular/core';
import { WakuService } from './waku.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title: string = 'relay-angular-chat';
wakuStatus!: string;
constructor(private wakuService: WakuService) {}
ngOnInit(): void {
this.wakuService.init();
this.wakuService.wakuStatus.subscribe(wakuStatus => {
this.wakuStatus = wakuStatus;
});
}
}

View File

@ -1,17 +0,0 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { MessagesComponent } from './messages/messages.component';
@NgModule({
declarations: [
AppComponent,
MessagesComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -1,7 +0,0 @@
<button (click)="sendMessage()" [disabled]="wakuStatus !== 'Connected'">Send Message</button>
<h2>Messages</h2>
<ul class="messages">
<li *ngFor="let message of messages">
<span>{{ message.timestamp }} {{ message.text }}</span>
</li>
</ul>

View File

@ -1,24 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MessagesComponent } from './messages.component';
describe('MessagesComponent', () => {
let component: MessagesComponent;
let fixture: ComponentFixture<MessagesComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ MessagesComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(MessagesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
xit('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,78 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { WakuService } from '../waku.service';
import { Waku, WakuMessage } from 'js-waku';
import protons from 'protons';
const proto = protons(`
message SimpleChatMessage {
uint64 timestamp = 1;
string text = 2;
}
`);
interface MessageInterface {
timestamp: Date,
text: string
}
@Component({
selector: 'app-messages',
templateUrl: './messages.component.html',
styleUrls: ['./messages.component.css']
})
export class MessagesComponent implements OnInit {
contentTopic: string = `/relay-angular-chat/1/chat/proto`;
messages: MessageInterface[] = [];
messageCount: number = 0;
waku!: Waku;
wakuStatus!: string;
constructor(private wakuService: WakuService) { }
ngOnInit(): void {
this.wakuService.wakuStatus.subscribe(wakuStatus => {
this.wakuStatus = wakuStatus;
});
this.wakuService.waku.subscribe(waku => {
this.waku = waku;
this.waku.relay.addObserver(this.processIncomingMessages, [this.contentTopic]);
});
window.onbeforeunload = () => this.ngOnDestroy();
}
ngOnDestroy(): void {
this.waku.relay.deleteObserver(this.processIncomingMessages, [this.contentTopic]);
}
sendMessage(): void {
const time = new Date().getTime();
const payload = proto.SimpleChatMessage.encode({
timestamp: time,
text: `Here is a message #${this.messageCount}`,
});
WakuMessage.fromBytes(payload, this.contentTopic).then(wakuMessage => {
this.waku.relay.send(wakuMessage).then(() => {
console.log(`Message #${this.messageCount} sent`);
this.messageCount += 1;
});
});
}
processIncomingMessages = (wakuMessage: WakuMessage) => {
if (!wakuMessage.payload) return;
const { timestamp, text } = proto.SimpleChatMessage.decode(
wakuMessage.payload
);
const time = new Date();
time.setTime(timestamp);
const message = { text, timestamp: time };
this.messages.push(message);
}
}

View File

@ -1,16 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { WakuService } from './waku.service';
describe('WakuService', () => {
let service: WakuService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(WakuService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -1,28 +0,0 @@
import { Injectable } from '@angular/core';
import { Waku } from 'js-waku';
import { BehaviorSubject, Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class WakuService {
private wakuSubject = new Subject<Waku>();
public waku = this.wakuSubject.asObservable();
private wakuStatusSubject = new BehaviorSubject('');
public wakuStatus = this.wakuStatusSubject.asObservable();
constructor() { }
init() {
Waku.create({ bootstrap: { default: true } }).then(waku => {
this.wakuSubject.next(waku);
this.wakuStatusSubject.next('Connecting...');
waku.waitForRemotePeer().then(() => {
this.wakuStatusSubject.next('Connected');
});
});
}
}

View File

@ -1,3 +0,0 @@
export const environment = {
production: true
};

View File

@ -1,16 +0,0 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

View File

@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>RelayAngularChat</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -1,12 +0,0 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

View File

@ -1,57 +0,0 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes recent versions of Safari, Chrome (including
* Opera), Edge on the desktop, and iOS and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
(window as any).process = { env: { DEBUG: undefined }, };
(window as any)['global'] = window;
global.Buffer = global.Buffer || require('buffer').Buffer;
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@ -1 +0,0 @@
/* You can add global styles to this file, and also import other style files */

View File

@ -1,26 +0,0 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
<T>(id: string): T;
keys(): string[];
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting(),
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@ -1,15 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
}

View File

@ -1,38 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2020",
"module": "es2020",
"lib": [
"es2020",
"dom"
],
"paths": {
"buffer": ["node_modules/buffer"],
"crypto": ["node_modules/crypto-browserify"],
"stream": ["node_modules/stream-browserify"]
},
"allowSyntheticDefaultImports": true,
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@ -1,18 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"files": [
"src/test.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +0,0 @@
# Using Waku Relay in JavaScript
**Demonstrates**:
- Waku Relay: Send and receive messages using Waku Relay.
- Pure Javascript/HTML.
- Use minified bundle of js from unpkg.com, no import, no package manager.
This example uses Waku Relay to send and receive simple text messages.
To test the example, simply download the `index.html` file from this folder and open it in a browser.
The `master` branch's HEAD is deployed at https://js-waku.wakuconnect.dev/examples/relay-js/.

Some files were not shown because too many files have changed in this diff Show More