diff --git a/client/src/modules/content.js b/client/src/modules/content.js index 0517c78b..a54d9e42 100644 --- a/client/src/modules/content.js +++ b/client/src/modules/content.js @@ -15,7 +15,17 @@ import Database from './database'; export default class Content { 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('settings-updated', event => this.events.emit('settings-updated', event)); @@ -170,3 +180,5 @@ export default class Content { } } + +Object.freeze(Content.prototype); diff --git a/client/src/modules/eventswrapper.js b/client/src/modules/eventswrapper.js new file mode 100644 index 00000000..49a654cd --- /dev/null +++ b/client/src/modules/eventswrapper.js @@ -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); + } +} diff --git a/client/src/modules/modules.js b/client/src/modules/modules.js index b93216a3..71956f05 100644 --- a/client/src/modules/modules.js +++ b/client/src/modules/modules.js @@ -13,3 +13,4 @@ export { default as SocketProxy } from './socketproxy'; export { default as EventHook } from './eventhook'; export { default as Permissions } from './permissionmanager'; export { default as Database } from './database'; +export { default as EventsWrapper } from './eventswrapper'; diff --git a/client/src/modules/permissionmanager.js b/client/src/modules/permissionmanager.js index a5fc0d94..b052207c 100644 --- a/client/src/modules/permissionmanager.js +++ b/client/src/modules/permissionmanager.js @@ -11,27 +11,27 @@ const PermissionMap = { IDENTIFY: { 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: { 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: { HEADER: 'Send messages', - BODY: 'Allows :NAME: to send messages on your behalf' + BODY: 'Allows :NAME: to send messages on your behalf.' }, 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: { HEADER: 'Edit messages', - BODY: 'Allows :NAME: to edit messages on your behalf' + BODY: 'Allows :NAME: to edit messages on your behalf.' }, JOIN_SERVERS: { HEADER: 'Join servers for you', - BODY: 'Allows :NAME: to join servers on your behalf' + BODY: 'Allows :NAME: to join servers on your behalf.' } } diff --git a/client/src/modules/pluginapi.js b/client/src/modules/pluginapi.js index 0ae6b4ff..f5b0a023 100644 --- a/client/src/modules/pluginapi.js +++ b/client/src/modules/pluginapi.js @@ -14,50 +14,20 @@ import ExtModuleManager from './extmodulemanager'; import PluginManager from './pluginmanager'; import ThemeManager from './thememanager'; import Events from './events'; +import EventsWrapper from './eventswrapper'; import WebpackModules from './webpackmodules'; import { SettingsSet, SettingsCategory, Setting, SettingsScheme } from 'structs'; import { BdMenuItems, Modals, DOM } from 'ui'; 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 { constructor(pluginInfo) { this.pluginInfo = pluginInfo; this.Events = new EventsWrapper(Events); + this._menuItems = undefined; + this._injectedStyles = undefined; + this._modalStack = undefined; } get plugin() { @@ -292,17 +262,20 @@ export default class PluginApi { return this.addModal(Modals.settings(settingsset, headertext, options)); } get Modals() { - return Object.defineProperty(Object.defineProperty({ + return Object.defineProperties({ add: this.addModal.bind(this), close: this.closeModal.bind(this), closeAll: this.closeAllModals.bind(this), closeLast: this.closeLastModal.bind(this), basic: this.basicModal.bind(this), settings: this.settingsModal.bind(this) - }, 'stack', { - get: () => this.modalStack - }), 'baseComponent', { - get: () => this.baseModalComponent + }, { + stack: { + get: () => this.modalStack + }, + 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); diff --git a/client/src/modules/pluginmanager.js b/client/src/modules/pluginmanager.js index 6f8a3b12..198d06e1 100644 --- a/client/src/modules/pluginmanager.js +++ b/client/src/modules/pluginmanager.js @@ -99,6 +99,9 @@ export default class extends ContentManager { } 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({ configs, info, main, paths: { diff --git a/common/modules/monkeypatch.js b/common/modules/monkeypatch.js index 75ab45a7..a7fe0da0 100644 --- a/common/modules/monkeypatch.js +++ b/common/modules/monkeypatch.js @@ -10,10 +10,15 @@ import { ClientLogger as Logger } from './logger'; +const patchedFunctions = new WeakMap(); + export class PatchedFunction { constructor(object, methodName, replaceOriginal = true) { - if (object[methodName].__monkeyPatch) - return object[methodName].__monkeyPatch; + if (patchedFunctions.has(object[methodName])) { + const patchedFunction = patchedFunctions.get(object[methodName]); + if (replaceOriginal) patchedFunction.replaceOriginal(); + return patchedFunction; + } this.object = object; this.methodName = methodName; @@ -25,7 +30,9 @@ export class PatchedFunction { this.replace = function(...args) { patchedFunction.call(this, arguments); }; - this.replace.__monkeyPatch = this; + + patchedFunctions.set(object[methodName], this); + patchedFunctions.set(this.replace, this); if (replaceOriginal) this.replaceOriginal();