diff --git a/.eslintrc b/.eslintrc index 0173af86..0a4c4b16 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,7 +1,8 @@ { "extends": "eslint:recommended", "env": { - "node": true + "node": true, + "es2020": true }, "parserOptions": { "ecmaVersion": 2022, @@ -82,15 +83,7 @@ "yoda": "error" }, "globals": { - "Proxy": "readonly", - "Set": "readonly", - "WeakMap": "readonly", - "WeakSet": "readonly", - "Map": "readonly", - "Promise": "readonly", - "Reflect": "readonly", "DiscordNative": "readonly", - "__non_webpack_require__": "readonly", - "Symbol": "readonly" + "__non_webpack_require__": "readonly" } } \ No newline at end of file diff --git a/common/clone.js b/common/clone.js new file mode 100644 index 00000000..303a98a2 --- /dev/null +++ b/common/clone.js @@ -0,0 +1,19 @@ +export function getKeys(object) { + const keys = []; + + for (const key in object) keys.push(key); + + return keys; +} + +export default function cloneObject(target, newObject = {}, keys) { + if (!Array.isArray(keys)) keys = getKeys(target); + + return keys.reduce((clone, key) => { + if (typeof(target[key]) === "object" && !Array.isArray(target[key]) && target[key] !== null) clone[key] = cloneObject(target[key], {}); + else if (typeof target[key] === "function") clone[key] = target[key].bind(target); + else clone[key] = target[key]; + + return clone; + }, newObject); +} \ No newline at end of file diff --git a/common/events.js b/common/events.js new file mode 100644 index 00000000..8ff8c1b3 --- /dev/null +++ b/common/events.js @@ -0,0 +1,36 @@ +import Logger from "./logger"; + +export default class EventEmitter { + static get EventEmitter() {return EventEmitter;} + + constructor() { + this.events = {}; + } + + setMaxListeners() {} + + on(event, callback) { + if (!this.events[event]) this.events[event] = new Set(); + + this.events[event].add(callback); + } + + emit(event, ...args) { + if (!this.events[event]) return; + + for (const [index, listener] of this.events[event].entries()) { + try { + listener(...args); + } + catch (error) { + Logger.error("Emitter", `Cannot fire listener for event ${event} at position ${index}:`, error); + } + } + } + + off(event, callback) { + if (!this.events[event]) return; + + return this.events[event].delete(callback); + } +} \ No newline at end of file diff --git a/injector/src/modules/betterdiscord.js b/injector/src/modules/betterdiscord.js index 608b2494..1f116f2d 100644 --- a/injector/src/modules/betterdiscord.js +++ b/injector/src/modules/betterdiscord.js @@ -65,7 +65,8 @@ export default class BetterDiscord { try { ${content} return true; - } catch { + } catch(error) { + console.error(error); return false; } })(); diff --git a/package.json b/package.json index 373c2700..83c27999 100644 --- a/package.json +++ b/package.json @@ -11,17 +11,17 @@ "build-preload": "pnpm --filter preload build", "pack-emotes": "node scripts/emotes.js", "inject": "node scripts/inject.js", - "lint": "eslint --ext .js common/ && pnpm --filter injector lint && pnpm --filter preload lint && pnpm --filter renderer lint", + "lint": "eslint --ext .js common/ && pnpm --filter injector lint && pnpm --filter preload lint && pnpm --filter renderer lint-js", "test": "mocha --require @babel/register --recursive \"./tests/renderer/*.js\"", "dist": "pnpm run build-prod && node scripts/pack.js", "api": "jsdoc -X renderer/src/modules/pluginapi.js > jsdoc-ast.json" }, "devDependencies": { - "asar": "^3.0.3", - "eslint": "^7.12.0", - "eslint-plugin-react": "^7.21.5", + "asar": "^3.2.0", + "eslint": "^8.23.0", + "eslint-plugin-react": "^7.31.6", "mocha": "^10.0.0", - "webpack": "^5.73.0", + "webpack": "^5.74.0", "webpack-cli": "^4.10.0" }, "engines": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6fe566a3..3a001d91 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,19 +4,19 @@ importers: .: specifiers: - asar: ^3.0.3 - eslint: ^7.12.0 - eslint-plugin-react: ^7.21.5 + asar: ^3.2.0 + eslint: ^8.23.0 + eslint-plugin-react: ^7.31.6 mocha: ^10.0.0 - webpack: ^5.73.0 + webpack: ^5.74.0 webpack-cli: ^4.10.0 devDependencies: - asar: 3.1.0 - eslint: 7.32.0 - eslint-plugin-react: 7.30.1_eslint@7.32.0 + asar: 3.2.0 + eslint: 8.23.0 + eslint-plugin-react: 7.31.6_eslint@8.23.0 mocha: 10.0.0 - webpack: 5.73.0_webpack-cli@4.10.0 - webpack-cli: 4.10.0_webpack@5.73.0 + webpack: 5.74.0_webpack-cli@4.10.0 + webpack-cli: 4.10.0_webpack@5.74.0 injector: specifiers: @@ -76,12 +76,6 @@ packages: '@jridgewell/trace-mapping': 0.3.14 dev: true - /@babel/code-frame/7.12.11: - resolution: {integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==} - dependencies: - '@babel/highlight': 7.17.12 - dev: true - /@babel/code-frame/7.18.6: resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} engines: {node: '>=6.9.0'} @@ -150,7 +144,7 @@ packages: '@babel/compat-data': 7.18.8 '@babel/core': 7.18.6 '@babel/helper-validator-option': 7.18.6 - browserslist: 4.21.0 + browserslist: 4.21.1 semver: 6.3.0 dev: true @@ -319,11 +313,6 @@ packages: '@babel/types': 7.18.8 dev: true - /@babel/helper-validator-identifier/7.16.7: - resolution: {integrity: sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==} - engines: {node: '>=6.9.0'} - dev: true - /@babel/helper-validator-identifier/7.18.6: resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==} engines: {node: '>=6.9.0'} @@ -357,15 +346,6 @@ packages: - supports-color dev: true - /@babel/highlight/7.17.12: - resolution: {integrity: sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.16.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - dev: true - /@babel/highlight/7.18.6: resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} engines: {node: '>=6.9.0'} @@ -1331,25 +1311,25 @@ packages: engines: {node: '>=10.0.0'} dev: true - /@eslint/eslintrc/0.4.3: - resolution: {integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==} - engines: {node: ^10.12.0 || >=12.0.0} + /@eslint/eslintrc/1.3.1: + resolution: {integrity: sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 debug: 4.3.4 - espree: 7.3.1 + espree: 9.4.0 globals: 13.15.0 - ignore: 4.0.6 + ignore: 5.2.0 import-fresh: 3.3.0 - js-yaml: 3.14.1 + js-yaml: 4.1.0 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color dev: true - /@humanwhocodes/config-array/0.5.0: - resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==} + /@humanwhocodes/config-array/0.10.4: + resolution: {integrity: sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==} engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 1.2.1 @@ -1359,6 +1339,15 @@ packages: - supports-color dev: true + /@humanwhocodes/gitignore-to-minimatch/1.0.2: + resolution: {integrity: sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==} + dev: true + + /@humanwhocodes/module-importer/1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + /@humanwhocodes/object-schema/1.2.1: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true @@ -1591,14 +1580,14 @@ packages: '@xtuc/long': 4.2.2 dev: true - /@webpack-cli/configtest/1.2.0_77l47gmqkrqiei5z7sbwz5iaj4: + /@webpack-cli/configtest/1.2.0_5v66e2inugklgvlh4huuavolfq: resolution: {integrity: sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==} peerDependencies: webpack: 4.x.x || 5.x.x webpack-cli: 4.x.x dependencies: - webpack: 5.73.0_webpack-cli@4.10.0 - webpack-cli: 4.10.0_webpack@5.73.0 + webpack: 5.74.0_webpack-cli@4.10.0 + webpack-cli: 4.10.0_webpack@5.74.0 dev: true /@webpack-cli/info/1.5.0_webpack-cli@4.10.0: @@ -1607,7 +1596,7 @@ packages: webpack-cli: 4.x.x dependencies: envinfo: 7.8.1 - webpack-cli: 4.10.0_webpack@5.73.0 + webpack-cli: 4.10.0_webpack@5.74.0 dev: true /@webpack-cli/serve/1.7.0_webpack-cli@4.10.0: @@ -1619,7 +1608,7 @@ packages: webpack-dev-server: optional: true dependencies: - webpack-cli: 4.10.0_webpack@5.73.0 + webpack-cli: 4.10.0_webpack@5.74.0 dev: true /@xtuc/ieee754/1.2.0: @@ -1638,18 +1627,12 @@ packages: acorn: 8.7.1 dev: true - /acorn-jsx/5.3.2_acorn@7.4.1: + /acorn-jsx/5.3.2_acorn@8.8.0: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - acorn: 7.4.1 - dev: true - - /acorn/7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} - engines: {node: '>=0.4.0'} - hasBin: true + acorn: 8.8.0 dev: true /acorn/8.7.1: @@ -1658,6 +1641,12 @@ packages: hasBin: true dev: true + /acorn/8.8.0: + resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + /ajv-keywords/3.5.2_ajv@6.12.6: resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} peerDependencies: @@ -1689,11 +1678,6 @@ packages: engines: {node: '>=6'} dev: true - /ansi-colors/4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - dev: true - /ansi-regex/5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1721,12 +1705,6 @@ packages: picomatch: 2.3.1 dev: true - /argparse/1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - dependencies: - sprintf-js: 1.0.3 - dev: true - /argparse/2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true @@ -1779,8 +1757,8 @@ packages: engines: {node: '>=0.10.0'} dev: true - /asar/3.1.0: - resolution: {integrity: sha512-vyxPxP5arcAqN4F/ebHd/HhwnAiZtwhglvdmc7BR2f0ywbVNTOpSeyhLDbGXtE/y58hv1oC75TaNIXutnsOZsQ==} + /asar/3.2.0: + resolution: {integrity: sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg==} engines: {node: '>=10.12.0'} hasBin: true dependencies: @@ -1912,7 +1890,7 @@ packages: hasBin: true dependencies: caniuse-lite: 1.0.30001359 - electron-to-chromium: 1.4.170 + electron-to-chromium: 1.4.185 node-releases: 2.0.5 update-browserslist-db: 1.0.4_browserslist@4.21.0 dev: true @@ -2055,7 +2033,7 @@ packages: dev: true /color-name/1.1.3: - resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} dev: true /color-name/1.1.4: @@ -2089,7 +2067,7 @@ packages: dev: true /concat-map/0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true /convert-source-map/1.8.0: @@ -2254,10 +2232,6 @@ packages: esutils: 2.0.3 dev: true - /electron-to-chromium/1.4.170: - resolution: {integrity: sha512-rZ8PZLhK4ORPjFqLp9aqC4/S1j4qWFsPPz13xmWdrbBkU/LlxMcok+f+6f8YnQ57MiZwKtOaW15biZZsY5Igvw==} - dev: true - /electron-to-chromium/1.4.185: resolution: {integrity: sha512-9kV/isoOGpKkBt04yYNaSWIBn3187Q5VZRtoReq8oz5NY/A4XmU6cAoqgQlDp7kKJCZMRjWZ8nsQyxfpFHvfyw==} dev: true @@ -2271,19 +2245,20 @@ packages: engines: {node: '>= 4'} dev: true - /enhanced-resolve/5.9.3: - resolution: {integrity: sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==} + /enhanced-resolve/5.10.0: + resolution: {integrity: sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==} engines: {node: '>=10.13.0'} dependencies: graceful-fs: 4.2.10 tapable: 2.2.1 dev: true - /enquirer/2.3.6: - resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} - engines: {node: '>=8.6'} + /enhanced-resolve/5.9.3: + resolution: {integrity: sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==} + engines: {node: '>=10.13.0'} dependencies: - ansi-colors: 4.1.3 + graceful-fs: 4.2.10 + tapable: 2.2.1 dev: true /envinfo/7.8.1: @@ -2361,8 +2336,8 @@ packages: engines: {node: '>=10'} dev: true - /eslint-plugin-react/7.30.1_eslint@7.32.0: - resolution: {integrity: sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg==} + /eslint-plugin-react/7.31.6_eslint@8.23.0: + resolution: {integrity: sha512-CXu4eu28sb8Sd2+cyUYsJVyDvpTlaXPG+bOzzpS9IzZKtye96AYX3ZmHQ6ayn/OAIQ/ufDJP8ElPWd63Pepn9w==} engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 @@ -2370,7 +2345,7 @@ packages: array-includes: 3.1.5 array.prototype.flatmap: 1.3.0 doctrine: 2.1.0 - eslint: 7.32.0 + eslint: 8.23.0 estraverse: 5.3.0 jsx-ast-utils: 3.3.1 minimatch: 3.1.2 @@ -2392,16 +2367,22 @@ packages: estraverse: 4.3.0 dev: true - /eslint-utils/2.1.0: - resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} - engines: {node: '>=6'} + /eslint-scope/7.1.1: + resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - eslint-visitor-keys: 1.3.0 + esrecurse: 4.3.0 + estraverse: 5.3.0 dev: true - /eslint-visitor-keys/1.3.0: - resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} - engines: {node: '>=4'} + /eslint-utils/3.0.0_eslint@8.23.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.23.0 + eslint-visitor-keys: 2.1.0 dev: true /eslint-visitor-keys/2.1.0: @@ -2409,68 +2390,66 @@ packages: engines: {node: '>=10'} dev: true - /eslint/7.32.0: - resolution: {integrity: sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==} - engines: {node: ^10.12.0 || >=12.0.0} + /eslint-visitor-keys/3.3.0: + resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint/8.23.0: + resolution: {integrity: sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@babel/code-frame': 7.12.11 - '@eslint/eslintrc': 0.4.3 - '@humanwhocodes/config-array': 0.5.0 + '@eslint/eslintrc': 1.3.1 + '@humanwhocodes/config-array': 0.10.4 + '@humanwhocodes/gitignore-to-minimatch': 1.0.2 + '@humanwhocodes/module-importer': 1.0.1 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 debug: 4.3.4 doctrine: 3.0.0 - enquirer: 2.3.6 escape-string-regexp: 4.0.0 - eslint-scope: 5.1.1 - eslint-utils: 2.1.0 - eslint-visitor-keys: 2.1.0 - espree: 7.3.1 + eslint-scope: 7.1.1 + eslint-utils: 3.0.0_eslint@8.23.0 + eslint-visitor-keys: 3.3.0 + espree: 9.4.0 esquery: 1.4.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 6.0.1 + find-up: 5.0.0 functional-red-black-tree: 1.0.1 - glob-parent: 5.1.2 + glob-parent: 6.0.2 globals: 13.15.0 - ignore: 4.0.6 + globby: 11.1.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.0 import-fresh: 3.3.0 imurmurhash: 0.1.4 is-glob: 4.0.3 - js-yaml: 3.14.1 + js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.1 - progress: 2.0.3 regexpp: 3.2.0 - semver: 7.3.7 strip-ansi: 6.0.1 strip-json-comments: 3.1.1 - table: 6.8.0 text-table: 0.2.0 - v8-compile-cache: 2.3.0 transitivePeerDependencies: - supports-color dev: true - /espree/7.3.1: - resolution: {integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==} - engines: {node: ^10.12.0 || >=12.0.0} + /espree/9.4.0: + resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 7.4.1 - acorn-jsx: 5.3.2_acorn@7.4.1 - eslint-visitor-keys: 1.3.0 - dev: true - - /esprima/4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true + acorn: 8.8.0 + acorn-jsx: 5.3.2_acorn@8.8.0 + eslint-visitor-keys: 3.3.0 dev: true /esquery/1.4.0: @@ -2713,6 +2692,13 @@ packages: is-glob: 4.0.3 dev: true + /glob-parent/6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + /glob-to-regexp/0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} dev: true @@ -2810,6 +2796,10 @@ packages: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} dev: true + /grapheme-splitter/1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + dev: true + /hard-rejection/2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} @@ -2884,11 +2874,6 @@ packages: postcss: 8.4.14 dev: true - /ignore/4.0.6: - resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} - engines: {node: '>= 4'} - dev: true - /ignore/5.2.0: resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} engines: {node: '>= 4'} @@ -3120,14 +3105,6 @@ packages: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true - /js-yaml/3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - dev: true - /js-yaml/4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -3903,11 +3880,6 @@ packages: engines: {node: '>= 0.8'} dev: true - /progress/2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} - dev: true - /prop-types/15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} dependencies: @@ -4256,10 +4228,6 @@ packages: resolution: {integrity: sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==} dev: true - /sprintf-js/1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - dev: true - /string-width/4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -4462,7 +4430,31 @@ packages: schema-utils: 3.1.1 serialize-javascript: 6.0.0 terser: 5.14.1 - webpack: 5.73.0_webpack-cli@4.10.0 + webpack: 5.73.0 + dev: true + + /terser-webpack-plugin/5.3.3_webpack@5.74.0: + resolution: {integrity: sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + dependencies: + '@jridgewell/trace-mapping': 0.3.14 + jest-worker: 27.5.1 + schema-utils: 3.1.1 + serialize-javascript: 6.0.0 + terser: 5.14.1 + webpack: 5.74.0_webpack-cli@4.10.0 dev: true /terser/5.14.1: @@ -4471,7 +4463,7 @@ packages: hasBin: true dependencies: '@jridgewell/source-map': 0.3.2 - acorn: 8.7.1 + acorn: 8.8.0 commander: 2.20.3 source-map-support: 0.5.21 dev: true @@ -4616,7 +4608,7 @@ packages: graceful-fs: 4.2.10 dev: true - /webpack-cli/4.10.0_webpack@5.73.0: + /webpack-cli/4.10.0_webpack@5.74.0: resolution: {integrity: sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==} engines: {node: '>=10.13.0'} hasBin: true @@ -4637,7 +4629,7 @@ packages: optional: true dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 1.2.0_77l47gmqkrqiei5z7sbwz5iaj4 + '@webpack-cli/configtest': 1.2.0_5v66e2inugklgvlh4huuavolfq '@webpack-cli/info': 1.5.0_webpack-cli@4.10.0 '@webpack-cli/serve': 1.7.0_webpack-cli@4.10.0 colorette: 2.0.19 @@ -4647,7 +4639,7 @@ packages: import-local: 3.1.0 interpret: 2.2.0 rechoir: 0.7.1 - webpack: 5.73.0_webpack-cli@4.10.0 + webpack: 5.74.0_webpack-cli@4.10.0 webpack-merge: 5.8.0 dev: true @@ -4704,8 +4696,8 @@ packages: - uglify-js dev: true - /webpack/5.73.0_webpack-cli@4.10.0: - resolution: {integrity: sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA==} + /webpack/5.74.0_webpack-cli@4.10.0: + resolution: {integrity: sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -4721,9 +4713,9 @@ packages: '@webassemblyjs/wasm-parser': 1.11.1 acorn: 8.7.1 acorn-import-assertions: 1.8.0_acorn@8.7.1 - browserslist: 4.21.0 + browserslist: 4.21.1 chrome-trace-event: 1.0.3 - enhanced-resolve: 5.9.3 + enhanced-resolve: 5.10.0 es-module-lexer: 0.9.3 eslint-scope: 5.1.1 events: 3.3.0 @@ -4735,9 +4727,9 @@ packages: neo-async: 2.6.2 schema-utils: 3.1.1 tapable: 2.2.1 - terser-webpack-plugin: 5.3.3_webpack@5.73.0 + terser-webpack-plugin: 5.3.3_webpack@5.74.0 watchpack: 2.4.0 - webpack-cli: 4.10.0_webpack@5.73.0 + webpack-cli: 4.10.0_webpack@5.74.0 webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' diff --git a/preload/src/api/crypto.js b/preload/src/api/crypto.js new file mode 100644 index 00000000..d1186cc6 --- /dev/null +++ b/preload/src/api/crypto.js @@ -0,0 +1,28 @@ +const crypto = (() => { + let cache = null; + + return () => { + if (cache) return cache; + + return cache = __non_webpack_require__("crypto"); + }; +})(); + +export function createHash(type) { + const hash = crypto().createHash(type); + + const ctx = { + update(data) { + hash.update(data); + + return ctx; + }, + digest(encoding) {return hash.digest(encoding);} + }; + + return ctx; +} + +export function randomBytes(length) { + return crypto().randomBytes(length); +} \ No newline at end of file diff --git a/preload/src/api/electron.js b/preload/src/api/electron.js new file mode 100644 index 00000000..246535f9 --- /dev/null +++ b/preload/src/api/electron.js @@ -0,0 +1,13 @@ +import {ipcRenderer as IPC, shell} from "electron"; + +export const ipcRenderer = { + send: IPC.send.bind(IPC), + sendToHost: IPC.sendToHost.bind(IPC), + sendTo: IPC.sendTo.bind(IPC), + sendSync: IPC.sendSync.bind(IPC), + invoke: IPC.invoke.bind(IPC), + on: IPC.on.bind(IPC), + off: IPC.off.bind(IPC) +}; + +export {shell}; \ No newline at end of file diff --git a/preload/src/api/filesystem.js b/preload/src/api/filesystem.js new file mode 100644 index 00000000..3ea4ccac --- /dev/null +++ b/preload/src/api/filesystem.js @@ -0,0 +1,73 @@ +import * as fs from "fs"; +import cloneObject from "common/clone"; +import Logger from "common/logger"; + +export function readFile(path, options = "utf8") { + return fs.readFileSync(path, options); +} + +export function writeFile(path, content, options) { + if (content instanceof Uint8Array) { + content = Buffer.from(content); + } + + const doWriteFile = options?.originalFs ? __non_webpack_require__("original-fs").writeFileSync : fs.writeFileSync; + + return doWriteFile(path, content, options); +} + +export function readDirectory(path, options) { + return fs.readdirSync(path, options); +} + +export function createDirectory(path, options) { + return fs.mkdirSync(path, options); +} + +export function deleteDirectory(path, options) { + fs.rmdirSync(path, options); +} + +export function exists(path) { + return fs.existsSync(path); +} + +export function getRealPath(path, options) { + return fs.realpathSync(path, options); +} + +export function rename(oldPath, newPath) { + return fs.renameSync(oldPath, newPath); +} + +export function createWriteStream(path, options) { + return cloneObject(fs.createWriteStream(path, options)); +} + +export function watch(path, options, callback) { + const watcher = fs.watch(path, options, (event, filename) => { + try { + callback(event, filename); + } + catch (error) { + Logger.stacktrace("filesystem", "Failed to watch path", error); + } + }); + + return { + close: () => { + watcher.close(); + } + }; +} + +export function getStats(path, options) { + const stats = fs.statSync(path, options); + + return { + ...stats, + isFile: stats.isFile.bind(stats), + isDirectory: stats.isDirectory.bind(stats), + isSymbolicLink: stats.isSymbolicLink.bind(stats) + }; +} \ No newline at end of file diff --git a/preload/src/api/https.js b/preload/src/api/https.js new file mode 100644 index 00000000..de306a26 --- /dev/null +++ b/preload/src/api/https.js @@ -0,0 +1,91 @@ +import Logger from "common/logger"; + +let request; + +const req = function (url, options, callback) { + if (!request) request = __non_webpack_require__("request"); + + return request(url, options, (error, res, body) => { + try { + Reflect.apply(callback, null, [error, res, body]); + } + catch (err) { + Logger.stacktrace("https", "Failed request", err); + } + }); +}; + +export const get = function (url, options, callback) { + if (!request) request = __non_webpack_require__("request"); + + return request.get(url, options, (error, res, body) => { + try { + Reflect.apply(callback, null, [error, res, body]); + } + catch (err) { + Logger.stacktrace("https", "Failed get request", err); + } + }); +}; + +export const put = function (url, options, callback) { + if (!request) request = __non_webpack_require__("request"); + + return request.put(url, options, (error, res, body) => { + try { + Reflect.apply(callback, null, [error, res, body]); + } + catch (err) { + Logger.stacktrace("https", "Failed put request", err); + } + }); +}; + +export const post = function (url, options, callback) { + if (!request) request = __non_webpack_require__("request"); + + return request.post(url, options, (error, res, body) => { + try { + Reflect.apply(callback, null, [error, res, body]); + } + catch (err) { + Logger.stacktrace("https", "Failed post request", err); + } + }); +}; + +const del = function (url, options, callback) { + if (!request) request = __non_webpack_require__("request"); + + return request.delete(url, options, (error, res, body) => { + try { + Reflect.apply(callback, null, [error, res, body]); + } + catch (err) { + Logger.stacktrace("https", "Failed delete request", err); + } + }); +}; + +const head = function (url, options, callback) { + if (!request) request = __non_webpack_require__("request"); + + return request.head(url, options, (error, res, body) => { + try { + Reflect.apply(callback, null, [error, res, body]); + } + catch (err) { + Logger.stacktrace("https", "Failed head request", err); + } + }); +}; + +export default req; + +Object.assign(req, { + get, + put, + post, + head, + delete: del // eslint-disable-line quote-props +}); \ No newline at end of file diff --git a/preload/src/api/index.js b/preload/src/api/index.js new file mode 100644 index 00000000..e96ae7a6 --- /dev/null +++ b/preload/src/api/index.js @@ -0,0 +1,14 @@ +import path from "path"; +import Module from "module"; + +Module.globalPaths.push(path.resolve(process.env.DISCORD_APP_PATH, "..", "app.asar", "node_modules")); + +export * as filesystem from "./filesystem"; +export * as https from "./https"; +export * as electron from "./electron"; +export * as crypto from "./crypto"; + +// We can expose that without any issues. +export * as path from "path"; +export * as net from "net"; // TODO: evaluate need and create wrapper +export * as os from "os"; \ No newline at end of file diff --git a/preload/src/index.js b/preload/src/index.js index bd144377..16647497 100644 --- a/preload/src/index.js +++ b/preload/src/index.js @@ -1,59 +1,15 @@ -const Module = require("module"); -const path = require("path"); -const electron = require("electron"); -const NodeEvents = require("events"); +import {contextBridge} from "electron"; +import newProcess from "./process"; +import * as BdApi from "./api"; +import init from "./init"; -const cloneObject = function (target, newObject = {}, keys) { - if (!Array.isArray(keys)) keys = Object.keys(Object.getOwnPropertyDescriptors(target)); - return keys.reduce((clone, key) => { - if (typeof(target[key]) === "object" && !Array.isArray(target[key]) && target[key] !== null && !(target[key] instanceof NodeEvents)) clone[key] = cloneObject(target[key], {}); - else clone[key] = target[key]; - - return clone; - }, newObject); -}; - -/* global window:false */ - -// const context = electron.webFrame.top.context; -Object.defineProperty(window, "webpackJsonp", { - get: () => electron.webFrame.top.context.webpackJsonp +let hasInitialized = false; +contextBridge.exposeInMainWorld("BetterDiscord", BdApi); +contextBridge.exposeInMainWorld("process", newProcess); +contextBridge.exposeInMainWorld("BetterDiscordPreload", () => { + if (hasInitialized) return null; + hasInitialized = true; + return BdApi; }); -electron.webFrame.top.context.global = electron.webFrame.top.context; -electron.webFrame.top.context.require = require; -electron.webFrame.top.context.Buffer = Buffer; - - -electron.webFrame.top.context.process = new class PatchedProcess extends NodeEvents { - get __ORIGINAL_PROCESS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__() {return process;} - - constructor() { - super(); - - Object.assign(this, - cloneObject(process, {}, Object.keys(NodeEvents.prototype)), - cloneObject(process, {}) - ); - } -}; - -// Load Discord's original preload -const preload = process.env.DISCORD_PRELOAD; -if (preload) { - - // Restore original preload for future windows - electron.ipcRenderer.send("bd-register-preload", preload); - // Run original preload - try { - const originalKill = process.kill; - process.kill = function() {}; - require(preload); - process.kill = originalKill; - } - catch (e) { - // TODO bail out - } -} - -Module.globalPaths.push(path.resolve(process.env.DISCORD_APP_PATH, "..", "app.asar", "node_modules")); \ No newline at end of file +init(); \ No newline at end of file diff --git a/preload/src/init.js b/preload/src/init.js new file mode 100644 index 00000000..790f062b --- /dev/null +++ b/preload/src/init.js @@ -0,0 +1,22 @@ +import {ipcRenderer as IPC} from "electron"; +import * as IPCEvents from "common/constants/ipcevents"; + +export default function() { + // Load Discord's original preload + const preload = process.env.DISCORD_PRELOAD; + if (preload) { + + // Restore original preload for future windows + IPC.send(IPCEvents.REGISTER_PRELOAD, preload); + // Run original preload + try { + const originalKill = process.kill; + process.kill = function() {}; + __non_webpack_require__(preload); + process.kill = originalKill; + } + catch (e) { + // TODO bail out + } + } +} \ No newline at end of file diff --git a/preload/src/process.js b/preload/src/process.js new file mode 100644 index 00000000..40e24cb6 --- /dev/null +++ b/preload/src/process.js @@ -0,0 +1,3 @@ +import cloneObject from "common/clone"; + +export default cloneObject(process, {}); \ No newline at end of file diff --git a/preload/webpack.config.js b/preload/webpack.config.js index 3e3ec06f..34a7d979 100644 --- a/preload/webpack.config.js +++ b/preload/webpack.config.js @@ -19,7 +19,9 @@ module.exports = (env, argv) => ({ rimraf: `require("rimraf")`, yauzl: `require("yauzl")`, mkdirp: `require("mkdirp")`, - module: `require("module")` + module: `require("module")`, + os: `require("os")`, + net: `require("net")` }, resolve: { extensions: [".js"], diff --git a/renderer/src/index.js b/renderer/src/index.js index 9921da4a..a6957802 100644 --- a/renderer/src/index.js +++ b/renderer/src/index.js @@ -1,3 +1,4 @@ +import require from "./polyfill"; // eslint-disable-line no-unused-vars import secure from "./secure"; import patchModuleLoad from "./moduleloader"; import LoadingIcon from "./loadingicon"; @@ -8,6 +9,7 @@ import BdApi from "./modules/pluginapi"; secure(); patchModuleLoad(); window.BdApi = BdApi; +window.global = window; // Add loading icon at the bottom right LoadingIcon.show(); diff --git a/renderer/src/polyfill/buffer.js b/renderer/src/polyfill/buffer.js new file mode 100644 index 00000000..ae19f600 --- /dev/null +++ b/renderer/src/polyfill/buffer.js @@ -0,0 +1,17 @@ +import WebpackModules from "../modules/webpackmodules"; + +Object.defineProperty(window, "Buffer", { + get() {return Buffer.getBuffer().Buffer;}, + configurable: true, + enumerable: false +}); + +export default class Buffer { + static getBuffer() { + if (this.cached) return this.cached; + + this.cached = WebpackModules.getByProps("Buffer", "SlowBuffer"); + + return this.cached; + } +} \ No newline at end of file diff --git a/renderer/src/polyfill/crypto.js b/renderer/src/polyfill/crypto.js new file mode 100644 index 00000000..fb053392 --- /dev/null +++ b/renderer/src/polyfill/crypto.js @@ -0,0 +1,9 @@ +import Remote from "./remote"; + +export default { + ...Remote.crypto, + // Wrap it in Buffer + randomBytes(length) { + return Buffer.from(Remote.crypto.randomBytes(length)); + } +}; \ No newline at end of file diff --git a/renderer/src/polyfill/fs.js b/renderer/src/polyfill/fs.js new file mode 100644 index 00000000..1cc86118 --- /dev/null +++ b/renderer/src/polyfill/fs.js @@ -0,0 +1,167 @@ + +import Remote from "./remote"; + +export const readFileSync = function (path, options = "utf8") { + return Remote.filesystem.readFile(path, options); +}; + +export const readFile = function (path, options = "utf8", callback) { + try { + const contents = Remote.filesystem.readFile(path, options); + callback(null, contents); + } + catch (error) { + callback(error, null); + } +}; + +export const writeFile = function (path, data, options = "utf8", callback) { + if (typeof(options) === "function") { + callback = options; + if (!["object", "string"].includes(typeof(options))) options = undefined; + } + + try { + Remote.filesystem.writeFile(path, data, options); + callback(null); + } + catch (error) { + callback(error); + } +}; + +export const writeFileSync = function (path, data, options) { + Remote.filesystem.writeFile(path, data, options); +}; + +export const readdir = function (path, options, callback) { + try { + const result = Remote.filesystem.readDirectory(path, options); + callback(null, result); + } + catch (error) { + callback(error, null); + } +}; + +export const readdirSync = function (path, options) { + return Remote.filesystem.readDirectory(path, options); +}; + +export const mkdir = function (path, options, callback) { + try { + const result = Remote.filesystem.createDirectory(path, options); + callback(null, result); + } + catch (error) { + callback(error, null); + } +}; + +export const mkdirSync = function (path, options) { + Remote.filesystem.createDirectory(path, options); +}; + +export const rmdir = function (path, options, callback) { + try { + const result = Remote.filesystem.deleteDirectory(path, options); + callback(null, result); + } + catch (error) { + callback(error, null); + } +}; + +export const rmdirSync = function (path, options) { + Remote.filesystem.deleteDirectory(path, options); +}; + +export const exists = function (path, options, callback) { + try { + const result = Remote.filesystem.exists(path, options); + callback(null, result); + } + catch (error) { + callback(error, null); + } +}; + +export const existsSync = function (path, options) { + return Remote.filesystem.exists(path, options); +}; + +export const stat = function (path, options, callback) { + try { + const result = Remote.filesystem.getStats(path, options); + callback(null, result); + } + catch (error) { + callback(error); + } +}; + +export const statSync = function (path, options) { + return Remote.filesystem.getStats(path, options); +}; + +export const lstat = stat; +export const lstatSync = statSync; + +export const rename = function (oldPath, newPath, options, callback) { + try { + const result = Remote.filesystem.rename(oldPath, newPath, options); + callback(null, result); + } + catch (error) { + callback(error, null); + } +}; + +export const renameSync = function (oldPath, newPath, options) { + return Remote.filesystem.renameSync(oldPath, newPath, options); +}; + +export const realpath = function (path, options, callback) { + try { + const result = Remote.filesystem.getStats(path, options); + callback(null, result); + } + catch (error) { + callback(error, null); + } +}; + +export const realpathSync = function (path, options) { + return Remote.filesystem.getRealPath(path, options); +}; + +export const watch = (path, options, callback) => { + return Remote.filesystem.watch(path, options, callback); +}; + +export const createWriteStream = (path, options) => { + return Remote.filesystem.createWriteStream(path, options); +}; + +export default { + readFile, + exists, + existsSync, + lstat, + lstatSync, + mkdir, + mkdirSync, + readFileSync, + readdir, + readdirSync, + realpath, + realpathSync, + rename, + renameSync, + rmdir, + rmdirSync, + watch, + writeFile, + writeFileSync, + createWriteStream +}; \ No newline at end of file diff --git a/renderer/src/polyfill/https.js b/renderer/src/polyfill/https.js new file mode 100644 index 00000000..9ef7e353 --- /dev/null +++ b/renderer/src/polyfill/https.js @@ -0,0 +1,23 @@ +import EventEmitter from "common/events"; +import Remote from "./remote"; + +export function get(url, options = {}, callback) { + if (typeof(options) === "function") { + callback = options; + options = null; + } + + const emitter = new EventEmitter(); + + callback(emitter); + + Remote.https.get(url, options, (error, res, body) => { + if (error) return emitter.emit("error", error); + emitter.emit("data", body); + emitter.emit("end", res); + }); + + return emitter; +} + +export default {get}; \ No newline at end of file diff --git a/renderer/src/polyfill/index.js b/renderer/src/polyfill/index.js new file mode 100644 index 00000000..72434088 --- /dev/null +++ b/renderer/src/polyfill/index.js @@ -0,0 +1,45 @@ +import Module from "./module"; +import * as vm from "./vm"; +import * as fs from "./fs"; +import request from "./request"; +import EventEmitter from "common/events"; +import * as https from "./https"; +import Buffer from "./buffer"; +import crypto from "./crypto"; +import Remote from "./remote"; + +const originalFs = Object.assign({}, fs); +originalFs.writeFileSync = (path, data, options) => fs.writeFileSync(path, data, Object.assign({}, options, {originalFs: true})); +originalFs.writeFile = (path, data, options) => fs.writeFile(path, data, Object.assign({}, options, {originalFs: true})); + +export const createRequire = function (path) { + return mod => { + switch (mod) { + case "request": return request; + case "https": return https; + case "original-fs": return originalFs; + case "fs": return fs; + case "path": return Remote.path; + case "events": return EventEmitter; + case "electron": return Remote.electron; + case "process": return window.process; + case "vm": return vm; + case "module": return Module; + case "buffer": return Buffer.getBuffer(); + case "crypto": return crypto; + + default: + return Module._load(mod, path, createRequire); + } + }; +}; + +const require = window.require = createRequire("."); +require.cache = {}; +require.resolve = (path) => { + for (const key of Object.keys(require.cache)) { + if (key.startsWith(path)) return require.cache[key]; + } +}; + +export default require; \ No newline at end of file diff --git a/renderer/src/polyfill/module.js b/renderer/src/polyfill/module.js new file mode 100644 index 00000000..c03d67d9 --- /dev/null +++ b/renderer/src/polyfill/module.js @@ -0,0 +1,103 @@ +import Logger from "common/logger"; +import {compileFunction} from "./vm"; +import Remote from "./remote"; +import fs from "./fs"; + +const path = Remote.path; + +export const RequireExtensions = { + ".js": (module, filename) => { + const fileContent = Remote.filesystem.readFile(filename, "utf8"); + module.fileContent = fileContent; + module._compile(fileContent); + return module.exports; + }, + ".json": (module, filename) => { + const fileContent = Remote.filesystem.readFile(filename, "utf8"); + module.fileContent = fileContent; + module.exports = JSON.parse(fileContent); + + return module.exports; + } +}; + +export default class Module { + static resolveMainFile(mod, basePath) { + const parent = path.extname(basePath) ? path.dirname(basePath) : basePath; + const files = Remote.filesystem.readDirectory(parent); + if (!Array.isArray(files)) return null; + + for (const file of files) { + const ext = path.extname(file); + + if (file === "package.json") { + const pkg = require(path.resolve(parent, file)); + if (!Reflect.has(pkg, "main")) continue; + + return path.resolve(parent, pkg.main); + } + + if (path.slice(0, -ext.length) == "index" && RequireExtensions[ext]) return mod; + } + } + + static getExtension(mod) { + return path.extname(mod) || Reflect.ownKeys(RequireExtensions).find(e => Remote.filesystem.exists(mod + e)); + } + + static getFilePath(basePath, mod) { + if (!path.isAbsolute(mod)) mod = path.resolve(basePath, mod); + const defaultExtension = path.extname(mod); + if (!defaultExtension) { + const ext = Reflect.ownKeys(RequireExtensions).find(e => Remote.filesystem.exists(mod + e)); + if (ext) { + mod = mod + ext; + } + } + + return fs.realpathSync(mod); + } + + static _load(mod, basePath, createRequire) { + const originalReq = mod; + if (!path.isAbsolute(mod)) mod = path.resolve(basePath, mod); + const filePath = this.getFilePath(basePath, mod); + if (!Remote.filesystem.exists(filePath)) throw new Error(`Cannot find module ${mod}`); + if (window.require.cache[filePath]) return window.require.cache[filePath].exports; + const stats = Remote.filesystem.getStats(filePath); + if (stats.isDirectory()) mod = this.resolveMainFile(mod, basePath); + const ext = this.getExtension(filePath); + const loader = RequireExtensions[ext]; + + if (!loader) throw new Error(`Cannot find module ${originalReq}`); + const module = window.require.cache[mod] = new Module(filePath, internalModule, createRequire(mod)); + loader(module, filePath); + return module.exports; + } + + static get Module() {return Module;} + + static get createRequire() {return Logger.warn("ContextModule", "Module.createRequire not implemented yet.");} + + static get _extensions() {return RequireExtensions;} + + constructor(id, parent, require) { + this.id = id; + this.path = Remote.path.dirname(id); + this.exports = {}; + this.parent = parent; + this.filename = id; + this.loaded = false; + this.children = []; + this.require = require; + + if (parent) parent.children.push(this); + } + + _compile(code) { + const wrapped = compileFunction(code, ["require", "module", "exports", "__filename", "__dirname", "global"], this.filename); + wrapped(this.require, this, this.exports, this.filename, this.path, window); + } +} + +const internalModule = new Module(".", null); \ No newline at end of file diff --git a/renderer/src/polyfill/remote.js b/renderer/src/polyfill/remote.js new file mode 100644 index 00000000..43d3c77e --- /dev/null +++ b/renderer/src/polyfill/remote.js @@ -0,0 +1,3 @@ +/** @type {import("../../../preload/src/api/index")} */ +const RemoteAPI = window.BetterDiscordPreload(); // eslint-disable-line new-cap +export default RemoteAPI; \ No newline at end of file diff --git a/renderer/src/polyfill/request.js b/renderer/src/polyfill/request.js new file mode 100644 index 00000000..48091a56 --- /dev/null +++ b/renderer/src/polyfill/request.js @@ -0,0 +1,55 @@ +import Remote from "./remote"; + +const methods = ["get", "put", "post", "delete", "head"]; +const aliases = {del: "delete"}; + +function parseArguments() { + let url, options, callback; + + for (const arg of arguments) { + switch (typeof arg) { + case (arg !== null && "object"): + options = arg; + if ("url" in options) { + url = options.url; + } + break; + + case (!url && "string"): + url = arg; + break; + + case (!callback && "function"): + callback = arg; + break; + } + } + + return {url, options, callback}; +} + +function validOptions(url, callback) { + return typeof url === "string" && typeof callback === "function"; +} + +export default function request() { + const {url, options = {}, callback} = parseArguments.apply(this, arguments); + + if (!validOptions(url, callback)) return null; + + if ("method" in options && methods.indexOf(options.method.toLowerCase()) >= 0) { + return Remote.https[options.method](url, options, callback); + } + + return Remote.https.default(url, options, callback); +} + +Object.assign(request, Object.fromEntries( + methods.concat(Object.keys(aliases)).map(method => [method, function () { + const {url, options = {}, callback} = parseArguments.apply(this, arguments); + + if (!validOptions(url, callback)) return null; + + return Remote.https[aliases[method] || method](url, options, callback); + }]) +)); \ No newline at end of file diff --git a/renderer/src/polyfill/vm.js b/renderer/src/polyfill/vm.js new file mode 100644 index 00000000..0fdbbe6d --- /dev/null +++ b/renderer/src/polyfill/vm.js @@ -0,0 +1,3 @@ +export const compileFunction = function (code, params = [], filename = "") { + return window.eval(`(${params.join(", ")}) => {${code}//# sourceURL=${filename.replace(/\\/g, "\\")}\n}`); // eslint-disable-line no-eval +}; \ No newline at end of file