commit boilerplate

This commit is contained in:
0xtsukino
2023-08-07 14:36:38 -07:00
committed by tsukino
parent 60066e9856
commit 59c5373fc4
45 changed files with 902 additions and 132 deletions

2
.gitignore vendored
View File

@@ -5,3 +5,5 @@ bin/
pkg/
wasm-pack.log
**/node_modules
**/.DS_Store
.idea

173
README.md Normal file → Executable file
View File

@@ -1,63 +1,150 @@
# tlsn-extension 🚧🚧🚧
<img src="src/assets/img/icon-128.png" width="64"/>
## Bind a rust websocket client
We used the example from [wasm-bindgen](https://rustwasm.github.io/docs/wasm-bindgen/examples/websockets.html) with the following changes:
1. Use webpack to bundle the javascript
2. Add a websocket server to listen to the messages from the browser
# Chrome Extension (MV3) Boilerplate with React 18 and Webpack 5
[![npm](https://img.shields.io/npm/v/chrome-extension-boilerplate-react)](https://www.npmjs.com/package/chrome-extension-boilerplate-react)
[![npm-download](https://img.shields.io/npm/dw/chrome-extension-boilerplate-react)](https://www.npmjs.com/package/chrome-extension-boilerplate-react)
[![npm](https://img.shields.io/npm/dm/chrome-extension-boilerplate-react)](https://www.npmjs.com/package/chrome-extension-boilerplate-react)
### Install `wasm-pack`
Do it with `yarn`, or you can install it in [other ways](https://rustwasm.github.io/wasm-pack/installer/).
## Announcements
```bash
yarn global add wasm-pack
- Recently updated from **[React](https://reactjs.org)** ~~17~~ to **18**!
- **_This boilerplate adopts [Manifest V3](https://developer.chrome.com/docs/extensions/mv3/intro/mv3-overview/)!_**
- For V2 users, please check out the [manifest-v2](https://github.com/lxieyang/chrome-extension-boilerplate-react/tree/manifest-v2) branch, or use version [3.x](https://www.npmjs.com/package/chrome-extension-boilerplate-react/v/3.3.0).
- Check out the [Manifest V3 Migration Guide](https://developer.chrome.com/docs/extensions/mv3/intro/mv3-migration/).
- Recently added [devtools](https://developer.chrome.com/docs/extensions/mv3/devtools/) Support! Thanks [GeekaholicLin](https://github.com/lxieyang/chrome-extension-boilerplate-react/issues/17)!
- Recently updated from **[Webpack Dev Server](https://webpack.js.org/configuration/dev-server/)** ~~3.x~~ to **4.x** and **[Webpack](https://webpack.js.org/)** ~~4~~ to **5**!
- Recently added [TypeScript](https://www.typescriptlang.org/) Support!
## Features
This is a basic Chrome Extensions boilerplate to help you write modular and modern Javascript code, load CSS easily and [automatic reload the browser on code changes](https://webpack.github.io/docs/webpack-dev-server.html#automatic-refresh).
This boilerplate is updated with:
- [Chrome Extension Manifest V3](https://developer.chrome.com/docs/extensions/mv3/intro/mv3-overview/)
- [React 18](https://reactjs.org)
- [Webpack 5](https://webpack.js.org/)
- [Webpack Dev Server 4](https://webpack.js.org/configuration/dev-server/)
- [React Refresh](https://www.npmjs.com/package/react-refresh)
- [react-refresh-webpack-plugin](https://github.com/pmmmwh/react-refresh-webpack-plugin)
- [eslint-config-react-app](https://www.npmjs.com/package/eslint-config-react-app)
- [Prettier](https://prettier.io/)
- [TypeScript](https://www.typescriptlang.org/)
This boilerplate is heavily inspired by and adapted from [https://github.com/samuelsimoes/chrome-extension-webpack-boilerplate](https://github.com/samuelsimoes/chrome-extension-webpack-boilerplate), with additional support for React 18 features, Webpack 5, and Webpack Dev Server 4.
Please open up an issue to nudge me to keep the npm packages up-to-date. FYI, it takes time to make different packages with different versions work together nicely.
## Installing and Running
### Procedures:
1. Check if your [Node.js](https://nodejs.org/) version is >= **18**.
2. Clone this repository.
3. Change the package's `name`, `description`, and `repository` fields in `package.json`.
4. Change the name of your extension on `src/manifest.json`.
5. Run `npm install` to install the dependencies.
6. Run `npm start`
7. Load your extension on Chrome following:
1. Access `chrome://extensions/`
2. Check `Developer mode`
3. Click on `Load unpacked extension`
4. Select the `build` folder.
8. Happy hacking.
## Structure
All your extension's code must be placed in the `src` folder.
The boilerplate is already prepared to have a popup, an options page, a background page, and a new tab page (which replaces the new tab page of your browser). But feel free to customize these.
## TypeScript
This boilerplate now supports TypeScript! The `Options` Page is implemented using TypeScript. Please refer to `src/pages/Options/` for example usages.
## Webpack auto-reload and HRM
To make your workflow much more efficient this boilerplate uses the [webpack server](https://webpack.github.io/docs/webpack-dev-server.html) to development (started with `npm start`) with auto reload feature that reloads the browser automatically every time that you save some file in your editor.
You can run the dev mode on other port if you want. Just specify the env var `port` like this:
```
$ PORT=6002 npm run start
```
### Compile rust to wasm and create a JS package binding
## Content Scripts
```bash
yarn build-rs
Although this boilerplate uses the webpack dev server, it's also prepared to write all your bundles files on the disk at every code change, so you can point, on your extension manifest, to your bundles that you want to use as [content scripts](https://developer.chrome.com/extensions/content_scripts), but you need to exclude these entry points from hot reloading [(why?)](https://github.com/samuelsimoes/chrome-extension-webpack-boilerplate/issues/4#issuecomment-261788690). To do so you need to expose which entry points are content scripts on the `webpack.config.js` using the `chromeExtensionBoilerplate -> notHotReload` config. Look the example below.
Let's say that you want use the `myContentScript` entry point as content script, so on your `webpack.config.js` you will configure the entry point and exclude it from hot reloading, like this:
```js
{
entry: {
myContentScript: "./src/js/myContentScript.js"
},
chromeExtensionBoilerplate: {
notHotReload: ["myContentScript"]
}
}
```
You can see the resulting JS package in `pkg/`.
and on your `src/manifest.json`:
```bash
tree pkg
pkg
├── README.md
├── package.json
├── tlsn_extension_rs.d.ts
├── tlsn_extension_rs.js
├── tlsn_extension_rs_bg.wasm
└── tlsn_extension_rs_bg.wasm.d.ts
```json
{
"content_scripts": [
{
"matches": ["https://www.google.com/*"],
"js": ["myContentScript.bundle.js"]
}
]
}
```
### Install dependencies
## Intelligent Code Completion
```bash
yarn install
Thanks to [@hudidit](https://github.com/lxieyang/chrome-extension-boilerplate-react/issues/4)'s kind suggestions, this boilerplate supports chrome-specific intelligent code completion using [@types/chrome](https://www.npmjs.com/package/@types/chrome).
## Packing
After the development of your extension run the command
```
$ NODE_ENV=production npm run build
```
### Run the websocket server in another terminal
Open a new terminal and run the websocket server
```bash
yarn ws-server
Now, the content of `build` folder will be the extension ready to be submitted to the Chrome Web Store. Just take a look at the [official guide](https://developer.chrome.com/webstore/publish) to more infos about publishing.
## Secrets
If you are developing an extension that talks with some API you probably are using different keys for testing and production. Is a good practice you not commit your secret keys and expose to anyone that have access to the repository.
To this task this boilerplate import the file `./secrets.<THE-NODE_ENV>.js` on your modules through the module named as `secrets`, so you can do things like this:
_./secrets.development.js_
```js
export default { key: '123' };
```
### Run the web server
```bash
yarn start
_./src/popup.js_
```js
import secrets from 'secrets';
ApiCall({ key: secrets.key });
```
### Open the browser
```bash
open http://localhost:8080
```
:point_right: The files with name `secrets.*.js` already are ignored on the repository.
And you should see outputs from the browser console:
```
socket opened
tlsn_extension_rs.js:278 message successfully sent
tlsn_extension_rs.js:278 binary message successfully sent
tlsn_extension_rs.js:278 message event, received Text: "something"
```
## Resources:
- [Webpack documentation](https://webpack.js.org/concepts/)
- [Chrome Extension documentation](https://developer.chrome.com/extensions/getstarted)
---
Michael Xieyang Liu | [Website](https://lxieyang.github.io)

5
bootstrap.js vendored
View File

@@ -1,5 +0,0 @@
// A dependency graph that contains any wasm must all be imported
// asynchronously. This `bootstrap.js` file does the single async import, so
// that no one else needs to worry about it again.
import("./index.js")
.catch(e => console.error("Error importing `index.js`:", e));

View File

@@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebSockets example</title>
<script src="bootstrap.js"></script>
</head>
<body>
</body>
</html>

65
package.json Normal file → Executable file
View File

@@ -1,25 +1,60 @@
{
"name": "tlsn-extension",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"build-rs": "wasm-pack build --target web",
"build": "webpack --config webpack.config.js",
"start": "webpack-dev-server",
"build-and-start": "yarn build-rs && yarn install && yarn start",
"clean": "rm -rf pkg/ node_modules/ dist/ yarn.lock"
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/tlsnotary/tlsn-extension.git"
},
"scripts": {
"build": "node utils/build.js",
"start": "node utils/webserver.js",
"prettier": "prettier --write '**/*.{js,jsx,ts,tsx,json,css,scss,md}'"
},
"author": "",
"license": "ISC",
"dependencies": {
"comlink": "^4.4.1",
"tlsn-extension-rs": "file:./pkg"
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/core": "^7.20.12",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"@types/chrome": "^0.0.202",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.10",
"babel-eslint": "^10.1.0",
"babel-loader": "^9.1.2",
"babel-preset-react-app": "^10.0.1",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"webpack": "^5.88.1",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
"css-loader": "^6.7.3",
"eslint": "^8.31.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.27.4",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-react": "^7.32.0",
"eslint-plugin-react-hooks": "^4.6.0",
"file-loader": "^6.2.0",
"fs-extra": "^11.1.0",
"html-loader": "^4.2.0",
"html-webpack-plugin": "^5.5.0",
"prettier": "^2.8.3",
"react-refresh": "^0.14.0",
"react-refresh-typescript": "^2.0.7",
"sass": "^1.57.1",
"sass-loader": "^13.2.0",
"source-map-loader": "^3.0.1",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.6",
"ts-loader": "^9.4.2",
"type-fest": "^3.5.2",
"typescript": "^4.9.4",
"webpack": "^5.75.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1",
"zip-webpack-plugin": "^4.0.1"
}
}

BIN
src/assets/img/icon-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
src/assets/img/icon-34.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

7
src/assets/img/logo.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,19 @@
import React, { Component } from 'react';
import icon from '../../assets/img/icon-128.png';
class GreetingComponent extends Component {
state = {
name: 'dev',
};
render() {
return (
<div>
<p>Hello, {this.state.name}!</p>
<img src={icon} alt="extension icon" />
</div>
);
}
}
export default GreetingComponent;

31
src/manifest.json Executable file
View File

@@ -0,0 +1,31 @@
{
"manifest_version": 3,
"name": "TLSN Extension",
"description": "A chrome extension for TLSN",
"options_page": "options.html",
"background": { "service_worker": "background.bundle.js" },
"action": {
"default_popup": "popup.html",
"default_icon": "icon-34.png"
},
"chrome_url_overrides": {
"newtab": "newtab.html"
},
"icons": {
"128": "icon-128.png"
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*", "<all_urls>"],
"js": ["contentScript.bundle.js"],
"css": ["content.styles.css"]
}
],
"devtools_page": "devtools.html",
"web_accessible_resources": [
{
"resources": ["content.styles.css", "icon-128.png", "icon-34.png"],
"matches": []
}
]
}

View File

@@ -0,0 +1,2 @@
console.log('This is the background page.');
console.log('Put the background scripts here.');

View File

View File

@@ -0,0 +1,6 @@
import { printLine } from './modules/print';
console.log('Content script works!');
console.log('Must reload extension for modifications to take effect.');
printLine("Using the 'printLine' function from the Print Module");

View File

@@ -0,0 +1,3 @@
export const printLine = (line) => {
console.log('===> FROM THE PRINT MODULE:', line);
};

View File

@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title></title>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,5 @@
chrome.devtools.panels.create(
'Dev Tools from chrome-extension-boilerplate-react',
'icon-34.png',
'panel.html'
);

View File

@@ -0,0 +1,38 @@
.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: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@@ -0,0 +1,28 @@
import React from 'react';
import logo from '../../assets/img/logo.svg';
import './Newtab.css';
import './Newtab.scss';
const Newtab = () => {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/pages/Newtab/Newtab.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React!
</a>
<h6>The color of this paragraph is defined using SASS.</h6>
</header>
</div>
);
};
export default Newtab;

View File

@@ -0,0 +1,10 @@
$myColor: orange;
h1,
h2,
h3,
h4,
h5,
h6 {
color: $myColor;
}

View File

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

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Chrome Extension Boilerplate (with React 16.6+ & Webpack 4+)</title>
</head>
<body>
<div id="app-container"></div>
</body>
</html>

View File

@@ -0,0 +1,9 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import Newtab from './Newtab';
import './index.css';
const container = document.getElementById('app-container');
const root = createRoot(container); // createRoot(container!) if you use TypeScript
root.render(<Newtab />);

View File

@@ -0,0 +1,8 @@
.OptionsContainer {
width: 100%;
height: 50vh;
font-size: 2rem;
display: flex;
align-items: center;
justify-content: center;
}

View File

@@ -0,0 +1,12 @@
import React from 'react';
import './Options.css';
interface Props {
title: string;
}
const Options: React.FC<Props> = ({ title }: Props) => {
return <div className="OptionsContainer">{title} Page</div>;
};
export default Options;

View File

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Settings</title>
</head>
<body>
<div id="app-container"></div>
</body>
</html>

View File

@@ -0,0 +1,9 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import Options from './Options';
import './index.css';
const container = document.getElementById('app-container');
const root = createRoot(container); // createRoot(container!) if you use TypeScript
root.render(<Options title={'Settings'} />);

View File

@@ -0,0 +1,7 @@
body {
background-color: #242424;
}
.container {
color: #ffffff;
}

12
src/pages/Panel/Panel.tsx Normal file
View File

@@ -0,0 +1,12 @@
import React from 'react';
import './Panel.css';
const Panel: React.FC = () => {
return (
<div className="container">
<h1>Dev Tools Panel</h1>
</div>
);
};
export default Panel;

View File

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Dev Tools Panel</title>
</head>
<body>
<div id="app-container"></div>
</body>
</html>

View File

@@ -0,0 +1,9 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import Panel from './Panel';
import './index.css';
const container = document.getElementById('app-container');
const root = createRoot(container); // createRoot(container!) if you use TypeScript
root.render(<Panel />);

45
src/pages/Popup/Popup.css Normal file
View File

@@ -0,0 +1,45 @@
.App {
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
text-align: center;
height: 100%;
padding: 10px;
background-color: #282c34;
}
.App-logo {
height: 30vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

27
src/pages/Popup/Popup.jsx Normal file
View File

@@ -0,0 +1,27 @@
import React from 'react';
import logo from '../../assets/img/logo.svg';
import Greetings from '../../containers/Greetings/Greetings';
import './Popup.css';
const Popup = () => {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/pages/Popup/Popup.jsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React!
</a>
</header>
</div>
);
};
export default Popup;

17
src/pages/Popup/index.css Normal file
View File

@@ -0,0 +1,17 @@
body {
width: 300px;
height: 260px;
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;
position: relative;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Popup</title>
</head>
<body>
<div id="app-container"></div>
</body>
</html>

View File

@@ -0,0 +1,9 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import Popup from './Popup';
import './index.css';
const container = document.getElementById('app-container');
const root = createRoot(container); // createRoot(container!) if you use TypeScript
root.render(<Popup />);

20
tsconfig.json Normal file
View File

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

27
utils/build.js Executable file
View File

@@ -0,0 +1,27 @@
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';
process.env.ASSET_PATH = '/';
var webpack = require('webpack'),
path = require('path'),
fs = require('fs'),
config = require('../webpack.config'),
ZipPlugin = require('zip-webpack-plugin');
delete config.chromeExtensionBoilerplate;
config.mode = 'production';
var packageInfo = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
config.plugins = (config.plugins || []).concat(
new ZipPlugin({
filename: `${packageInfo.name}-${packageInfo.version}.zip`,
path: path.join(__dirname, '../', 'zip'),
})
);
webpack(config, function (err) {
if (err) throw err;
});

5
utils/env.js Executable file
View File

@@ -0,0 +1,5 @@
// tiny wrapper with default env vars
module.exports = {
NODE_ENV: process.env.NODE_ENV || 'development',
PORT: process.env.PORT || 3000,
};

56
utils/webserver.js Executable file
View File

@@ -0,0 +1,56 @@
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';
process.env.ASSET_PATH = '/';
var WebpackDevServer = require('webpack-dev-server'),
webpack = require('webpack'),
config = require('../webpack.config'),
env = require('./env'),
path = require('path');
var options = config.chromeExtensionBoilerplate || {};
var excludeEntriesToHotReload = options.notHotReload || [];
for (var entryName in config.entry) {
if (excludeEntriesToHotReload.indexOf(entryName) === -1) {
config.entry[entryName] = [
'webpack/hot/dev-server',
`webpack-dev-server/client?hot=true&hostname=localhost&port=${env.PORT}`,
].concat(config.entry[entryName]);
}
}
delete config.chromeExtensionBoilerplate;
var compiler = webpack(config);
var server = new WebpackDevServer(
{
https: false,
hot: true,
liveReload: false,
client: {
webSocketTransport: 'sockjs',
},
webSocketServer: 'sockjs',
host: 'localhost',
port: env.PORT,
static: {
directory: path.join(__dirname, '../build'),
},
devMiddleware: {
publicPath: `http://localhost:${env.PORT}/`,
writeToDisk: true,
},
headers: {
'Access-Control-Allow-Origin': '*',
},
allowedHosts: 'all',
},
compiler
);
(async () => {
await server.start();
})();

253
webpack.config.js Normal file → Executable file
View File

@@ -1,31 +1,236 @@
const path = require('path');
const CopyPlugin = require("copy-webpack-plugin");
var webpack = require('webpack'),
path = require('path'),
fileSystem = require('fs-extra'),
env = require('./utils/env'),
CopyWebpackPlugin = require('copy-webpack-plugin'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
TerserPlugin = require('terser-webpack-plugin');
var { CleanWebpackPlugin } = require('clean-webpack-plugin');
var ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
var ReactRefreshTypeScript = require('react-refresh-typescript');
module.exports = {
mode: 'development',
const ASSET_PATH = process.env.ASSET_PATH || '/';
var alias = {};
// load the secrets
var secretsPath = path.join(__dirname, 'secrets.' + env.NODE_ENV + '.js');
var fileExtensions = [
'jpg',
'jpeg',
'png',
'gif',
'eot',
'otf',
'svg',
'ttf',
'woff',
'woff2',
];
if (fileSystem.existsSync(secretsPath)) {
alias['secrets'] = secretsPath;
}
const isDevelopment = process.env.NODE_ENV !== 'production';
var options = {
mode: process.env.NODE_ENV || 'development',
entry: {
newtab: path.join(__dirname, 'src', 'pages', 'Newtab', 'index.jsx'),
options: path.join(__dirname, 'src', 'pages', 'Options', 'index.jsx'),
popup: path.join(__dirname, 'src', 'pages', 'Popup', 'index.jsx'),
background: path.join(__dirname, 'src', 'pages', 'Background', 'index.js'),
contentScript: path.join(__dirname, 'src', 'pages', 'Content', 'index.js'),
devtools: path.join(__dirname, 'src', 'pages', 'Devtools', 'index.js'),
panel: path.join(__dirname, 'src', 'pages', 'Panel', 'index.jsx'),
},
chromeExtensionBoilerplate: {
notHotReload: ['background', 'contentScript', 'devtools'],
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'build'),
clean: true,
publicPath: ASSET_PATH,
},
module: {
rules: [
{
// look for .css or .scss files
test: /\.(css|scss)$/,
// in the `src` directory
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
},
},
],
},
{
test: new RegExp('.(' + fileExtensions.join('|') + ')$'),
type: 'asset/resource',
exclude: /node_modules/,
// loader: 'file-loader',
// options: {
// name: '[name].[ext]',
// },
},
{
test: /\.html$/,
loader: 'html-loader',
exclude: /node_modules/,
},
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve('ts-loader'),
options: {
getCustomTransformers: () => ({
before: [isDevelopment && ReactRefreshTypeScript()].filter(
Boolean
),
}),
transpileOnly: isDevelopment,
},
},
],
},
{
test: /\.(js|jsx)$/,
use: [
{
loader: 'source-map-loader',
},
{
loader: require.resolve('babel-loader'),
options: {
plugins: [
isDevelopment && require.resolve('react-refresh/babel'),
].filter(Boolean),
},
},
],
exclude: /node_modules/,
},
],
},
resolve: {
alias: alias,
extensions: fileExtensions
.map((extension) => '.' + extension)
.concat(['.js', '.jsx', '.ts', '.tsx', '.css']),
},
plugins: [
new CopyPlugin({
isDevelopment && new ReactRefreshWebpackPlugin(),
new CleanWebpackPlugin({ verbose: false }),
new webpack.ProgressPlugin(),
// expose and write the allowed env vars on the compiled bundle
new webpack.EnvironmentPlugin(['NODE_ENV']),
new CopyWebpackPlugin({
patterns: [
{ from: 'index.html' },
{ from: 'pkg/', to: 'pkg/' },
{
from: 'src/manifest.json',
to: path.join(__dirname, 'build'),
force: true,
transform: function (content, path) {
// generates the manifest file using the package.json informations
return Buffer.from(
JSON.stringify({
description: process.env.npm_package_description,
version: process.env.npm_package_version,
...JSON.parse(content.toString()),
})
);
},
},
],
}),
],
// FIXME: this is required to show which library causes
// "Module not found: Error: Can't resolve 'env'"
resolve: {
fallback: {
"env": false
},
new CopyWebpackPlugin({
patterns: [
{
from: 'src/pages/Content/content.styles.css',
to: path.join(__dirname, 'build'),
force: true,
},
],
}),
new CopyWebpackPlugin({
patterns: [
{
from: 'src/assets/img/icon-128.png',
to: path.join(__dirname, 'build'),
force: true,
},
],
}),
new CopyWebpackPlugin({
patterns: [
{
from: 'src/assets/img/icon-34.png',
to: path.join(__dirname, 'build'),
force: true,
},
],
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src', 'pages', 'Newtab', 'index.html'),
filename: 'newtab.html',
chunks: ['newtab'],
cache: false,
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src', 'pages', 'Options', 'index.html'),
filename: 'options.html',
chunks: ['options'],
cache: false,
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src', 'pages', 'Popup', 'index.html'),
filename: 'popup.html',
chunks: ['popup'],
cache: false,
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src', 'pages', 'Devtools', 'index.html'),
filename: 'devtools.html',
chunks: ['devtools'],
cache: false,
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src', 'pages', 'Panel', 'index.html'),
filename: 'panel.html',
chunks: ['panel'],
cache: false,
}),
].filter(Boolean),
infrastructureLogging: {
level: 'info',
},
entry: {
bootstrap: path.join(__dirname, 'bootstrap'),
worker: path.join(__dirname, 'worker.js'),
},
devServer: {
headers: {
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin',
}
}
};
if (env.NODE_ENV === 'development') {
options.devtool = 'cheap-module-source-map';
} else {
options.optimization = {
minimize: true,
minimizer: [
new TerserPlugin({
extractComments: false,
}),
],
};
}
module.exports = options;

View File

@@ -1,12 +0,0 @@
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 5566 });
wss.on('connection', function connection(ws) {
ws.on('message', function message(data) {
console.log('received: %s', data);
});
ws.send('something');
});
console.log("ws server started")

View File

@@ -1,15 +0,0 @@
{
"name": "ws-server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"ws": "^8.13.0"
},
"type": "module"
}

View File

@@ -1,8 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
ws@^8.13.0:
version "8.13.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==