Store patched functions in a weakmap, move events wrapper and prevent plugins from modifying the plugin and content prototypes and their internals object
This commit is contained in:
parent
e7b0acb5a0
commit
bc1f93dd89
|
@ -15,7 +15,17 @@ import Database from './database';
|
||||||
export default class Content {
|
export default class Content {
|
||||||
|
|
||||||
constructor(internals) {
|
constructor(internals) {
|
||||||
this.__internals = internals;
|
Object.freeze(internals);
|
||||||
|
Utils.deepfreeze(internals.info);
|
||||||
|
Object.freeze(internals.paths);
|
||||||
|
Object.freeze(internals.configs);
|
||||||
|
|
||||||
|
Object.defineProperty(this, '__internals', {
|
||||||
|
value: internals,
|
||||||
|
configurable: false,
|
||||||
|
enumerable: false,
|
||||||
|
writable: false
|
||||||
|
});
|
||||||
|
|
||||||
this.settings.on('setting-updated', event => this.events.emit('setting-updated', event));
|
this.settings.on('setting-updated', event => this.events.emit('setting-updated', event));
|
||||||
this.settings.on('settings-updated', event => this.events.emit('settings-updated', event));
|
this.settings.on('settings-updated', event => this.events.emit('settings-updated', event));
|
||||||
|
@ -170,3 +180,5 @@ export default class Content {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object.freeze(Content.prototype);
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/**
|
||||||
|
* BetterDiscord Events Wrapper 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const eventemitters = new WeakMap();
|
||||||
|
|
||||||
|
export default class EventsWrapper {
|
||||||
|
constructor(eventemitter) {
|
||||||
|
eventemitters.set(this, eventemitter);
|
||||||
|
}
|
||||||
|
|
||||||
|
get eventSubs() {
|
||||||
|
return this._eventSubs || (this._eventSubs = []);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(event, callback) {
|
||||||
|
if (this.eventSubs.find(e => e.event === event && e.callback === callback)) return;
|
||||||
|
this.eventSubs.push({
|
||||||
|
event,
|
||||||
|
callback
|
||||||
|
});
|
||||||
|
eventemitters.get(this).on(event, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe(event, callback) {
|
||||||
|
for (let index of this.eventSubs) {
|
||||||
|
if (this.eventSubs[index].event !== event || (callback && this.eventSubs[index].callback === callback)) return;
|
||||||
|
eventemitters.get(this).off(event, this.eventSubs[index].callback);
|
||||||
|
this.eventSubs.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribeAll() {
|
||||||
|
for (let event of this.eventSubs) {
|
||||||
|
eventemitters.get(this).off(event.event, event.callback);
|
||||||
|
}
|
||||||
|
this.eventSubs.splice(0, this.eventSubs.length);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,3 +13,4 @@ export { default as SocketProxy } from './socketproxy';
|
||||||
export { default as EventHook } from './eventhook';
|
export { default as EventHook } from './eventhook';
|
||||||
export { default as Permissions } from './permissionmanager';
|
export { default as Permissions } from './permissionmanager';
|
||||||
export { default as Database } from './database';
|
export { default as Database } from './database';
|
||||||
|
export { default as EventsWrapper } from './eventswrapper';
|
||||||
|
|
|
@ -11,27 +11,27 @@
|
||||||
const PermissionMap = {
|
const PermissionMap = {
|
||||||
IDENTIFY: {
|
IDENTIFY: {
|
||||||
HEADER: 'Access your account information',
|
HEADER: 'Access your account information',
|
||||||
BODY: 'Allows :NAME: to read your account information(excluding user token)'
|
BODY: 'Allows :NAME: to read your account information (excluding user token).'
|
||||||
},
|
},
|
||||||
READ_MESSAGES: {
|
READ_MESSAGES: {
|
||||||
HEADER: 'Read all messages',
|
HEADER: 'Read all messages',
|
||||||
BODY: 'Allows :NAME: to read all messages accessible through your Discord account'
|
BODY: 'Allows :NAME: to read all messages accessible through your Discord account.'
|
||||||
},
|
},
|
||||||
SEND_MESSAGES: {
|
SEND_MESSAGES: {
|
||||||
HEADER: 'Send messages',
|
HEADER: 'Send messages',
|
||||||
BODY: 'Allows :NAME: to send messages on your behalf'
|
BODY: 'Allows :NAME: to send messages on your behalf.'
|
||||||
},
|
},
|
||||||
DELETE_MESSAGES: {
|
DELETE_MESSAGES: {
|
||||||
HEADER: 'Delete messages',
|
HEADER: 'Delete messages',
|
||||||
BODY: 'Allows :NAME: to delete messages on your behalf'
|
BODY: 'Allows :NAME: to delete messages on your behalf.'
|
||||||
},
|
},
|
||||||
EDIT_MESSAGES: {
|
EDIT_MESSAGES: {
|
||||||
HEADER: 'Edit messages',
|
HEADER: 'Edit messages',
|
||||||
BODY: 'Allows :NAME: to edit messages on your behalf'
|
BODY: 'Allows :NAME: to edit messages on your behalf.'
|
||||||
},
|
},
|
||||||
JOIN_SERVERS: {
|
JOIN_SERVERS: {
|
||||||
HEADER: 'Join servers for you',
|
HEADER: 'Join servers for you',
|
||||||
BODY: 'Allows :NAME: to join servers on your behalf'
|
BODY: 'Allows :NAME: to join servers on your behalf.'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,50 +14,20 @@ import ExtModuleManager from './extmodulemanager';
|
||||||
import PluginManager from './pluginmanager';
|
import PluginManager from './pluginmanager';
|
||||||
import ThemeManager from './thememanager';
|
import ThemeManager from './thememanager';
|
||||||
import Events from './events';
|
import Events from './events';
|
||||||
|
import EventsWrapper from './eventswrapper';
|
||||||
import WebpackModules from './webpackmodules';
|
import WebpackModules from './webpackmodules';
|
||||||
import { SettingsSet, SettingsCategory, Setting, SettingsScheme } from 'structs';
|
import { SettingsSet, SettingsCategory, Setting, SettingsScheme } from 'structs';
|
||||||
import { BdMenuItems, Modals, DOM } from 'ui';
|
import { BdMenuItems, Modals, DOM } from 'ui';
|
||||||
import SettingsModal from '../ui/components/bd/modals/SettingsModal.vue';
|
import SettingsModal from '../ui/components/bd/modals/SettingsModal.vue';
|
||||||
|
|
||||||
class EventsWrapper {
|
|
||||||
constructor(eventemitter) {
|
|
||||||
this.__eventemitter = eventemitter;
|
|
||||||
}
|
|
||||||
|
|
||||||
get eventSubs() {
|
|
||||||
return this._eventSubs || (this._eventSubs = []);
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe(event, callback) {
|
|
||||||
if (this.eventSubs.find(e => e.event === event && e.callback === callback)) return;
|
|
||||||
this.eventSubs.push({
|
|
||||||
event,
|
|
||||||
callback
|
|
||||||
});
|
|
||||||
this.__eventemitter.on(event, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribe(event, callback) {
|
|
||||||
for (let index of this.eventSubs) {
|
|
||||||
if (this.eventSubs[index].event !== event || (callback && this.eventSubs[index].callback === callback)) return;
|
|
||||||
this.__eventemitter.off(event, this.eventSubs[index].callback);
|
|
||||||
this.eventSubs.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribeAll() {
|
|
||||||
for (let event of this.eventSubs) {
|
|
||||||
this.__eventemitter.off(event.event, event.callback);
|
|
||||||
}
|
|
||||||
this._eventSubs = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class PluginApi {
|
export default class PluginApi {
|
||||||
|
|
||||||
constructor(pluginInfo) {
|
constructor(pluginInfo) {
|
||||||
this.pluginInfo = pluginInfo;
|
this.pluginInfo = pluginInfo;
|
||||||
this.Events = new EventsWrapper(Events);
|
this.Events = new EventsWrapper(Events);
|
||||||
|
this._menuItems = undefined;
|
||||||
|
this._injectedStyles = undefined;
|
||||||
|
this._modalStack = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
get plugin() {
|
get plugin() {
|
||||||
|
@ -292,17 +262,20 @@ export default class PluginApi {
|
||||||
return this.addModal(Modals.settings(settingsset, headertext, options));
|
return this.addModal(Modals.settings(settingsset, headertext, options));
|
||||||
}
|
}
|
||||||
get Modals() {
|
get Modals() {
|
||||||
return Object.defineProperty(Object.defineProperty({
|
return Object.defineProperties({
|
||||||
add: this.addModal.bind(this),
|
add: this.addModal.bind(this),
|
||||||
close: this.closeModal.bind(this),
|
close: this.closeModal.bind(this),
|
||||||
closeAll: this.closeAllModals.bind(this),
|
closeAll: this.closeAllModals.bind(this),
|
||||||
closeLast: this.closeLastModal.bind(this),
|
closeLast: this.closeLastModal.bind(this),
|
||||||
basic: this.basicModal.bind(this),
|
basic: this.basicModal.bind(this),
|
||||||
settings: this.settingsModal.bind(this)
|
settings: this.settingsModal.bind(this)
|
||||||
}, 'stack', {
|
}, {
|
||||||
get: () => this.modalStack
|
stack: {
|
||||||
}), 'baseComponent', {
|
get: () => this.modalStack
|
||||||
get: () => this.baseModalComponent
|
},
|
||||||
|
baseComponent: {
|
||||||
|
get: () => this.baseModalComponent
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -396,3 +369,8 @@ export default class PluginApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop plugins from modifying the plugin API for all plugins
|
||||||
|
// Plugins can still modify their own plugin API object
|
||||||
|
Object.freeze(PluginApi);
|
||||||
|
Object.freeze(PluginApi.prototype);
|
||||||
|
|
|
@ -99,6 +99,9 @@ export default class extends ContentManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
const plugin = window.require(paths.mainPath)(Plugin, new PluginApi(info), Vendor, deps);
|
const plugin = window.require(paths.mainPath)(Plugin, new PluginApi(info), Vendor, deps);
|
||||||
|
if (!(plugin.prototype instanceof Plugin))
|
||||||
|
throw {message: `Plugin ${info.name} did not return a class that extends Plugin.`};
|
||||||
|
|
||||||
const instance = new plugin({
|
const instance = new plugin({
|
||||||
configs, info, main,
|
configs, info, main,
|
||||||
paths: {
|
paths: {
|
||||||
|
|
|
@ -10,10 +10,15 @@
|
||||||
|
|
||||||
import { ClientLogger as Logger } from './logger';
|
import { ClientLogger as Logger } from './logger';
|
||||||
|
|
||||||
|
const patchedFunctions = new WeakMap();
|
||||||
|
|
||||||
export class PatchedFunction {
|
export class PatchedFunction {
|
||||||
constructor(object, methodName, replaceOriginal = true) {
|
constructor(object, methodName, replaceOriginal = true) {
|
||||||
if (object[methodName].__monkeyPatch)
|
if (patchedFunctions.has(object[methodName])) {
|
||||||
return object[methodName].__monkeyPatch;
|
const patchedFunction = patchedFunctions.get(object[methodName]);
|
||||||
|
if (replaceOriginal) patchedFunction.replaceOriginal();
|
||||||
|
return patchedFunction;
|
||||||
|
}
|
||||||
|
|
||||||
this.object = object;
|
this.object = object;
|
||||||
this.methodName = methodName;
|
this.methodName = methodName;
|
||||||
|
@ -25,7 +30,9 @@ export class PatchedFunction {
|
||||||
this.replace = function(...args) {
|
this.replace = function(...args) {
|
||||||
patchedFunction.call(this, arguments);
|
patchedFunction.call(this, arguments);
|
||||||
};
|
};
|
||||||
this.replace.__monkeyPatch = this;
|
|
||||||
|
patchedFunctions.set(object[methodName], this);
|
||||||
|
patchedFunctions.set(this.replace, this);
|
||||||
|
|
||||||
if (replaceOriginal)
|
if (replaceOriginal)
|
||||||
this.replaceOriginal();
|
this.replaceOriginal();
|
||||||
|
|
Loading…
Reference in New Issue