Webpack Warnings & Errors (#178)

* dev: Useful strict Warnings & Errors

* Always log error

* Ignore pending patches with all or whose predicate = false

* Error -> Warn
This commit is contained in:
Ven 2022-10-30 20:45:18 +01:00 committed by GitHub
parent b905743077
commit 8adf7ca155
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 90 additions and 55 deletions

@ -25,7 +25,7 @@ export * as Webpack from "./webpack";
import { popNotice, showNotice } from "./api/Notices"; import { popNotice, showNotice } from "./api/Notices";
import { PlainSettings, Settings } from "./api/settings"; import { PlainSettings, Settings } from "./api/settings";
import { startAllPlugins } from "./plugins"; import { patches, PMLogger, startAllPlugins } from "./plugins";
export { PlainSettings, Settings }; export { PlainSettings, Settings };
@ -61,6 +61,19 @@ async function init() {
UpdateLogger.error("Failed to check for updates", err); UpdateLogger.error("Failed to check for updates", err);
} }
} }
if (IS_DEV) {
const pendingPatches = patches.filter(p => !p.all && p.predicate?.() !== false);
if (pendingPatches.length)
PMLogger.warn(
"Webpack has finished initialising, but some patches haven't been applied yet.",
"This might be expected since some Modules are lazy loaded, but please verify",
"that all plugins are working as intended.",
"You are seeing this warning because this is a Development build of Vencord.",
"\nThe following patches have not been applied:",
"\n\n" + pendingPatches.map(p => `${p.plugin}: ${p.find}`).join("\n")
);
}
} }
init(); init();

@ -24,7 +24,7 @@ import { filters, waitFor } from "../../webpack";
import { Argument } from "./types"; import { Argument } from "./types";
const createBotMessage = lazyWebpack(filters.byCode('username:"Clyde"')); const createBotMessage = lazyWebpack(filters.byCode('username:"Clyde"'));
const MessageSender = lazyWebpack(filters.byProps(["receiveMessage"])); const MessageSender = lazyWebpack(filters.byProps("receiveMessage"));
let SnowflakeUtils: any; let SnowflakeUtils: any;
waitFor("fromTimestamp", m => SnowflakeUtils = m); waitFor("fromTimestamp", m => SnowflakeUtils = m);

