BetterDiscordApp-v2/client/src/ui/reflection.js

224 lines
7.2 KiB
JavaScript

/**
* BetterDiscord Reflection Module
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
* All rights reserved.
* https://betterdiscord.net
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { Filters, ClientLogger as Logger } from 'common';
import { ReactComponents } from 'modules';
class Reflection {
static reactInternalInstance(node) {
if (!node) return null;
if (!Object.keys(node) || !Object.keys(node).length) return null;
const riiKey = Object.keys(node).find(k => k.startsWith('__reactInternalInstance'));
return riiKey ? node[riiKey] : null;
}
static findProp(node, prop) {
const ii = this.reactInternalInstance(node);
if (!ii) return null;
const fir = this.findInReturn(ii, prop);
if (fir) return fir;
const fim = this.findInChildProps(ii, prop);
if (fim) return fim;
return null;
}
static findInReturn(internalInstance, prop) {
const r = internalInstance.return;
if (!r) return null;
let find = this.findMemoizedProp(r, prop);
if (find) return find;
find = this.findMemoizedState(r, prop);
if (find) return find;
return this.findInReturn(r, prop);
}
static findMemoizedProp(obj, prop) {
if (!obj.hasOwnProperty('memoizedProps')) return null;
obj = obj.memoizedProps;
return this.findPropIn(obj, prop);
}
static findMemoizedState(obj, prop) {
if (!obj.hasOwnProperty('memoizedState')) return null;
obj = obj.memoizedState;
return this.findPropIn(obj, prop);
}
static findInChildProps(obj, prop) {
try {
const f = obj.children || obj.memoizedProps.children;
if (!f.props) return null;
if (!f.props.hasOwnProperty(prop)) return null;
return f.props[prop];
} catch (err) {
return null;
}
}
static findPropIn(obj, prop) {
if (obj && !(obj instanceof Array) && obj instanceof Object && obj.hasOwnProperty(prop)) return obj[prop];
if (obj && obj instanceof Array) {
const found = obj.find(mp => {
if (mp.props && mp.props.hasOwnProperty(prop)) return true;
});
if (found) return found;
}
return null;
}
static propIterator(obj, propNames) {
if (obj === null || obj === undefined) return null;
const curPropName = propNames.shift(1);
if (!obj.hasOwnProperty(curPropName)) return null;
const curProp = obj[curPropName];
if (propNames.length === 0) {
return curProp;
}
return this.propIterator(curProp, propNames);
}
static getState(node) {
const stateNode = this.getStateNode(node);
if (stateNode) return stateNode.state;
}
static getStateNode(node) {
return this.getStateNodes(node)[0];
}
static getStateNodes(node) {
const instance = this.reactInternalInstance(node);
const stateNodes = [];
let lastInstance = instance;
do {
if (lastInstance.return.stateNode instanceof HTMLElement) break;
if (lastInstance.return.stateNode) stateNodes.push(lastInstance.return.stateNode);
lastInstance = lastInstance.return;
} while (lastInstance.return);
return stateNodes;
}
static getComponentStateNode(node, component) {
if (component instanceof ReactComponents.ReactComponent) component = component.component;
for (const stateNode of this.getStateNodes(node)) {
if (stateNode instanceof component) return stateNode;
}
}
static findStateNode(node, filter, first = true) {
return this.getStateNodes(node)[first ? 'find' : 'filter'](filter);
}
static getComponent(node) {
return this.getComponents(node)[0];
}
static getComponents(node) {
const instance = this.reactInternalInstance(node);
const components = [];
let lastInstance = instance;
do {
if (typeof lastInstance.return.type === 'string') break;
if (lastInstance.return.type) components.push(lastInstance.return.type);
lastInstance = lastInstance.return;
} while (lastInstance.return);
return components;
}
static findComponent(node, filter, first = true) {
return this.getComponents(node)[first ? 'find' : 'filter'](filter);
}
}
const propsProxyHandler = {
get(node, prop) {
return Reflection.findProp(node, prop);
}
};
export default function (node) {
return new class ReflectionInstance {
constructor(node) {
if (typeof node === 'string') node = document.querySelector(node);
this.node = /* this.el = this.element = */ node;
}
get el() { return this.node }
get element() { return this.node }
get reactInternalInstance() {
return Reflection.reactInternalInstance(this.node);
}
get props() {
return new Proxy(this.node, propsProxyHandler);
}
get state() {
return Reflection.getState(this.node);
}
get stateNode() {
return Reflection.getStateNode(this.node);
}
get stateNodes() {
return Reflection.getStateNodes(this.node);
}
getComponentStateNode(component) {
return Reflection.getComponentStateNode(this.node, component);
}
findStateNode(filter) {
if (typeof filter === 'function') return Reflection.findStateNode(this.node, filter);
if (filter) return Reflection.getComponentStateNode(this.node, filter);
return Reflection.getStateNode(this.node);
}
get component() {
return Reflection.getComponent(this.node);
}
get components() {
return Reflection.getComponents(this.node);
}
getComponentByProps(props, selector) {
return Reflection.findComponent(this.node, Filters.byProperties(props, selector));
}
getComponentByPrototypes(props, selector) {
return Reflection.findComponent(this.node, Filters.byPrototypeFields(props, selector));
}
getComponentByRegex(regex, selector) {
return Reflection.findComponent(this.node, Filters.byCode(regex, selector));
}
getComponentByDisplayName(name) {
return Reflection.findComponent(this.node, Filters.byDisplayName(name));
}
forceUpdate(filter) {
try {
const stateNode = this.findStateNode(filter);
if (!stateNode || !stateNode.forceUpdate) return;
stateNode.forceUpdate();
} catch (err) {
Logger.err('Reflection', err);
}
}
prop(propName) {
const split = propName.split('.');
const first = Reflection.findProp(this.node, split[0]);
if (split.length === 1) return first;
return Reflection.propIterator(first, split.slice(1));
}
}(node);
}