From 6426f389d53223fb2ecf2ee4d7e83e9af260ae49 Mon Sep 17 00:00:00 2001 From: adryd Date: Thu, 30 Nov 2023 19:51:04 -0500 Subject: [PATCH] Validation time --- .eslintrc.json | 1 + src/Patcher.js | 125 ++++++++++++++++++++++++++++++++++++++---- src/index.js | 2 - src/validate.js | 17 ++++++ src/wpTools.js | 4 +- userscriptTemplate.js | 2 +- 6 files changed, 134 insertions(+), 17 deletions(-) create mode 100644 src/validate.js diff --git a/.eslintrc.json b/.eslintrc.json index 4d8c722..1dede59 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -13,6 +13,7 @@ }, "rules": { "indent": 0, + "eqeqeq":"error", "quotes": [1, "double", { "avoidEscape": true, "allowTemplateLiterals": true }], "no-unused-vars": [1, { "args": "none", "varsIgnorePattern": "^_" }] } diff --git a/src/Patcher.js b/src/Patcher.js index 188ef1f..6354e73 100644 --- a/src/Patcher.js +++ b/src/Patcher.js @@ -1,19 +1,23 @@ import matchModule from "./matchModule"; +import { validateProperty } from "./validate"; import { getWpToolsFunc } from "./wpTools"; export default class Patcher { constructor(config) { + this._validateConfig(config); this.name = config.name; this.chunkObject = config.chunkObject; - this.webpackVersion = config.webpackVersion; + this.webpackVersion = config.webpackVersion.toString(); this.inspectAll = config.inspectAll; this.modules = new Set(config.modules ?? []); - this.patches = new Set(config.patches ?? []); + for (const module of this.modules) { + this._validateModuleConfig(module); + } - // Validation - if (typeof this.webpackVersion == "number") { - this.webpackVersion = this.webpackVersion.toString(); + this.patches = new Set(config.patches ?? []); + for (const patch of this.patches) { + this._validatePatchConfig(patch); } // Populate patches to apply and modules to inject @@ -27,14 +31,14 @@ export default class Patcher { this.modulesToInject = new Set(); if (this.modules) { for (const module of this.modules) { - if (module.needs != undefined && module.needs instanceof Array) { + if (module.needs !== undefined && module.needs instanceof Array) { module.needs = new Set(module.needs); } this.modulesToInject.add(module); } } - if (config.injectWpTools) { + if (config.injectWpTools !== false) { this.modulesToInject.add({ name: "wpTools", // This is sorta a scope hack. @@ -46,7 +50,7 @@ export default class Patcher { } run() { - if (this.webpackVersion == "4" || this.webpackVersion == "5") { + if (this.webpackVersion === "4" || this.webpackVersion === "5") { this._interceptWebpackModern(); } else { this._interceptWebpackLegacy; @@ -80,7 +84,7 @@ export default class Patcher { }; value.push.__wpt_injected = true; - if (realPush == Array.prototype.push) { + if (realPush === Array.prototype.push) { console.log("[wpTools] Injected " + patcher._chunkObject + " (before webpack runtime)"); } else { console.log("[wpTools] Injected " + patcher._chunkObject + " (at webpack runtime)"); @@ -143,9 +147,9 @@ export default class Patcher { for (const need of moduleToInject.needs) { for (const wpModule of Object.entries(chunk[1])) { // match { moduleId: "id" } as well as strings and regex - if ((need?.moduleId && wpModule[0] == need.moduleId) || matchModule(wpModule[1].__wpt_funcStr, need)) { + if ((need?.moduleId && wpModule[0] === need.moduleId) || matchModule(wpModule[1].__wpt_funcStr, need)) { moduleToInject.needs.delete(need); - if (moduleToInject.needs.size == 0) { + if (moduleToInject.needs.size === 0) { readyModules.add(moduleToInject); } break; @@ -205,7 +209,104 @@ export default class Patcher { break; } } - console.log(chunk); } } + _validateConfig(config) { + validateProperty("siteConfigs[?]", config, "name", true, (value) => { + return typeof value === "string"; + }); + + const name = config.name; + + validateProperty(`siteConfigs[${name}]`, config, "chunkObject", true, (value) => { + return typeof value === "string"; + }); + + validateProperty(`siteConfigs[${name}]`, config, "webpackVersion", true, (value) => { + return ["4", "5"].includes(value.toString()); + }); + + validateProperty(`siteConfigs[${name}]`, config, "inspectAll", false, (value) => { + return typeof value === "boolean"; + }); + + validateProperty(`siteConfigs[${name}]`, config, "modules", false, (value) => { + return value instanceof Array; + }); + + validateProperty(`siteConfigs[${name}]`, config, "patches", false, (value) => { + return value instanceof Array; + }); + + validateProperty(`siteConfigs[${name}]`, config, "injectWpTools", false, (value) => { + return typeof value === "boolean"; + }); + } + + _validatePatchConfig(config) { + validateProperty(`siteConfigs[${this.name}].patches[?]`, config, "name", true, (value) => { + return typeof value === "string"; + }); + + const name = config.name; + + validateProperty(`siteConfigs[${this.name}].patches[${name}]`, config, "find", true, (value) => { + return ( + // RegExp, String, or an Array of RegExps and Strings + typeof value === "string" || + value instanceof RegExp || + (value instanceof Array && + !value.some((value) => { + !(typeof value === "string" || value instanceof RegExp); + })) + ); + }); + + validateProperty(`siteConfigs[${this.name}].patches[${name}]`, config, "replace", true, (value) => { + return typeof value === "object"; + }); + + validateProperty(`siteConfigs[${this.name}].patches[${name}].replace`, config.replace, "match", true, (value) => { + return typeof value === "string" || value instanceof RegExp; + }); + + validateProperty(`siteConfigs[${this.name}].patches[${name}].replace`, config.replace, "replacement", true, (value) => { + return typeof value === "string" || value instanceof Function; + }); + } + + _validateModuleConfig(config) { + validateProperty(`siteConfigs[${this.name}].modules[?]`, config, "name", true, (value) => { + return typeof value === "string"; + }); + + const name = config.name; + + validateProperty(`siteConfigs[${this.name}].modules[${name}]`, config, "needs", false, (value) => { + // A set or array of strings, RegExps or `{moduleId: ""}`'s return ( + return ( + (value instanceof Array || value instanceof Set) && + ![...value].some((value) => { + !( + typeof value === "string" || + value instanceof RegExp || + (value instanceof Object && typeof value.moduleId === "string") + ); + }) + ); + }); + + validateProperty(`siteConfigs[${this.name}].modules[${name}]`, config, "run", true, (value) => { + return typeof value === "function"; + }); + + validateProperty(`siteConfigs[${this.name}].modules[${name}]`, config, "entry", false, (value) => { + return typeof value === "boolean"; + }); + + // Possible future thing + // validateProperty(`siteConfigs[${this.name}].modules[${name}]`, config, "rewrap", false, (value) => { + // return typeof value === "boolean"; + // }); + } } diff --git a/src/index.js b/src/index.js index d014365..0cd3166 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,6 @@ - import Patcher from "./Patcher"; import { injectEverywhere } from "./injectEverywhere"; - export const globalConfig = window.__webpackTools_config; delete window.__webpackTools_config; diff --git a/src/validate.js b/src/validate.js new file mode 100644 index 0000000..70cd33d --- /dev/null +++ b/src/validate.js @@ -0,0 +1,17 @@ +class ConfigValidationError extends Error {} + +export function validateProperty(name, object, key, required, validationCallback) { + if (!Object.prototype.hasOwnProperty.call(object, [key])) { + if (required) { + throw new ConfigValidationError(`Required property not found, missing ${key} in ${name}`); + } else { + return; + } + } else { + if (!validationCallback(object[key])) { + throw new ConfigValidationError( + `Failed to validate ${key} in ${name}. The following check failed: \n${validationCallback.toString()}`, + ); + } + } +} diff --git a/src/wpTools.js b/src/wpTools.js index 50f9e7e..7df0029 100644 --- a/src/wpTools.js +++ b/src/wpTools.js @@ -13,8 +13,8 @@ export function getWpToolsFunc(chunkObject) { .filter(([moduleId, exportCache]) => { return !keys.some((searchKey) => { return !( - exportCache != undefined && - exportCache != window && + exportCache !== undefined && + exportCache !== window && (exports?.[searchKey] || exports?.default?.[searchKey]) ); }); diff --git a/userscriptTemplate.js b/userscriptTemplate.js index 0d80873..4a38122 100644 --- a/userscriptTemplate.js +++ b/userscriptTemplate.js @@ -16,7 +16,7 @@ wpToolsEverywhere: true, // not yet implemented siteConfigs: [ { - name: "twitter", // Not parsed, for documentation purposes + name: "twitter", // Required, for documentation and debug logging matchSites: ["twitter.com"], // String or Array of strings of sites to inject on. Matches globs (eg. *.discord.com) chunkObject: "webpackChunk_twitter_responsive_web", // Name of webpack chunk object to intercept webpackVersion: "5", // Version of webpack used to compile. TODO: Document this. Supported are 4 and 5