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:
Samuel Elliott 2018-03-08 23:43:26 +00:00
parent e7b0acb5a0
commit bc1f93dd89
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6
7 changed files with 95 additions and 49 deletions

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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';

View File

@ -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.'
}
}

View File

@ -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);

View File

@ -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: {

View File

@ -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();