diff --git a/sdk/typescript/examples/chrome-extension/README.md b/sdk/typescript/examples/chrome-extension/README.md new file mode 100644 index 0000000000..8d7359e8c3 --- /dev/null +++ b/sdk/typescript/examples/chrome-extension/README.md @@ -0,0 +1,22 @@ +# Nym Chrome Extension Example + +This is an example of how Nym can be used within the context of a Chrome extension. + +## Running the example + +1. Copy a build of the Nym TypeScript SDK (ESM version) into `./sdk`. +2. Navigate to `chrome://extensions` in Google Chrome. +3. Enable "Developer mode" (top right of the page). +4. Click on "Load unpacked" (top left of the page). +5. Load this extension folder. + +## How does it work? + +The Nym Mixnet Client runs a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) that wraps +a WASM library that builds and encrypts Sphinx packets in the browser to send over the Nym mixnet: + +![Sphinx packet](../docs/worker.svg) + +The WASM code encrypts each layer of the Sphinx packet in the browser, before sending the Sphinx packet over a websocket to the ingress gateway: + +![Sphinx packet](../docs/sphinx.svg) diff --git a/sdk/typescript/examples/chrome-extension/icon.png b/sdk/typescript/examples/chrome-extension/icon.png new file mode 100644 index 0000000000..23d00a8289 Binary files /dev/null and b/sdk/typescript/examples/chrome-extension/icon.png differ diff --git a/sdk/typescript/examples/chrome-extension/manifest.json b/sdk/typescript/examples/chrome-extension/manifest.json new file mode 100644 index 0000000000..0b75dc2f95 --- /dev/null +++ b/sdk/typescript/examples/chrome-extension/manifest.json @@ -0,0 +1,15 @@ +{ + "name": "Nym Chrome Extension Example", + "description": "An example demonstrating how to integrate the Nym TypeScript SDK in the context of a Google Chrome browser extension.", + "version": "1.0", + "manifest_version": 3, + "permissions": [], + "content_security_policy": { + "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';" + }, + "action": { + "default_title": "Nym Chrome Extension Example", + "default_icon": "icon.png", + "default_popup": "popup.html" + } +} \ No newline at end of file diff --git a/sdk/typescript/examples/chrome-extension/popup.css b/sdk/typescript/examples/chrome-extension/popup.css new file mode 100644 index 0000000000..491c7dc3c7 --- /dev/null +++ b/sdk/typescript/examples/chrome-extension/popup.css @@ -0,0 +1,8 @@ +body { + width: 800px; + min-height: 400px; +} + +#editdialog input { + width: 100%; +} \ No newline at end of file diff --git a/sdk/typescript/examples/chrome-extension/popup.html b/sdk/typescript/examples/chrome-extension/popup.html new file mode 100644 index 0000000000..c1471a3617 --- /dev/null +++ b/sdk/typescript/examples/chrome-extension/popup.html @@ -0,0 +1,21 @@ + + + + + + + + +

+

+

+

+

Send messages from your browser, through the mixnet, and to the recipient using the "send" button.

+

Sent messages show in blue, received messages show in green.

+
+

+
+

