From 7e1ab560bdd29e0ba2fa2474ea2f07ac6356d188 Mon Sep 17 00:00:00 2001 From: Valentin Kruglikov Date: Thu, 2 Jan 2025 23:51:09 +0700 Subject: [PATCH] Second iteration ;) --- package-lock.json | 502 +++++++++++++++++- package.json | 5 +- packages/connect/global.d.ts | 7 + packages/connect/package.json | 2 +- packages/connect/src/constants.ts | 7 - packages/connect/src/index.ts | 1 - packages/connect/src/resolver.ts | 53 +- packages/connect/tsconfig.json | 2 +- packages/core/package.json | 16 + packages/core/src/index.ts | 1 + packages/core/src/types.ts | 68 +++ packages/core/tsconfig.json | 4 + packages/demo/package.json | 5 +- packages/demo/src/json-config.json | 13 - packages/demo/webpack.config.js | 2 +- packages/extension/declaration.d.ts | 4 + packages/extension/global.d.ts | 7 + packages/extension/package.json | 8 +- packages/extension/public/icon.png | Bin 0 -> 2917 bytes packages/extension/public/manifest.json | 7 + packages/extension/public/popup.html | 13 +- packages/extension/src/background.ts | 150 ++++-- packages/extension/src/content.ts | 43 +- packages/extension/src/injected.ts | 46 ++ packages/extension/src/popup.ts | 58 +- .../src/popup/Alert/Alert.module.css | 9 + packages/extension/src/popup/Alert/Alert.tsx | 7 + packages/extension/src/popup/App.module.css | 62 +++ packages/extension/src/popup/App.tsx | 136 +++++ .../src/popup/Button/Button.module.css | 23 + .../extension/src/popup/Button/Button.tsx | 7 + packages/extension/src/popup/DocsLink.tsx | 22 + .../src/popup/Status/Status.module.css | 31 ++ .../extension/src/popup/Status/Status.tsx | 21 + packages/extension/src/popup/utils.ts | 89 ++++ packages/extension/src/utils/wds-listener.ts | 21 + packages/extension/tsconfig.json | 3 +- packages/extension/webpack.config.js | 44 +- packages/json-config/package.json | 19 + packages/json-config/src/example-invalid.json | 12 + packages/json-config/src/example.json | 26 + packages/json-config/src/index.ts | 29 + packages/json-config/tsconfig.json | 4 + tsconfig.json | 8 +- webpack.common.js | 43 +- 45 files changed, 1408 insertions(+), 232 deletions(-) create mode 100644 packages/connect/global.d.ts delete mode 100644 packages/connect/src/constants.ts create mode 100644 packages/core/package.json create mode 100644 packages/core/src/index.ts create mode 100644 packages/core/src/types.ts create mode 100644 packages/core/tsconfig.json delete mode 100644 packages/demo/src/json-config.json create mode 100644 packages/extension/declaration.d.ts create mode 100644 packages/extension/global.d.ts create mode 100644 packages/extension/public/icon.png create mode 100644 packages/extension/src/injected.ts create mode 100644 packages/extension/src/popup/Alert/Alert.module.css create mode 100644 packages/extension/src/popup/Alert/Alert.tsx create mode 100644 packages/extension/src/popup/App.module.css create mode 100644 packages/extension/src/popup/App.tsx create mode 100644 packages/extension/src/popup/Button/Button.module.css create mode 100644 packages/extension/src/popup/Button/Button.tsx create mode 100644 packages/extension/src/popup/DocsLink.tsx create mode 100644 packages/extension/src/popup/Status/Status.module.css create mode 100644 packages/extension/src/popup/Status/Status.tsx create mode 100644 packages/extension/src/popup/utils.ts create mode 100644 packages/extension/src/utils/wds-listener.ts create mode 100644 packages/json-config/package.json create mode 100644 packages/json-config/src/example-invalid.json create mode 100644 packages/json-config/src/example.json create mode 100644 packages/json-config/src/index.ts create mode 100644 packages/json-config/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 70180e5..48a85aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,12 @@ "devDependencies": { "@types/chrome": "^0.0.287", "@types/node": "^22.10.2", + "copy-webpack-plugin": "^12.0.2", + "css-loader": "^7.1.2", "microbundle": "^0.15.1", "msw": "^2.7.0", "prettier": "^3.4.2", + "style-loader": "^4.0.0", "ts-loader": "^9.5.1", "turbo": "^2.3.3", "typescript": "^5.7.2", @@ -1709,6 +1712,10 @@ "resolved": "packages/connect", "link": true }, + "node_modules/@msw-devtools/core": { + "resolved": "packages/core", + "link": true + }, "node_modules/@msw-devtools/demo": { "resolved": "packages/demo", "link": true @@ -1717,6 +1724,10 @@ "resolved": "packages/extension", "link": true }, + "node_modules/@msw-devtools/json-config": { + "resolved": "packages/json-config", + "link": true + }, "node_modules/@mswjs/interceptors": { "version": "0.37.3", "license": "MIT", @@ -1732,6 +1743,44 @@ "node": ">=18" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@open-draft/deferred-promise": { "version": "2.2.0", "license": "MIT" @@ -1855,6 +1904,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "dev": true, @@ -2048,6 +2110,26 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/react": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.2.tgz", + "integrity": "sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.2.tgz", + "integrity": "sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "dev": true, @@ -2973,6 +3055,16 @@ "node": ">=6" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "license": "MIT", @@ -3108,6 +3200,111 @@ "dev": true, "license": "MIT" }, + "node_modules/copy-webpack-plugin": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/copy-webpack-plugin/node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/core-js-compat": { "version": "3.39.0", "dev": true, @@ -3164,6 +3361,55 @@ "postcss": "^8.0.9" } }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/css-select": { "version": "4.3.0", "dev": true, @@ -3297,6 +3543,13 @@ "node": ">=8.0.0" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, "node_modules/data-view-buffer": { "version": "1.0.2", "dev": true, @@ -3906,6 +4159,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "dev": true, @@ -3924,6 +4194,16 @@ "node": ">= 4.9.1" } }, + "node_modules/fastq": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/faye-websocket": { "version": "0.11.4", "dev": true, @@ -4312,6 +4592,53 @@ "dev": true, "license": "MIT" }, + "node_modules/globby": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globrex": { "version": "0.1.2", "dev": true, @@ -4606,6 +4933,16 @@ "postcss": "^8.1.0" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/import-cwd": { "version": "3.0.0", "dev": true, @@ -5526,6 +5863,16 @@ "dev": true, "license": "MIT" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "dev": true, @@ -6778,6 +7125,27 @@ "version": "2.2.0", "license": "MIT" }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/randombytes": { "version": "2.1.0", "dev": true, @@ -6808,6 +7176,29 @@ "node": ">= 0.8" } }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "dev": true, @@ -7026,6 +7417,17 @@ "node": ">= 4" } }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rollup": { "version": "2.79.2", "dev": true, @@ -7247,6 +7649,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/sade": { "version": "1.8.1", "dev": true, @@ -7336,6 +7762,13 @@ "dev": true, "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "dev": true, + "license": "MIT" + }, "node_modules/schema-utils": { "version": "3.3.0", "dev": true, @@ -7879,6 +8312,23 @@ "dev": true, "license": "MIT" }, + "node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" + } + }, "node_modules/stylehacks": { "version": "5.1.1", "dev": true, @@ -8394,6 +8844,19 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/universalify": { "version": "2.0.1", "dev": true, @@ -9057,6 +9520,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "packages/connect": { "name": "@msw-devtools/connect", "version": "0.1", @@ -9065,12 +9537,18 @@ "msw": "^2.7.0" } }, + "packages/core": { + "name": "@msw-devtools/core", + "version": "0.1", + "license": "MIT" + }, "packages/demo": { "name": "@msw-devtools/demo", "version": "0.1", "license": "MIT", "devDependencies": { - "@msw-devtools/connect": "*" + "@msw-devtools/connect": "*", + "@msw-devtools/json-config": "*" } }, "packages/extension": { @@ -9078,8 +9556,28 @@ "version": "0.1", "license": "MIT", "devDependencies": { - "@msw-devtools/connect": "*" + "@msw-devtools/connect": "*", + "@msw-devtools/json-config": "*", + "@types/react": "^19.0.2", + "@types/react-dom": "^19.0.2", + "clsx": "^2.1.1", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, + "packages/json-config": { + "name": "@msw-devtools/json-config", + "version": "0.1", + "license": "MIT", + "dependencies": { + "zod": "^3.24.1" } + }, + "packages/types": { + "name": "@msw-devtools/types", + "version": "0.1", + "extraneous": true, + "license": "MIT" } } } diff --git a/package.json b/package.json index 6d7365a..e04e775 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ }, "packageManager": "npm@8.5.0", "scripts": { - "prepare": "npm run build", + "prepare": "turbo build", "check-types": "tsc --noEmit", "build": "turbo check-types build", "dev": "turbo dev" @@ -23,9 +23,12 @@ "devDependencies": { "@types/chrome": "^0.0.287", "@types/node": "^22.10.2", + "copy-webpack-plugin": "^12.0.2", + "css-loader": "^7.1.2", "microbundle": "^0.15.1", "msw": "^2.7.0", "prettier": "^3.4.2", + "style-loader": "^4.0.0", "ts-loader": "^9.5.1", "turbo": "^2.3.3", "typescript": "^5.7.2", diff --git a/packages/connect/global.d.ts b/packages/connect/global.d.ts new file mode 100644 index 0000000..77f7240 --- /dev/null +++ b/packages/connect/global.d.ts @@ -0,0 +1,7 @@ +import { WindowWithMswDevtools } from '@msw-devtools/core' + +declare global { + interface Window extends WindowWithMswDevtools {} +} + +export {} diff --git a/packages/connect/package.json b/packages/connect/package.json index 500cc16..dd145c3 100644 --- a/packages/connect/package.json +++ b/packages/connect/package.json @@ -4,7 +4,7 @@ "type": "module", "license": "MIT", "types": "./dist/index.d.ts", - "source": "src/index.ts", + "source": "./src/index.ts", "exports": { "require": "./dist/index.cjs", "default": "./dist/index.modern.js" diff --git a/packages/connect/src/constants.ts b/packages/connect/src/constants.ts deleted file mode 100644 index 7850551..0000000 --- a/packages/connect/src/constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const MESSAGE_TYPE = { - content: 'msw-devtools-extension-content', - handledRequest: 'msw-devtools-extension_handled', - setJsonConfig: 'msw-devtools-extension_setJsonConfig', - unhandledRequest: 'msw-devtools-extension_unhandled', - injected: 'msw-devtools-extension-injected' -} as const diff --git a/packages/connect/src/index.ts b/packages/connect/src/index.ts index 76d5e9d..332527e 100644 --- a/packages/connect/src/index.ts +++ b/packages/connect/src/index.ts @@ -1,2 +1 @@ export * from './resolver' -export * from './constants' diff --git a/packages/connect/src/resolver.ts b/packages/connect/src/resolver.ts index 87270e8..0a062c6 100644 --- a/packages/connect/src/resolver.ts +++ b/packages/connect/src/resolver.ts @@ -1,47 +1,20 @@ -import { HttpResponse, passthrough, ResponseResolver } from 'msw' - -import { MESSAGE_TYPE } from './constants' +import { passthrough, ResponseResolver, HttpResponse } from 'msw' export const responseResolver: ResponseResolver = ({ requestId, request }) => { return new Promise((resolve, reject) => { - window.postMessage( - { - type: MESSAGE_TYPE.injected, - requestId, - request: { - id: requestId, - method: request.method, - url: request.url - } - }, - window.location.origin - ) - const handleMessage = ( - e: MessageEvent<{ - type: (typeof MESSAGE_TYPE)[keyof typeof MESSAGE_TYPE] - requestId: string - response: { - body: BodyInit - init?: ResponseInit - } - }> - ) => { - if ( - e.data.requestId !== requestId || - e.data.type === MESSAGE_TYPE.injected - ) { - return - } - - window.removeEventListener('message', handleMessage) - - if (e.data.type === MESSAGE_TYPE.unhandledRequest) { - resolve(passthrough()) - } else if (e.data.type === MESSAGE_TYPE.handledRequest) { - resolve(new HttpResponse(e.data.response.body, e.data.response.init)) - } + if (window.__MSW_DEVTOOLS_EXTENSION?.resolve) { + window.__MSW_DEVTOOLS_EXTENSION + .resolve({ requestId, request }) + .then((response) => { + resolve(new HttpResponse(response.body, response.init)) + }) + .catch((e) => { + console.log(e) + resolve(passthrough()) + }) + } else { + resolve(passthrough()) } - window.addEventListener('message', handleMessage) }) } diff --git a/packages/connect/tsconfig.json b/packages/connect/tsconfig.json index a5cb75c..b7ef609 100644 --- a/packages/connect/tsconfig.json +++ b/packages/connect/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../../tsconfig.json", - "include": ["src/**/*.ts"] + "include": ["./src/**/*", "./global.d.ts"] } diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..67a9333 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,16 @@ +{ + "name": "@msw-devtools/core", + "version": "0.1", + "type": "module", + "license": "MIT", + "types": "./dist/index.d.ts", + "source": "./src/index.ts", + "main": "./dist/index.js", + "files": [ + "dist" + ], + "scripts": { + "build": "microbundle --no-pkg-main -f modern", + "dev": "microbundle watch --no-pkg-main -f modern" + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 0000000..c9f6f04 --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1 @@ +export * from './types' diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts new file mode 100644 index 0000000..e050397 --- /dev/null +++ b/packages/core/src/types.ts @@ -0,0 +1,68 @@ +import type { JsonConfig } from '@msw-devtools/json-config' +import type { ResponseResolver } from 'msw' + +export enum MessageType { + Content = 'msw-devtools-extension-content', + HandledRequest = 'msw-devtools-extension_handled', + SetJsonConfig = 'msw-devtools-extension_setJsonConfig', + UnhandledRequest = 'msw-devtools-extension_unhandled', + Status = 'msw-devtools-extension_status', + Injected = 'msw-devtools-extension-injected' +} + +export type MswDevtoolsExtensionTransporter = { + resolve: (...args: Parameters) => Promise +} + +export interface WindowWithMswDevtools { + __MSW_DEVTOOLS_EXTENSION?: MswDevtoolsExtensionTransporter +} + +export type ChromeExtensionLocalStorage = { + jsonConfig: JsonConfig +} + +export type BackgroundReceiveMessage = + | { + type: MessageType.Content + request: { + url: string + method: string + } + } + | { + type: MessageType.SetJsonConfig + payload: JsonConfig + } + | { + type: MessageType.Status + } + +export type BackgroundResponseMessage = + | { + type: MessageType.HandledRequest + response: JsonConfig[string] + } + | { + type: MessageType.UnhandledRequest + } + | { + type: MessageType.SetJsonConfig + status: 'success' + } + | ({ + type: MessageType.Status + } & ( + | { status: 'error' } + | { + status: 'success' + payload: { + host: string + hasHandle: boolean + hasConfig: boolean + configIsValid: boolean + } + } + )) + +export type { JsonConfig } diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000..752e48c --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["./src/**/*"] +} diff --git a/packages/demo/package.json b/packages/demo/package.json index 765c918..9c24ddf 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -7,11 +7,12 @@ "build": "NODE_ENV=production npx webpack" }, "devDependencies": { - "@msw-devtools/connect": "*" + "@msw-devtools/connect": "*", + "@msw-devtools/json-config": "*" }, "msw": { "workerDirectory": [ - "public" + "dist" ] } } diff --git a/packages/demo/src/json-config.json b/packages/demo/src/json-config.json deleted file mode 100644 index c5c121d..0000000 --- a/packages/demo/src/json-config.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "/test": { - "body": { - "firstName": "John", - "lastName": "Maverick" - }, - "init": { - "headers": { - "Content-Type": "application/json" - } - } - } -} diff --git a/packages/demo/webpack.config.js b/packages/demo/webpack.config.js index a56407c..1ac3c76 100644 --- a/packages/demo/webpack.config.js +++ b/packages/demo/webpack.config.js @@ -1,7 +1,7 @@ const createWebpackConfig = require('../../webpack.common.js') module.exports = { - ...createWebpackConfig({ root: __dirname }), + ...createWebpackConfig({ root: __dirname, port: 8081 }), entry: { main: './src/index.ts' } diff --git a/packages/extension/declaration.d.ts b/packages/extension/declaration.d.ts new file mode 100644 index 0000000..1e6388f --- /dev/null +++ b/packages/extension/declaration.d.ts @@ -0,0 +1,4 @@ +declare module '*.css' { + const content: Record + export default content +} diff --git a/packages/extension/global.d.ts b/packages/extension/global.d.ts new file mode 100644 index 0000000..77f7240 --- /dev/null +++ b/packages/extension/global.d.ts @@ -0,0 +1,7 @@ +import { WindowWithMswDevtools } from '@msw-devtools/core' + +declare global { + interface Window extends WindowWithMswDevtools {} +} + +export {} diff --git a/packages/extension/package.json b/packages/extension/package.json index ac2cfec..2454d96 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -7,6 +7,12 @@ "build": "NODE_ENV=production npx webpack" }, "devDependencies": { - "@msw-devtools/connect": "*" + "@msw-devtools/connect": "*", + "@msw-devtools/json-config": "*", + "@types/react": "^19.0.2", + "@types/react-dom": "^19.0.2", + "clsx": "^2.1.1", + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/extension/public/icon.png b/packages/extension/public/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6ab034e44005a01e829f34a59318e47af369cb58 GIT binary patch literal 2917 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 literal 0 HcmV?d00001 diff --git a/packages/extension/public/manifest.json b/packages/extension/public/manifest.json index 6e4f0d7..daad925 100644 --- a/packages/extension/public/manifest.json +++ b/packages/extension/public/manifest.json @@ -13,8 +13,15 @@ "run_at": "document_start" } ], + "web_accessible_resources": [ + { + "resources": ["injected.js"], + "matches": [""] + } + ], "permissions": ["storage", "tabs", "activeTab"], "action": { + "default_icon": "icon.png", "default_popup": "popup.html", "default_title": "Upload JSON" } diff --git a/packages/extension/public/popup.html b/packages/extension/public/popup.html index 2eb775f..76c2028 100644 --- a/packages/extension/public/popup.html +++ b/packages/extension/public/popup.html @@ -1,19 +1,10 @@ - Upload JSON - + MSW devtools extension - - -

+
diff --git a/packages/extension/src/background.ts b/packages/extension/src/background.ts index 5329182..a0dca8e 100644 --- a/packages/extension/src/background.ts +++ b/packages/extension/src/background.ts @@ -1,61 +1,111 @@ -import { MESSAGE_TYPE } from '@msw-devtools/connect' +import { + MessageType, + BackgroundReceiveMessage, + ChromeExtensionLocalStorage, + BackgroundResponseMessage +} from '@msw-devtools/core' const getJsonConfig = () => - new Promise< - | Record< - string, - { - body: BodyInit - init?: ResponseInit + new Promise((resolve, reject) => { + try { + chrome.storage.local.get( + 'jsonConfig', + (result) => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError.message) + } else { + resolve(result.jsonConfig) + } } - > - | undefined - >((resolve, reject) => { - chrome.storage.local.get('jsonConfig', (result) => { - if (chrome.runtime.lastError) { - reject( - 'Failed to retrieve JSON config:' + chrome.runtime.lastError.message - ) - } else { - console.log('Retrieved JSON config:', result.jsonConfig) - resolve(result.jsonConfig) - } - }) + ) + } catch (e) { + reject(e) + } }) -chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => { - if (message.type === MESSAGE_TYPE.content) { - const url = new URL(message.request.url) - const path = url.pathname + url.search - const jsonPathMapper = await getJsonConfig() +chrome.runtime.onMessage.addListener( + ( + message: BackgroundReceiveMessage, + sender, + sendResponse: (response: BackgroundResponseMessage) => void + ) => { + if (message.type === MessageType.Content) { + const url = new URL(message.request.url) + const path = url.pathname + url.search + + getJsonConfig() + .then((jsonPathMapper) => { + const response = jsonPathMapper + ? jsonPathMapper[path] || jsonPathMapper[url.origin + path] + : null + + if ( + !response || + message.request.method !== (response.method || 'GET') + ) { + return Promise.reject('No response found') + } + + sendResponse({ + type: MessageType.HandledRequest, + response: { + ...response, + body: + typeof response.body !== 'string' + ? JSON.stringify(response.body) + : response.body + } + }) + }) + .catch(() => { + sendResponse({ + type: MessageType.UnhandledRequest + }) + }) + } else if (message.type === MessageType.SetJsonConfig) { + chrome.storage.local.set( + { jsonConfig: message.payload }, + () => { + if (chrome.runtime.lastError) { + console.error( + 'Failed to save JSON config:', + chrome.runtime.lastError.message + ) + } - const response = jsonPathMapper - ? jsonPathMapper[path] || jsonPathMapper[url.origin + path] - : null + sendResponse({ type: message.type, status: 'success' }) + } + ) + } else if (message.type === MessageType.Status) { + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + try { + const tabUrl = tabs[0]?.url + if (!tabUrl) { + throw new Error('Failed to retrieve the active tab URL.') + } + + const host = new URL(tabUrl).host - if (!response) { - sendResponse({ - type: MESSAGE_TYPE.unhandledRequest + sendResponse({ + type: message.type, + status: 'success', + payload: { + host, + hasHandle: true, + hasConfig: true, + configIsValid: true + } + }) + } catch (e) { + console.error(e) + sendResponse({ + type: message.type, + status: 'error' + }) + } }) - return true } - sendResponse({ - type: MESSAGE_TYPE.handledRequest, - response - }) - } else if (message.type === MESSAGE_TYPE.setJsonConfig) { - chrome.storage.local.set({ jsonConfig: message.payload }, () => { - if (chrome.runtime.lastError) { - console.error( - 'Failed to save JSON config:', - chrome.runtime.lastError.message - ) - } else { - console.log('JSON config saved successfully!') - } - }) + return true } - - return true -}) +) diff --git a/packages/extension/src/content.ts b/packages/extension/src/content.ts index b29cf3d..6737386 100644 --- a/packages/extension/src/content.ts +++ b/packages/extension/src/content.ts @@ -1,27 +1,44 @@ -import { MESSAGE_TYPE } from '@msw-devtools/connect' +import { MessageType } from '@msw-devtools/core' +import { + BackgroundReceiveMessage, + BackgroundResponseMessage +} from '@msw-devtools/core/src' + +const script = document.createElement('script') +script.src = chrome.runtime.getURL('injected.js') +script.onload = () => { + script.remove() +} +;(document.head || document.documentElement).appendChild(script) window.addEventListener('message', (event) => { if ( event.source === window && - event.data.type === MESSAGE_TYPE.injected && + event.data.type === MessageType.Injected && event.data.requestId ) { - chrome.runtime.sendMessage( - { type: MESSAGE_TYPE.content, request: event.data.request }, - (message) => { - if (chrome.runtime.lastError) { - console.error(chrome.runtime.lastError) - return - } - + chrome.runtime.sendMessage< + BackgroundReceiveMessage, + BackgroundResponseMessage + >({ type: MessageType.Content, request: event.data.request }, (message) => { + if (chrome.runtime.lastError) { + console.error(chrome.runtime.lastError) window.postMessage( { - ...message, - requestId: event.data.requestId + type: MessageType.UnhandledRequest }, window.location.origin ) + return } - ) + + window.postMessage( + { + ...message, + requestId: event.data.requestId + }, + window.location.origin + ) + }) } }) diff --git a/packages/extension/src/injected.ts b/packages/extension/src/injected.ts new file mode 100644 index 0000000..421088a --- /dev/null +++ b/packages/extension/src/injected.ts @@ -0,0 +1,46 @@ +import { MessageType } from '@msw-devtools/core' + +window.__MSW_DEVTOOLS_EXTENSION = { + resolve: ({ request, requestId }) => { + return new Promise((resolve, reject) => { + const handleMessage = ( + e: MessageEvent<{ + type: MessageType + requestId: string + response: { + body: BodyInit + init?: ResponseInit + } + }> + ) => { + if ( + e.data.requestId !== requestId || + e.data.type === MessageType.Injected + ) { + return + } + + window.removeEventListener('message', handleMessage) + if (e.data.type === MessageType.UnhandledRequest) { + reject() + } else if (e.data.type === MessageType.HandledRequest) { + resolve(e.data.response) + } + } + window.addEventListener('message', handleMessage) + + window.postMessage( + { + type: MessageType.Injected, + requestId, + request: { + id: requestId, + method: request.method, + url: request.url + } + }, + window.location.origin + ) + }) + } +} diff --git a/packages/extension/src/popup.ts b/packages/extension/src/popup.ts index 2c98674..6ecec1d 100644 --- a/packages/extension/src/popup.ts +++ b/packages/extension/src/popup.ts @@ -1,55 +1,7 @@ -import { MESSAGE_TYPE } from '@msw-devtools/connect' +import { createRoot } from 'react-dom/client' +import { createElement } from 'react' +import { App } from './popup/App' -const saveTo = async (...asd: any[]) => { - const jsonData = asd[0] - return new Promise((resolve, reject) => { - chrome.runtime.sendMessage( - { type: MESSAGE_TYPE.setJsonConfig, payload: jsonData }, - (response) => { - if (chrome.runtime.lastError) { - console.error( - 'Error sending message:', - chrome.runtime.lastError.message - ) - reject(chrome.runtime.lastError) - } else if (response?.status === 'success') { - resolve() - } else { - reject(new Error('Failed to process the JSON in background.')) - } - } - ) - }) -} +const root = createRoot(document.getElementById('root')!) -document.getElementById('uploadButton')?.addEventListener('click', () => { - const fileInput = document.getElementById('fileInput') as HTMLInputElement - const file = fileInput.files?.[0] - - if (!file) { - alert('Please select a JSON file.') - return - } - const reader = new FileReader() - - reader.onload = function (event) { - try { - const jsonData = JSON.parse(event.target?.result as string) - - saveTo(jsonData) - .then(() => { - document.getElementById('status')!.textContent = - 'JSON uploaded successfully!' - }) - .catch((error) => { - document.getElementById('status')!.textContent = - 'Failed to upload JSON.' - }) - } catch (error) { - console.error(error) - document.getElementById('status')!.textContent = 'Invalid JSON file.' - } - } - - reader.readAsText(file) -}) +root.render(createElement(App)) diff --git a/packages/extension/src/popup/Alert/Alert.module.css b/packages/extension/src/popup/Alert/Alert.module.css new file mode 100644 index 0000000..c213cc1 --- /dev/null +++ b/packages/extension/src/popup/Alert/Alert.module.css @@ -0,0 +1,9 @@ +.wrapper { + border: 1px solid #334155; + border-radius: 8px; + padding: 5px 10px; + background: #2a2c30; + font-weight: bold; + color: #ffffff; + line-height: 1.4em; +} \ No newline at end of file diff --git a/packages/extension/src/popup/Alert/Alert.tsx b/packages/extension/src/popup/Alert/Alert.tsx new file mode 100644 index 0000000..56d4e70 --- /dev/null +++ b/packages/extension/src/popup/Alert/Alert.tsx @@ -0,0 +1,7 @@ +import React, { FC, PropsWithChildren } from 'react' + +import styles from './Alert.module.css' + +export const Alert: FC = (props) => { + return
+} diff --git a/packages/extension/src/popup/App.module.css b/packages/extension/src/popup/App.module.css new file mode 100644 index 0000000..78ec826 --- /dev/null +++ b/packages/extension/src/popup/App.module.css @@ -0,0 +1,62 @@ +html, +body { + font-family: Arial, sans-serif; + font-size: 14px; + padding: 0; + margin: 0; + background: #171717; +} + +:root { + --primary-color: rgb(255 106 51); +} + +:global(.text-primary) { + color: var(--primary-color); +} + +* { + box-sizing: border-box !important; +} + +.wrapper { + width: 300px; + color: #ffffff; + padding: 10px; + + display: flex; + flex-direction: column; + gap: 10px; +} + +.fileInput { + display: none; +} + +.footer { + opacity: 0.5; + text-align: center; + + &:hover { + opacity: 0.8; + } +} + +.status { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 10px; + + .icon { + width: 10px; + height: 10px; + border-radius: 50%; + background: #2ce32c; + } +} + +.header { + font-weight: bold; + color: var(--primary-color) +} \ No newline at end of file diff --git a/packages/extension/src/popup/App.tsx b/packages/extension/src/popup/App.tsx new file mode 100644 index 0000000..86881e9 --- /dev/null +++ b/packages/extension/src/popup/App.tsx @@ -0,0 +1,136 @@ +import React, { + ChangeEventHandler, + ComponentProps, + useCallback, + useEffect, + useRef, + useState +} from 'react' +import { validate } from '@msw-devtools/json-config' + +import packageJson from '../../package.json' + +import { getStatus, saveTo } from './utils' + +import { Alert } from './Alert/Alert' +import { Button } from './Button/Button' +import { Status } from './Status/Status' +import { DocsLink } from './DocsLink' + +import styles from './App.module.css' + +export const App = () => { + const [isLoading, setIsLoading] = useState(false) + const [host, setHost] = useState() + + const [handleStatus, setHandleStatus] = + useState['type']>('pending') + const [configStatus, setConfigStatus] = + useState['type']>('pending') + const [verificationStatus, setVerificationStatus] = + useState['type']>('pending') + const inputRef = useRef(null) + + const fetchStatuses = useCallback(async () => { + setHandleStatus('pending') + setConfigStatus('pending') + setVerificationStatus('pending') + setHost(undefined) + + const statuses = await getStatus() + + setHandleStatus(statuses.hasHandle ? 'success' : 'error') + setConfigStatus(statuses.hasConfig ? 'success' : 'error') + setVerificationStatus(statuses.configIsValid ? 'success' : 'error') + setHost(statuses.host) + }, []) + + useEffect(() => { + fetchStatuses() + }, [fetchStatuses]) + + const handleLoadFiles: ChangeEventHandler = (event) => { + const file = event.target.files?.[0] + if (!file) return + inputRef.current!.value = '' + + setIsLoading(true) + setVerificationStatus('pending') + + const reader = new FileReader() + reader.onload = async (event) => { + try { + const jsonData = JSON.parse(event.target?.result as string) + const jsonConfig = validate(jsonData) + await saveTo(jsonConfig) + setConfigStatus('success') + setVerificationStatus('success') + } catch (e) { + setVerificationStatus('error') + console.error(e) + } finally { + setIsLoading(false) + } + } + reader.readAsText(file) + } + + return ( +
+
{host}
+ + + {(() => { + switch (handleStatus) { + case 'success': + return <>Handler has been detected + default: + return ( + <> + Handler has not been detected, please follow the{' '} + getting started guide + + ) + } + })()} + + + {(() => { + switch (configStatus) { + case 'success': + return <>JSON config has been detected + default: + return <>Upload JSON file with mocks + } + })()} + + {verificationStatus !== 'pending' && ( + + {(() => { + switch (verificationStatus) { + case 'success': + return <>JSON config is valid + default: + return <>Invalid JSON, please check the format + } + })()} + + )} + + + +
+ For more details, visit Github (v + {packageJson.version}) +
+
+ ) +} diff --git a/packages/extension/src/popup/Button/Button.module.css b/packages/extension/src/popup/Button/Button.module.css new file mode 100644 index 0000000..bd7e5a8 --- /dev/null +++ b/packages/extension/src/popup/Button/Button.module.css @@ -0,0 +1,23 @@ +.wrapper { + all: unset; + + cursor: pointer; + width: 100%; + padding: 10px; + border: 1px solid #404040; + border-radius: 8px; + text-align: center; + font-weight: bold; + background-color: #282828; + color: rgb(255 255 255); + + &:hover { + background-color: #404040; + } + + &:disabled { + cursor: not-allowed; + background-color: #282828; + opacity: 0.5; + } +} \ No newline at end of file diff --git a/packages/extension/src/popup/Button/Button.tsx b/packages/extension/src/popup/Button/Button.tsx new file mode 100644 index 0000000..7654853 --- /dev/null +++ b/packages/extension/src/popup/Button/Button.tsx @@ -0,0 +1,7 @@ +import React, { ButtonHTMLAttributes, FC } from 'react' + +import styles from './Button.module.css' + +export const Button: FC> = (props) => { + return