@ -38,7 +38,7 @@ import {
} from "./components"; } from "./components";
const UserSummaryItem = lazyWebpack(filters.byCode("defaultRenderUser", "showDefaultAvatarsForNullUsers")); const UserSummaryItem = lazyWebpack(filters.byCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
const AvatarStyles = lazyWebpack(filters.byProps(["moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"])); const AvatarStyles = lazyWebpack(filters.byProps("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"));
const UserRecord: Constructor<Partial<User>> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any; const UserRecord: Constructor<Partial<User>> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any;
interface PluginModalProps extends ModalProps { interface PluginModalProps extends ModalProps {

@ -36,7 +36,7 @@ import * as styles from "./styles";
const logger = new Logger("PluginSettings", "#a6d189"); const logger = new Logger("PluginSettings", "#a6d189");
const Select = lazyWebpack(filters.byCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems")); const Select = lazyWebpack(filters.byCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems"));
const InputStyles = lazyWebpack(filters.byProps(["inputDefault", "inputWrapper"])); const InputStyles = lazyWebpack(filters.byProps("inputDefault", "inputWrapper"));
const CogWheel = lazyWebpack(filters.byCode("18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069")); const CogWheel = lazyWebpack(filters.byCode("18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069"));
const InfoIcon = lazyWebpack(filters.byCode("4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16")); const InfoIcon = lazyWebpack(filters.byCode("4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16"));

@ -23,7 +23,7 @@ import { Settings } from "../Vencord";
import { filters } from "../webpack"; import { filters } from "../webpack";
import { Forms, React } from "../webpack/common"; import { Forms, React } from "../webpack/common";
const KbdStyles = lazyWebpack(filters.byProps(["key", "removeBuildOverride"])); const KbdStyles = lazyWebpack(filters.byProps("key", "removeBuildOverride"));
export default definePlugin({ export default definePlugin({
name: "Experiments", name: "Experiments",

@ -28,7 +28,7 @@ interface MatchAndReplace {
} }
/** Used to re-render the Registered Games tab to update how our button looks like */ /** Used to re-render the Registered Games tab to update how our button looks like */
const RunningGameStoreModule = lazyWebpack(filters.byProps(["IgnoreActivities_reRenderGames"])); const RunningGameStoreModule = lazyWebpack(filters.byProps("IgnoreActivities_reRenderGames"));
let ignoredActivitiesCache: string[] = []; let ignoredActivitiesCache: string[] = [];

@ -25,6 +25,7 @@ import { Patch, Plugin } from "../utils/types";
const logger = new Logger("PluginManager", "#a6d189"); const logger = new Logger("PluginManager", "#a6d189");
export const PMLogger = logger;
export const plugins = Plugins; export const plugins = Plugins;
export const patches = [] as Patch[]; export const patches = [] as Patch[];

@ -24,7 +24,7 @@ import definePlugin from "../utils/types";
import { filters } from "../webpack"; import { filters } from "../webpack";
import { ChannelStore, FluxDispatcher as Dispatcher, SelectedChannelStore, UserStore } from "../webpack/common"; import { ChannelStore, FluxDispatcher as Dispatcher, SelectedChannelStore, UserStore } from "../webpack/common";
const MessageStore = lazyWebpack(filters.byProps(["getRawMessages"])); const MessageStore = lazyWebpack(filters.byProps("getRawMessages"));
const isMac = navigator.platform.includes("Mac"); // bruh const isMac = navigator.platform.includes("Mac"); // bruh
let replyIdx = -1; let replyIdx = -1;

@ -16,9 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { ApplicationCommandInputType, ApplicationCommandOptionType, Argument, CommandContext,findOption } from "../api/Commands"; import { ApplicationCommandInputType, ApplicationCommandOptionType, Argument, CommandContext, findOption } from "../api/Commands";
import { Devs } from "../utils/constants"; import { Devs } from "../utils/constants";
import { lazyWebpack,makeLazy } from "../utils/misc"; import { lazyWebpack, makeLazy } from "../utils/misc";
import definePlugin from "../utils/types"; import definePlugin from "../utils/types";
import { filters } from "../webpack"; import { filters } from "../webpack";
@ -41,7 +41,7 @@ const getFrames = makeLazy(() => Promise.all(
const fetchUser = lazyWebpack(filters.byCode(".USER(")); const fetchUser = lazyWebpack(filters.byCode(".USER("));
const promptToUpload = lazyWebpack(filters.byCode("UPLOAD_FILE_LIMIT_ERROR")); const promptToUpload = lazyWebpack(filters.byCode("UPLOAD_FILE_LIMIT_ERROR"));
const UploadStore = lazyWebpack(filters.byProps(["getUploads"])); const UploadStore = lazyWebpack(filters.byProps("getUploads"));
function loadImage(source: File | string) { function loadImage(source: File | string) {
const isFile = source instanceof File; const isFile = source instanceof File;

@ -25,7 +25,7 @@ import { UserStore } from "../../../webpack/common";
import { PronounMapping } from "../types"; import { PronounMapping } from "../types";
import { fetchPronouns, formatPronouns } from "../utils"; import { fetchPronouns, formatPronouns } from "../utils";
const styles: Record<string, string> = lazyWebpack(filters.byProps(["timestampInline"])); const styles: Record<string, string> = lazyWebpack(filters.byProps("timestampInline"));
export default function PronounsChatComponent({ message }: { message: Message; }) { export default function PronounsChatComponent({ message }: { message: Message; }) {
// Don't bother fetching bot or system users // Don't bother fetching bot or system users

@ -29,7 +29,7 @@ const Engines = {
TinEye: "https://www.tineye.com/search?url=" TinEye: "https://www.tineye.com/search?url="
}; };
const Menu = lazyWebpack(filters.byProps(["MenuItem"])); const Menu = lazyWebpack(filters.byProps("MenuItem"));
export default definePlugin({ export default definePlugin({

@ -53,9 +53,9 @@ interface Track {
name: string; name: string;
} }
const Spotify = lazyWebpack(filters.byProps(["getPlayerState"])); const Spotify = lazyWebpack(filters.byProps("getPlayerState"));
const MessageCreator = lazyWebpack(filters.byProps(["getSendMessageOptionsForReply", "sendMessage"])); const MessageCreator = lazyWebpack(filters.byProps("getSendMessageOptionsForReply", "sendMessage"));
const PendingReplyStore = lazyWebpack(filters.byProps(["getPendingReply"])); const PendingReplyStore = lazyWebpack(filters.byProps("getPendingReply"));
function sendMessage(channelId, message) { function sendMessage(channelId, message) {
message = { message = {

@ -29,7 +29,7 @@ export default class Logger {
return ["%c %c %s ", "", `background: ${color}; color: black; font-weight: bold; border-radius: 5px;`, title]; return ["%c %c %s ", "", `background: ${color}; color: black; font-weight: bold; border-radius: 5px;`, title];
} }
constructor(public name: string, public color: string) { } constructor(public name: string, public color: string = "white") { }
private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") { private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") {
console[level]( console[level](

@ -23,7 +23,7 @@ import type Stores from "discord-types/stores";
import { lazyWebpack } from "../utils/misc"; import { lazyWebpack } from "../utils/misc";
import { _resolveReady, filters, mapMangledModuleLazy, waitFor } from "./webpack"; import { _resolveReady, filters, mapMangledModuleLazy, waitFor } from "./webpack";
export const Margins = lazyWebpack(filters.byProps(["marginTop20"])); export const Margins = lazyWebpack(filters.byProps("marginTop20"));
export let FluxDispatcher: Other.FluxDispatcher; export let FluxDispatcher: Other.FluxDispatcher;
export let React: typeof import("react"); export let React: typeof import("react");

@ -102,7 +102,7 @@ function patchPush() {
callback(exports.default); callback(exports.default);
} }
for (const nested in exports) if (nested.length < 3) { for (const nested in exports) if (nested.length <= 3) {
if (exports[nested] && filter(exports[nested])) { if (exports[nested] && filter(exports[nested])) {
subscriptions.delete(filter); subscriptions.delete(filter);
callback(exports[nested]); callback(exports[nested]);

@ -18,6 +18,7 @@
import type { WebpackInstance } from "discord-types/other"; import type { WebpackInstance } from "discord-types/other";
import Logger from "../utils/logger";
import { proxyLazy } from "../utils/proxyLazy"; import { proxyLazy } from "../utils/proxyLazy";
export let _resolveReady: () => void; export let _resolveReady: () => void;
@ -33,11 +34,13 @@ export let cache: WebpackInstance["c"];
export type FilterFn = (mod: any) => boolean; export type FilterFn = (mod: any) => boolean;
export const filters = { export const filters = {
byProps: (props: string[]): FilterFn => byProps: (...props: string[]): FilterFn =>
props.length === 1 props.length === 1
? m => m[props[0]] !== void 0 ? m => m[props[0]] !== void 0
: m => props.every(p => m[p] !== void 0), : m => props.every(p => m[p] !== void 0),
byDisplayName: (deezNuts: string): FilterFn => m => m.default?.displayName === deezNuts, byDisplayName: (deezNuts: string): FilterFn => m => m.default?.displayName === deezNuts,
byCode: (...code: string[]): FilterFn => m => { byCode: (...code: string[]): FilterFn => m => {
if (typeof m !== "function") return false; if (typeof m !== "function") return false;
const s = Function.prototype.toString.call(m); const s = Function.prototype.toString.call(m);
@ -48,6 +51,7 @@ export const filters = {
}, },
}; };
const logger = new Logger("Webpack");
export const subscriptions = new Map<FilterFn, CallbackFn>(); export const subscriptions = new Map<FilterFn, CallbackFn>();
export const listeners = new Set<CallbackFn>(); export const listeners = new Set<CallbackFn>();
@ -56,12 +60,12 @@ export type CallbackFn = (mod: any) => void;
export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) { export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) {
if (cache !== void 0) throw "no."; if (cache !== void 0) throw "no.";
wreq = instance.push([[Symbol()], {}, r => r]); wreq = instance.push([[Symbol("Vencord")], {}, r => r]);
cache = wreq.c; cache = wreq.c;
instance.pop(); instance.pop();
} }
export function find(filter: FilterFn, getDefault = true) { export function find(filter: FilterFn, getDefault = true, isWaitFor = false) {
if (typeof filter !== "function") if (typeof filter !== "function")
throw new Error("Invalid filter. Expected a function got " + typeof filter); throw new Error("Invalid filter. Expected a function got " + typeof filter);
@ -77,7 +81,6 @@ export function find(filter: FilterFn, getDefault = true) {
if (mod.exports.default && filter(mod.exports.default)) if (mod.exports.default && filter(mod.exports.default))
return getDefault ? mod.exports.default : mod.exports; return getDefault ? mod.exports.default : mod.exports;
// is 3 is the longest obfuscated export?
// the length check makes search about 20% faster // the length check makes search about 20% faster
for (const nestedMod in mod.exports) if (nestedMod.length <= 3) { for (const nestedMod in mod.exports) if (nestedMod.length <= 3) {
const nested = mod.exports[nestedMod]; const nested = mod.exports[nestedMod];
@ -85,11 +88,21 @@ export function find(filter: FilterFn, getDefault = true) {
} }
} }
if (!isWaitFor) {
const err = new Error("Didn't find module matching this filter");
if (IS_DEV) {
// Strict behaviour in DevBuilds to fail early and make sure the issue is found
throw err;
}
logger.warn(err);
}
return null; return null;
} }
export function findAll(filter: FilterFn, getDefault = true) { export function findAll(filter: FilterFn, getDefault = true) {
if (typeof filter !== "function") throw new Error("Invalid filter. Expected a function got " + typeof filter); if (typeof filter !== "function")
throw new Error("Invalid filter. Expected a function got " + typeof filter);
const ret = [] as any[]; const ret = [] as any[];
for (const key in cache) { for (const key in cache) {
@ -113,17 +126,17 @@ export function findAll(filter: FilterFn, getDefault = true) {
} }
/** /**
* Finds a mangled module by the provided code "code" (must be unique and can be anywhere in the module) * Finds a mangled module by the provided code "code" (must be unique and can be anywhere in the module)
* then maps it into an easily usable module via the specified mappers * then maps it into an easily usable module via the specified mappers
* @param code Code snippet * @param code Code snippet
* @param mappers Mappers to create the non mangled exports * @param mappers Mappers to create the non mangled exports
* @returns Unmangled exports as specified in mappers * @returns Unmangled exports as specified in mappers
* *
* @example mapMangledModule("headerIdIsManaged:", { * @example mapMangledModule("headerIdIsManaged:", {
* openModal: filters.byCode("headerIdIsManaged:"), * openModal: filters.byCode("headerIdIsManaged:"),
* closeModal: filters.byCode("key==") * closeModal: filters.byCode("key==")
* }) * })
*/ */
export function mapMangledModule<S extends string>(code: string, mappers: Record<S, FilterFn>): Record<S, any> { export function mapMangledModule<S extends string>(code: string, mappers: Record<S, FilterFn>): Record<S, any> {
const exports = {} as Record<S, any>; const exports = {} as Record<S, any>;
@ -143,26 +156,31 @@ export function mapMangledModule<S extends string>(code: string, mappers: Record
} }
} }
} }
break; return exports;
} }
} }
const err = new Error("Didn't find module matching this code:\n" + code);
if (IS_DEV)
throw err;
logger.warn(err);
return exports; return exports;
} }
/** /**
* Same as {@link mapMangledModule} but lazy * Same as {@link mapMangledModule} but lazy
*/ */
export function mapMangledModuleLazy<S extends string>(code: string, mappers: Record<S, FilterFn>): Record<S, any> { export function mapMangledModuleLazy<S extends string>(code: string, mappers: Record<S, FilterFn>): Record<S, any> {
return proxyLazy(() => mapMangledModule(code, mappers)); return proxyLazy(() => mapMangledModule(code, mappers));
} }
export function findByProps(...props: string[]) { export function findByProps(...props: string[]) {
return find(filters.byProps(props)); return find(filters.byProps(...props));
} }
export function findAllByProps(...props: string[]) { export function findAllByProps(...props: string[]) {
return findAll(filters.byProps(props)); return findAll(filters.byProps(...props));
} }
export function findByDisplayName(deezNuts: string) { export function findByDisplayName(deezNuts: string) {
@ -170,11 +188,14 @@ export function findByDisplayName(deezNuts: string) {
} }
export function waitFor(filter: string | string[] | FilterFn, callback: CallbackFn) { export function waitFor(filter: string | string[] | FilterFn, callback: CallbackFn) {
if (typeof filter === "string") filter = filters.byProps([filter]); if (typeof filter === "string")
else if (Array.isArray(filter)) filter = filters.byProps(filter); filter = filters.byProps(filter);
else if (typeof filter !== "function") throw new Error("filter must be a string, string[] or function, got " + typeof filter); else if (Array.isArray(filter))
filter = filters.byProps(...filter);
else if (typeof filter !== "function")
throw new Error("filter must be a string, string[] or function, got " + typeof filter);
const existing = find(filter!); const existing = find(filter!, true, true);
if (existing) return void callback(existing); if (existing) return void callback(existing);
subscriptions.set(filter, callback); subscriptions.set(filter, callback);
@ -189,11 +210,11 @@ export function removeListener(callback: CallbackFn) {
} }
/** /**
* Search modules by keyword. This searches the factory methods, * Search modules by keyword. This searches the factory methods,
* meaning you can search all sorts of things, displayName, methodName, strings somewhere in the code, etc * meaning you can search all sorts of things, displayName, methodName, strings somewhere in the code, etc
* @param filters One or more strings or regexes * @param filters One or more strings or regexes
* @returns Mapping of found modules * @returns Mapping of found modules
*/ */
export function search(...filters: Array<string | RegExp>) { export function search(...filters: Array<string | RegExp>) {
const results = {} as Record<number, Function>; const results = {} as Record<number, Function>;
const factories = wreq.m; const factories = wreq.m;
@ -212,13 +233,13 @@ export function search(...filters: Array<string | RegExp>) {
} }
/** /**
* Extract a specific module by id into its own Source File. This has no effect on * Extract a specific module by id into its own Source File. This has no effect on
* the code, it is only useful to be able to look at a specific module without having * the code, it is only useful to be able to look at a specific module without having
* to view a massive file. extract then returns the extracted module so you can jump to it. * to view a massive file. extract then returns the extracted module so you can jump to it.
* As mentioned above, note that this extracted module is not actually used, * As mentioned above, note that this extracted module is not actually used,
* so putting breakpoints or similar will have no effect. * so putting breakpoints or similar will have no effect.
* @param id The id of the module to extract * @param id The id of the module to extract
*/ */
export function extract(id: number) { export function extract(id: number) {
const mod = wreq.m[id] as Function; const mod = wreq.m[id] as Function;
if (!mod) return null; if (!mod) return null;