+ + + \ No newline at end of file diff --git a/sdk/typescript/examples/chrome-extension/sdk/.gitkeep b/sdk/typescript/examples/chrome-extension/sdk/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdk/typescript/examples/chrome-extension/src/dom-utils.js b/sdk/typescript/examples/chrome-extension/src/dom-utils.js new file mode 100644 index 0000000000..322606c241 --- /dev/null +++ b/sdk/typescript/examples/chrome-extension/src/dom-utils.js @@ -0,0 +1,66 @@ +// dom-utils.js +// Contains utility functionality to help manipulate the DOM elements necessary to demonstrate the Nym example. + +/** + * Create a Sphinx packet and send it to the mixnet through the gateway node. + * + * Message and recipient are taken from the values in the user interface. + * + * @param {Client} nymClient the nym client to use for message sending + */ +async function sendMessageTo(nym) { + const message = document.getElementById('message').value; + const recipient = document.getElementById('recipient').value; + await nym.client.send({ + payload: { + message, + mimeType: 'text/plain' + }, + recipient + }); + displaySend(message); +} + +/** + * Display messages that have been sent up the websocket. Colours them blue. + * + * @param {string} message + */ +function displaySend(message) { + const timestamp = new Date().toISOString().substr(11, 12); + const sendDiv = document.createElement('div'); + const paragraph = document.createElement('p'); + paragraph.setAttribute('style', 'color: blue'); + const paragraphContent = document.createTextNode(`${timestamp} sent >>> ${message}`); + paragraph.appendChild(paragraphContent); + sendDiv.appendChild(paragraph); + document.getElementById('output')?.appendChild(sendDiv); +} + +/** + * Display received text messages in the browser. Colour them green. + * + * @param {string} message + */ +function displayReceived(message) { + const content = message; + const timestamp = new Date().toLocaleTimeString(); + const receivedDiv = document.createElement('div'); + const paragraph = document.createElement('p'); + paragraph.setAttribute('style', 'color: green'); + const paragraphContent = document.createTextNode(`${timestamp} received >>> ${content}`); + paragraph.appendChild(paragraphContent); + receivedDiv.appendChild(paragraph); + document.getElementById('output')?.appendChild(receivedDiv); +} + +/** + * Display the nymClient's sender address in the user interface + * + * @param {Client} nymClient + */ +function displaySenderAddress(address) { + document.getElementById('sender').value = address; +} + +export { sendMessageTo, displaySend, displayReceived , displaySenderAddress } \ No newline at end of file diff --git a/sdk/typescript/examples/chrome-extension/src/main.js b/sdk/typescript/examples/chrome-extension/src/main.js new file mode 100644 index 0000000000..30d84fb4c3 --- /dev/null +++ b/sdk/typescript/examples/chrome-extension/src/main.js @@ -0,0 +1,59 @@ +// main.js +// Simple example of how to load Nym's TypeScript SDK and bind it to a DOM. +// Look at dom-utils.js for the DOM utility functionality referenced here. + +// Import the Nym mixnet ESM module. +import { + createNymMixnetClient +} from "../sdk/index.js"; + +// Import the DOM utility functionality. +import { + displaySenderAddress, + displayReceived, + sendMessageTo +} from "./dom-utils.js" + +async function main() { + // Initialize the Nym mixnet client. + let nymClient = await createNymMixnetClient(); + if (!nymClient) { + console.error('Oh no! Could not create client'); + return; + } + + const nymApiUrl = 'https://validator.nymtech.net/api'; + const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM'; + + // subscribe to connect event, so that we can show the client's address + nymClient.events.subscribeToConnected((e) => { + if (e.args.address) { + displaySenderAddress(e.args.address); + } + }); + + // subscribe to message received events and show any string messages received + nymClient.events.subscribeToTextMessageReceivedEvent((e) => { + displayReceived(e.args.payload); + }); + + const sendButton = document.querySelector('#send-button') + if (sendButton) { + sendButton.onclick = function() { + if (nymClient) { + sendMessageTo(nymClient); + } + }; + } + + nymClient.events.subscribeToRawMessageReceivedEvent((e) => console.log('Received: ', e.args.payload)); + await nymClient.client.start({ + clientId: 'My awesome client', + nymApiUrl, + preferredGatewayIdentityKey, + }); +} + +window.addEventListener('DOMContentLoaded', () => { + main(); +}); \ No newline at end of file diff --git a/sdk/typescript/examples/firefox-extension/README.md b/sdk/typescript/examples/firefox-extension/README.md new file mode 100644 index 0000000000..8739d1da1f --- /dev/null +++ b/sdk/typescript/examples/firefox-extension/README.md @@ -0,0 +1,33 @@ +# Nym Firefox Extension Example + +This is an example of how Nym can be used within the context of a Mozilla Firefox extension. + +## Running the example + +Copy a build of the Nym TypeScript SDK (ESM version) into `./sdk`. + +Then, Open `sdk/index.js` and change the following line: +```js +var WorkerFactory = createURLWorkerFactory('web-worker-0.js'); +``` + +to: + +```js +var WorkerFactory = createURLWorkerFactory('sdk/web-worker-0.js'); +``` + +The above annoying workaround is currently necessary for Firefox extensions. + +Load the extension normally via manifest.json. + +## How does it work? + +The Nym Mixnet Client runs a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) that wraps +a WASM library that builds and encrypts Sphinx packets in the browser to send over the Nym mixnet: + +![Sphinx packet](../docs/worker.svg) + +The WASM code encrypts each layer of the Sphinx packet in the browser, before sending the Sphinx packet over a websocket to the ingress gateway: + +![Sphinx packet](../docs/sphinx.svg) diff --git a/sdk/typescript/examples/firefox-extension/background.html b/sdk/typescript/examples/firefox-extension/background.html new file mode 100644 index 0000000000..f87c4b9f01 --- /dev/null +++ b/sdk/typescript/examples/firefox-extension/background.html @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/sdk/typescript/examples/firefox-extension/icon.png b/sdk/typescript/examples/firefox-extension/icon.png new file mode 100644 index 0000000000..23d00a8289 Binary files /dev/null and b/sdk/typescript/examples/firefox-extension/icon.png differ diff --git a/sdk/typescript/examples/firefox-extension/manifest.json b/sdk/typescript/examples/firefox-extension/manifest.json new file mode 100644 index 0000000000..0e91a01740 --- /dev/null +++ b/sdk/typescript/examples/firefox-extension/manifest.json @@ -0,0 +1,23 @@ +{ + "manifest_version": 3, + "name": "Nym Firefox Extension Example", + "version": "1.0", + "description": "An example demonstrating how to integrate the Nym TypeScript SDK in the context of a Mozilla Firefox browser extension.", + "icons": { + "48": "icon.png" + }, + "permissions": [], + "content_security_policy": { + "extension_pages": "script-src 'self' 'wasm-unsafe-eval' blob:; object-src 'self' 'wasm-unsafe-eval' blob:; worker-src 'self' 'wasm-unsafe-eval' blob:;" + }, + "background": { + "page": "background.html" + }, + "action": { + "default_icon": { + "32" : "icon.png" + }, + "default_title": "Quicknote", + "default_popup": "popup.html" + } +} diff --git a/sdk/typescript/examples/firefox-extension/popup.css b/sdk/typescript/examples/firefox-extension/popup.css new file mode 100644 index 0000000000..491c7dc3c7 --- /dev/null +++ b/sdk/typescript/examples/firefox-extension/popup.css @@ -0,0 +1,8 @@ +body { + width: 800px; + min-height: 400px; +} + +#editdialog input { + width: 100%; +} \ No newline at end of file diff --git a/sdk/typescript/examples/firefox-extension/popup.html b/sdk/typescript/examples/firefox-extension/popup.html new file mode 100644 index 0000000000..ea5a674dc9 --- /dev/null +++ b/sdk/typescript/examples/firefox-extension/popup.html @@ -0,0 +1,21 @@ + + + + + + + + +

+

+

+

+

Send messages from your browser, through the mixnet, and to the recipient using the "send" button.

+

Sent messages show in blue, received messages show in green.

+
+

+
+

+ + + \ No newline at end of file diff --git a/sdk/typescript/examples/firefox-extension/sdk/.gitkeep b/sdk/typescript/examples/firefox-extension/sdk/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdk/typescript/examples/firefox-extension/src/background.js b/sdk/typescript/examples/firefox-extension/src/background.js new file mode 100644 index 0000000000..dda0289578 --- /dev/null +++ b/sdk/typescript/examples/firefox-extension/src/background.js @@ -0,0 +1,105 @@ +// main.js +// Simple example of how to load Nym's TypeScript SDK and bind it to a DOM. +// Look at dom-utils.js for the DOM utility functionality referenced here. + +// Import the Nym mixnet ESM module. +import { + createNymMixnetClient +} from "../sdk/index.js"; + +const backgroundState = { + isReady: false, + address: '', + recipient: '', + messageLog: [] +} + +async function initBackground() { + // Initialize the Nym mixnet client. + let nymClient = await createNymMixnetClient().catch((err) => { + console.log(err) + }); + if (!nymClient) { + console.error('Oh no! Could not create client'); + return; + } + const nymApiUrl = 'https://validator.nymtech.net/api'; + const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM'; + + // subscribe to connect event, so that we can show the client's address + nymClient.events.subscribeToConnected((e) => { + if (e.args.address) { + backgroundState.address = e.args.address + browser.runtime.sendMessage({ + type: 'displaySenderAddress', + message: backgroundState.address + }) + } + }); + + // subscribe to message received events and show any string messages received + nymClient.events.subscribeToTextMessageReceivedEvent((e) => { + backgroundState.messageLog.push({ + type: 'received', + message: e.args.payload + }) + browser.runtime.sendMessage({ + type: 'displayReceived', + message: e.args.payload + }) + }); + + nymClient.events.subscribeToRawMessageReceivedEvent((e) => console.log('Received: ', e.args.payload)); + await nymClient.client.start({ + clientId: 'My awesome client', + nymApiUrl, + preferredGatewayIdentityKey, + }); + browser.runtime.onMessage.addListener(async (data) => { + switch (data.type) { + case 'nymClientSendMessage': + if (nymClient) { + await nymClient.client.send({ + payload: { + message: data.message, + mimeType: 'text/plain' + }, + recipient: data.recipient + }); + backgroundState.messageLog.push({ + type: 'sent', + message: data.message + }) + break; + } + } + }) + backgroundState.isReady = true +} + +window.addEventListener('DOMContentLoaded', () => { + browser.runtime.onMessage.addListener((data) => { + switch (data.type) { + case 'popupReady': + if (backgroundState.isReady) { + browser.runtime.sendMessage({ + type: 'displaySenderAddress', + message: backgroundState.address + }) + browser.runtime.sendMessage({ + type: 'displayMessageLog', + message: backgroundState.messageLog + }) + browser.runtime.sendMessage({ + type: 'updateRecipient', + message: backgroundState.recipient + }) + } else { + initBackground(); + } + break; + case 'updateRecipient': + backgroundState.recipient = data.message + } + }); +}); \ No newline at end of file diff --git a/sdk/typescript/examples/firefox-extension/src/dom-utils.js b/sdk/typescript/examples/firefox-extension/src/dom-utils.js new file mode 100644 index 0000000000..1bbfd628d3 --- /dev/null +++ b/sdk/typescript/examples/firefox-extension/src/dom-utils.js @@ -0,0 +1,80 @@ +// dom-utils.js +// Contains utility functionality to help manipulate the DOM elements necessary to demonstrate the Nym example. + +/** + * Create a Sphinx packet and send it to the mixnet through the gateway node. + * + * Message and recipient are taken from the values in the user interface. + * + * @param {Client} nymClient the nym client to use for message sending + */ +async function sendMessageTo() { + const message = document.getElementById('message').value; + const recipient = document.getElementById('recipient').value; + browser.runtime.sendMessage({ + type: 'nymClientSendMessage', + message, + recipient + }) + displaySend(message); +} + +/** + * Display messages that have been sent up the websocket. Colours them blue. + * + * @param {string} message + */ +function displaySend(message) { + const timestamp = new Date().toISOString().substr(11, 12); + const sendDiv = document.createElement('div'); + const paragraph = document.createElement('p'); + paragraph.setAttribute('style', 'color: blue'); + const paragraphContent = document.createTextNode(`${timestamp} sent >>> ${message}`); + paragraph.appendChild(paragraphContent); + sendDiv.appendChild(paragraph); + document.getElementById('output')?.appendChild(sendDiv); +} + +/** + * Display received text messages in the browser. Colour them green. + * + * @param {string} message + */ +function displayReceived(message) { + const content = message; + const timestamp = new Date().toLocaleTimeString(); + const receivedDiv = document.createElement('div'); + const paragraph = document.createElement('p'); + paragraph.setAttribute('style', 'color: green'); + const paragraphContent = document.createTextNode(`${timestamp} received >>> ${content}`); + paragraph.appendChild(paragraphContent); + receivedDiv.appendChild(paragraph); + document.getElementById('output')?.appendChild(receivedDiv); +} + +/** + * Display the nymClient's sender address in the user interface + * + * @param {Client} nymClient + */ +function displaySenderAddress(address) { + document.getElementById('sender').value = address; +} + +function displayMessageLog(messageLog) { + for (let i = 0; i < messageLog.length; i++) { + if (messageLog[i].type === 'sent') { + displaySend(messageLog[i].message) + } else if (messageLog[i].type === 'received') { + displayReceived(messageLog[i].message) + } + } +} + +export { + sendMessageTo, + displaySend, + displayReceived, + displaySenderAddress, + displayMessageLog +} \ No newline at end of file diff --git a/sdk/typescript/examples/firefox-extension/src/popup.js b/sdk/typescript/examples/firefox-extension/src/popup.js new file mode 100644 index 0000000000..9fe4600d4b --- /dev/null +++ b/sdk/typescript/examples/firefox-extension/src/popup.js @@ -0,0 +1,45 @@ +// Import the DOM utility functionality. +import { + displaySenderAddress, + displayReceived, + sendMessageTo, + displayMessageLog +} from "./dom-utils.js" + +window.addEventListener('DOMContentLoaded', () => { + const sendButton = document.querySelector('#send-button') + if (sendButton) { + sendButton.onclick = function() { + sendMessageTo() + }; + } + const recipient = document.getElementById('recipient') + recipient.onchange = () => { + browser.runtime.sendMessage({ + type: 'updateRecipient', + message: recipient.value + }) + } + browser.runtime.onMessage.addListener((data) => { + switch (data.type) { + case 'displaySenderAddress': + displaySenderAddress(data.message); + break; + case 'displayReceived': + displayReceived(data.message); + break; + case 'sendMessageTo': + sendMessageTo(data.message); + break; + case 'displayMessageLog': + displayMessageLog(data.message) + break; + case 'updateRecipient': + recipient.value = data.message + } + }); + browser.runtime.sendMessage({ + type: 'popupReady', + message: '', + }) +}); \ No newline at end of file diff --git a/sdk/typescript/examples/plain-html/README.md b/sdk/typescript/examples/plain-html/README.md index 4cc2c16e8e..91892da205 100644 --- a/sdk/typescript/examples/plain-html/README.md +++ b/sdk/typescript/examples/plain-html/README.md @@ -9,7 +9,7 @@ You can use this example as a seed for a new project. Try out the chat app by running: ``` -npm install +npm install --legacy-peer-deps npm start ``` diff --git a/sdk/typescript/examples/plain-html/assets/logo-circle.svg b/sdk/typescript/examples/plain-html/assets/logo-circle.svg new file mode 100644 index 0000000000..fdfff58182 --- /dev/null +++ b/sdk/typescript/examples/plain-html/assets/logo-circle.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/sdk/typescript/examples/plain-html/package.json b/sdk/typescript/examples/plain-html/package.json index a937165155..309abfc4ff 100644 --- a/sdk/typescript/examples/plain-html/package.json +++ b/sdk/typescript/examples/plain-html/package.json @@ -4,49 +4,56 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "@nymproject/sdk": "1" + "@nymproject/sdk": "1", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10", + "fork-ts-checker-webpack-plugin": "^8.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-refresh": "^0.14.0", + "react-refresh-typescript": "^2.0.9", + "webpack-favicons": "^1.3.8" }, "devDependencies": { - "@babel/core": "^7.15.0", - "@babel/plugin-transform-async-to-generator": "^7.14.5", - "@babel/preset-env": "^7.15.0", - "@babel/preset-typescript": "^7.15.0", - "@types/jest": "^27.0.1", - "@types/node": "^16.7.13", - "@typescript-eslint/eslint-plugin": "^5.13.0", - "@typescript-eslint/parser": "^5.13.0", - "babel-loader": "^8.3.0", - "babel-plugin-root-import": "^5.1.0", + "@babel/core": "^7.21.8", + "@babel/plugin-transform-async-to-generator": "^7.20.7", + "@babel/preset-env": "^7.21.5", + "@babel/preset-typescript": "^7.21.5", + "@types/jest": "^29.5.1", + "@types/node": "^20.1.1", + "@typescript-eslint/eslint-plugin": "^5.59.5", + "@typescript-eslint/parser": "^5.59.5", + "ajv": "^8.12.0", + "babel-loader": "^9.1.2", + "babel-plugin-root-import": "^6.6.0", "clean-webpack-plugin": "^4.0.0", "css-loader": "^6.7.3", - "css-minimizer-webpack-plugin": "^3.0.2", - "dotenv-webpack": "^7.0.3", - "eslint": "^8.10.0", + "css-minimizer-webpack-plugin": "^5.0.0", + "dotenv-webpack": "^8.0.1", + "eslint": "^8.40.0", "eslint-config-airbnb": "^19.0.4", - "eslint-config-airbnb-typescript": "^16.1.0", - "eslint-config-prettier": "^8.5.0", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-prettier": "^8.8.0", "eslint-import-resolver-root-import": "^1.0.4", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jest": "^26.1.1", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jest": "^27.2.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-prettier": "^4.2.1", "file-loader": "^6.2.0", - "fork-ts-checker-webpack-plugin": "^7.2.1", - "html-webpack-plugin": "^5.3.2", - "jest": "^27.1.0", - "mini-css-extract-plugin": "^2.2.2", + "html-webpack-plugin": "^5.5.1", + "jest": "^29.5.0", + "mini-css-extract-plugin": "^2.7.5", "npm-run-all": "^4.1.5", - "prettier": "^2.8.7", - "style-loader": "^3.3.1", + "prettier": "^2.8.8", + "style-loader": "^3.3.2", "thread-loader": "^3.0.4", - "ts-jest": "^27.0.5", + "ts-jest": "^29.1.0", "ts-loader": "^9.4.2", - "tsconfig-paths-webpack-plugin": "^3.5.2", - "typescript": "^4.6.2", + "tsconfig-paths-webpack-plugin": "^4.0.1", + "typescript": "^5.0.4", "url-loader": "^4.1.1", - "webpack": "^5.75.0", - "webpack-cli": "^4.8.0", - "webpack-dev-server": "^4.5.0", + "webpack": "^5.82.0", + "webpack-cli": "^5.1.1", + "webpack-dev-server": "^4.15.0", "webpack-merge": "^5.8.0" }, "scripts": { diff --git a/sdk/typescript/examples/plain-html/tsconfig.json b/sdk/typescript/examples/plain-html/tsconfig.json index b8059e8698..b8c323c67c 100644 --- a/sdk/typescript/examples/plain-html/tsconfig.json +++ b/sdk/typescript/examples/plain-html/tsconfig.json @@ -1,7 +1,26 @@ { - "extends": "../../tsconfig.json", + "compileOnSave": false, "compilerOptions": { - "jsx": "react-jsx", + "target": "es2021", + "lib": ["es2021", "dom", "dom.iterable", "esnext"], + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "module": "ES2020", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": false, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "baseUrl": ".", + "paths": { + "@assets/*": ["../assets/*"] + }, + "jsx": "react-jsx", "outDir": "./dist" }, "include": [ @@ -9,8 +28,21 @@ "src/**/*.tsx" ], "exclude": [ + "jest.config.js", + "webpack.config.js", + "webpack.prod.js", + "webpack.common.js", "node_modules", - "build", - "dist" + "build", + "**/node_modules", + "dist", + "**/dist", + "scripts", + "jest", + "__tests__", + "**/__tests__", + "__jest__", + "**/__jest__", + "config/*" ] } diff --git a/sdk/typescript/examples/.webpack/webpack.base.js b/sdk/typescript/examples/plain-html/webpack.base.js similarity index 95% rename from sdk/typescript/examples/.webpack/webpack.base.js rename to sdk/typescript/examples/plain-html/webpack.base.js index 7725e12bc0..c9656fb20b 100644 --- a/sdk/typescript/examples/.webpack/webpack.base.js +++ b/sdk/typescript/examples/plain-html/webpack.base.js @@ -81,7 +81,7 @@ const webpackCommon = (baseDir, htmlPath) => ({ }), new WebpackFavicons({ - src: path.resolve(__dirname, '../../assets/favicon/favicon.png'), // the asset directory is relative to THIS file + src: path.resolve(__dirname, 'assets/logo-circle.svg'), // the asset directory is relative to THIS file }), new Dotenv(), diff --git a/sdk/typescript/examples/plain-html/webpack.common.js b/sdk/typescript/examples/plain-html/webpack.common.js index 3d5f37c912..9d86d2bc80 100644 --- a/sdk/typescript/examples/plain-html/webpack.common.js +++ b/sdk/typescript/examples/plain-html/webpack.common.js @@ -1,6 +1,6 @@ const path = require('path'); const { mergeWithRules } = require('webpack-merge'); -const { webpackCommon } = require('../.webpack/webpack.base'); +const { webpackCommon } = require('./webpack.base'); module.exports = mergeWithRules({ module: { diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/README.md b/sdk/typescript/examples/react-webpack-with-theme-example/README.md index 9a773a9e0f..17ee5a9016 100644 --- a/sdk/typescript/examples/react-webpack-with-theme-example/README.md +++ b/sdk/typescript/examples/react-webpack-with-theme-example/README.md @@ -15,7 +15,7 @@ You can use this example as a seed for a new project, and it uses: Try out the chat app by running: ``` -npm install +npm install --legacy-peer-deps npm start ``` diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/assets/logo-circle.svg b/sdk/typescript/examples/react-webpack-with-theme-example/assets/logo-circle.svg new file mode 100644 index 0000000000..fdfff58182 --- /dev/null +++ b/sdk/typescript/examples/react-webpack-with-theme-example/assets/logo-circle.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/package.json b/sdk/typescript/examples/react-webpack-with-theme-example/package.json index 5af2e7b841..13f227e62e 100644 --- a/sdk/typescript/examples/react-webpack-with-theme-example/package.json +++ b/sdk/typescript/examples/react-webpack-with-theme-example/package.json @@ -4,80 +4,76 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "@mui/icons-material": "^5.5.0", - "@mui/lab": "^5.0.0-alpha.72", - "@mui/material": "^5.0.1", - "@mui/styles": "^5.0.1", - "react-mui-dropzone": "^4.0.6", + "@emotion/react": "^11.11.0", + "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.11.16", + "@mui/lab": "^5.0.0-alpha.129", + "@mui/material": "^5.12.3", + "@mui/styles": "^5.12.3", "@nymproject/sdk": "1", + "fork-ts-checker-webpack-plugin": "^8.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-mui-dropzone": "^4.0.6", + "react-refresh": "^0.14.0", "use-clipboard-copy": "^0.2.0" }, "devDependencies": { - "@babel/core": "^7.15.0", - "@babel/plugin-transform-async-to-generator": "^7.14.5", - "@babel/preset-env": "^7.15.0", - "@babel/preset-react": "^7.14.5", - "@babel/preset-typescript": "^7.15.0", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", - "@svgr/webpack": "^6.1.1", - "@testing-library/jest-dom": "^5.14.1", - "@testing-library/react": "^12.0.0", - "@types/jest": "^27.0.1", - "@types/node": "^16.7.13", - "@types/react": "^18.0.26", - "@types/react-dom": "^18.0.10", - "@typescript-eslint/eslint-plugin": "^5.13.0", - "@typescript-eslint/parser": "^5.13.0", - "babel-loader": "^8.3.0", - "babel-plugin-root-import": "^5.1.0", + "@babel/core": "^7.21.8", + "@babel/plugin-transform-async-to-generator": "^7.20.7", + "@babel/preset-env": "^7.21.5", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.5", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10", + "@svgr/webpack": "^8.0.1", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^14.0.0", + "@types/jest": "^29.5.1", + "@types/node": "^20.1.1", + "@types/react": "^18.2.6", + "@types/react-dom": "^18.2.4", + "@typescript-eslint/eslint-plugin": "^5.59.5", + "@typescript-eslint/parser": "^5.59.5", + "babel-loader": "^9.1.2", + "babel-plugin-root-import": "^6.6.0", "clean-webpack-plugin": "^4.0.0", "css-loader": "^6.7.3", - "css-minimizer-webpack-plugin": "^3.0.2", - "dotenv-webpack": "^7.0.3", - "eslint": "^8.10.0", + "css-minimizer-webpack-plugin": "^5.0.0", + "dotenv-webpack": "^8.0.1", + "eslint": "^8.40.0", "eslint-config-airbnb": "^19.0.4", - "eslint-config-airbnb-typescript": "^16.1.0", - "eslint-config-prettier": "^8.5.0", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-prettier": "^8.8.0", "eslint-import-resolver-root-import": "^1.0.4", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jest": "^26.1.1", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-react": "^7.29.2", - "eslint-plugin-react-hooks": "^4.3.0", - "favicons": "^7.0.2", - "favicons-webpack-plugin": "^5.0.2", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jest": "^27.2.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react": "^7.32.2", + "eslint-plugin-react-hooks": "^4.6.0", + "favicons": "^7.1.2", + "favicons-webpack-plugin": "^6.0.0", "file-loader": "^6.2.0", - "fork-ts-checker-webpack-plugin": "^7.2.1", - "html-webpack-plugin": "^5.3.2", - "jest": "^27.1.0", - "mini-css-extract-plugin": "^2.2.2", + "html-webpack-plugin": "^5.5.1", + "jest": "^29.5.0", + "mini-css-extract-plugin": "^2.7.5", "npm-run-all": "^4.1.5", - "prettier": "^2.8.7", - "react-refresh-typescript": "^2.0.3", - "style-loader": "^3.3.1", + "prettier": "^2.8.8", + "react-refresh-typescript": "^2.0.9", + "style-loader": "^3.3.2", "thread-loader": "^3.0.4", - "ts-jest": "^27.0.5", + "ts-jest": "^29.1.0", "ts-loader": "^9.4.2", - "tsconfig-paths-webpack-plugin": "^3.5.2", - "typescript": "^4.6.2", + "tsconfig-paths-webpack-plugin": "^4.0.1", + "typescript": "^5.0.4", "url-loader": "^4.1.1", - "webpack": "^5.75.0", - "webpack-cli": "^4.8.0", - "webpack-dev-server": "^4.5.0", + "webpack": "^5.82.0", + "webpack-cli": "^5.1.1", + "webpack-dev-server": "^4.15.0", "webpack-favicons": "^1.3.8", "webpack-merge": "^5.8.0" }, - "overrides": { - "react": "^18.2.0", - "react-dom": "^18.2.0", - "@types/react": "^18.0.26", - "@types/react-dom": "^18.0.10" - }, "scripts": { "start": "webpack serve --progress --port 3000", "build": "webpack build --progress --config webpack.prod.js", diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx b/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx index c2252585de..f435d2ddbc 100644 --- a/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx +++ b/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx @@ -31,7 +31,7 @@ import { ThemeToggle } from './ThemeToggle'; import { AppContextProvider, useAppContext } from './context'; import { MixnetContextProvider, parseBinaryMessageHeaders, useMixnetContext } from './context/mixnet'; // eslint-disable-next-line import/no-relative-packages -import Logo from '../../../../../assets/logo/logo-circle.svg'; +import Logo from '../assets/logo-circle.svg'; export const AppTheme: FCWithChildren = ({ children }) => { const { mode } = useAppContext(); diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/tsconfig.json b/sdk/typescript/examples/react-webpack-with-theme-example/tsconfig.json index b8059e8698..b8c323c67c 100644 --- a/sdk/typescript/examples/react-webpack-with-theme-example/tsconfig.json +++ b/sdk/typescript/examples/react-webpack-with-theme-example/tsconfig.json @@ -1,7 +1,26 @@ { - "extends": "../../tsconfig.json", + "compileOnSave": false, "compilerOptions": { - "jsx": "react-jsx", + "target": "es2021", + "lib": ["es2021", "dom", "dom.iterable", "esnext"], + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "module": "ES2020", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": false, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "baseUrl": ".", + "paths": { + "@assets/*": ["../assets/*"] + }, + "jsx": "react-jsx", "outDir": "./dist" }, "include": [ @@ -9,8 +28,21 @@ "src/**/*.tsx" ], "exclude": [ + "jest.config.js", + "webpack.config.js", + "webpack.prod.js", + "webpack.common.js", "node_modules", - "build", - "dist" + "build", + "**/node_modules", + "dist", + "**/dist", + "scripts", + "jest", + "__tests__", + "**/__tests__", + "__jest__", + "**/__jest__", + "config/*" ] } diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/webpack.base.js b/sdk/typescript/examples/react-webpack-with-theme-example/webpack.base.js new file mode 100644 index 0000000000..c9656fb20b --- /dev/null +++ b/sdk/typescript/examples/react-webpack-with-theme-example/webpack.base.js @@ -0,0 +1,98 @@ +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); +// const { CleanWebpackPlugin } = require('clean-webpack-plugin'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const WebpackFavicons = require('webpack-favicons'); +const Dotenv = require('dotenv-webpack'); +const path = require('path'); +const os = require('os'); + +/** + * Creates the default Webpack config + * @param baseDir The base directory path, e.g. pass `__dirname` of the webpack config file using this method + */ +const webpackCommon = (baseDir, htmlPath) => ({ + module: { + rules: [ + { + test: /\.tsx?$/, + use: [ + { + loader: 'thread-loader', + options: { workers: Math.max(2, os.cpus().length - 1) }, + }, + { loader: 'ts-loader', options: { happyPackMode: true } }, + ], + exclude: /node_modules/, + }, + { + test: /\.css$/i, + use: ['style-loader', 'css-loader'], + }, + { + test: /\.svg$/i, + issuer: /\.[jt]sx?$/, + use: ['@svgr/webpack'], + }, + { + test: /\.(png|jpe?g|gif|md|webp)$/i, + // More information here https://webpack.js.org/guides/asset-modules/ + type: 'asset', + }, + { + // See https://webpack.js.org/guides/asset-management/#loading-fonts + test: /\.(woff|woff2|eot|ttf|otf)$/i, + type: 'asset/resource', + }, + { + test: /\.ya?ml$/, + type: 'json', + use: 'yaml-loader', + }, + ], + }, + resolve: { + extensions: ['.tsx', '.ts', '.js'], + plugins: [new TsconfigPathsPlugin()], + alias: { + 'react/jsx-runtime': require.resolve('react/jsx-runtime'), + }, + }, + plugins: [ + // new CleanWebpackPlugin(), + + ...(Array.isArray(htmlPath) + ? htmlPath.map((item) => new HtmlWebpackPlugin(item)) + : [ + new HtmlWebpackPlugin({ + filename: 'index.html', + template: path.resolve(baseDir, htmlPath || 'src/index.html'), + }), + ]), + + new ForkTsCheckerWebpackPlugin({ + typescript: { + mode: 'write-references', + diagnosticOptions: { + semantic: true, + syntactic: true, + }, + }, + }), + + new WebpackFavicons({ + src: path.resolve(__dirname, 'assets/logo-circle.svg'), // the asset directory is relative to THIS file + }), + + new Dotenv(), + ], + output: { + path: path.resolve(baseDir, 'dist'), + publicPath: '/', + }, +}); + + +module.exports = { + webpackCommon, +}; diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/webpack.common.js b/sdk/typescript/examples/react-webpack-with-theme-example/webpack.common.js index 459c5495e4..3d2b0f60c4 100644 --- a/sdk/typescript/examples/react-webpack-with-theme-example/webpack.common.js +++ b/sdk/typescript/examples/react-webpack-with-theme-example/webpack.common.js @@ -1,6 +1,6 @@ const path = require('path'); const { mergeWithRules } = require('webpack-merge'); -const { webpackCommon } = require('../.webpack/webpack.base'); +const { webpackCommon } = require('./webpack.base'); module.exports = mergeWithRules({ module: { diff --git a/sdk/typescript/packages/sdk/rollup-esm.config.mjs b/sdk/typescript/packages/sdk/rollup-esm.config.mjs index 3ad4c148fc..1dd32d0828 100644 --- a/sdk/typescript/packages/sdk/rollup-esm.config.mjs +++ b/sdk/typescript/packages/sdk/rollup-esm.config.mjs @@ -11,7 +11,7 @@ export default { format: 'es', }, plugins: [ - webWorkerLoader({ targetPlatform: 'browser', inline: true }), + webWorkerLoader({ targetPlatform: 'browser', inline: false }), resolve({ extensions }), typescript({ exclude: ['mixnet/wasm/worker.ts', 'mixnet/node-tester/worker.ts'],