2017-04-21 16:50:14 +09:00
|
|
|
import * as path from 'path';
|
|
|
|
import {Config, Account} from '../main/config';
|
|
|
|
import log from './log';
|
|
|
|
|
2017-04-25 17:30:18 +09:00
|
|
|
interface Plugin {
|
2017-04-26 15:52:57 +09:00
|
|
|
preload?(c: Config, a: Account): void;
|
|
|
|
keymaps?: {
|
|
|
|
[action: string]: (e: KeyboardEvent, c: Config, a: Account) => void;
|
|
|
|
};
|
2017-04-25 17:30:18 +09:00
|
|
|
}
|
2017-04-21 16:50:14 +09:00
|
|
|
interface Plugins {
|
|
|
|
[module_path: string]: Plugin;
|
|
|
|
}
|
|
|
|
|
2017-04-24 18:31:55 +09:00
|
|
|
export default class PluginsLoader {
|
|
|
|
preloads: Plugins;
|
|
|
|
loaded: boolean;
|
|
|
|
|
|
|
|
constructor(private config: Config, private account: Account) {
|
|
|
|
this.loaded = false;
|
|
|
|
this.preloads = {};
|
|
|
|
|
|
|
|
if (config.chromium_sandbox) {
|
2017-04-25 17:30:18 +09:00
|
|
|
log.info('Chromium sandbox is enabled. Plugin is disabled.');
|
2017-04-24 18:31:55 +09:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const dir_base = path.join(config.__DATA_DIR!, 'node_modules');
|
2017-04-25 17:30:18 +09:00
|
|
|
for (const plugin of this.account.plugins || []) {
|
|
|
|
const plugin_path = path.join(dir_base, `mstdn-plugin-${plugin}`);
|
2017-04-24 18:31:55 +09:00
|
|
|
try {
|
2017-04-25 17:37:10 +09:00
|
|
|
this.preloads[plugin_path] = require(plugin_path) as Plugin;
|
2017-04-24 18:31:55 +09:00
|
|
|
} catch (e) {
|
|
|
|
log.error(`Failed to load plugin ${plugin_path}:`, e);
|
|
|
|
}
|
|
|
|
}
|
2017-04-21 16:50:14 +09:00
|
|
|
}
|
2017-04-24 18:31:55 +09:00
|
|
|
|
|
|
|
loadAfterAppPrepared() {
|
|
|
|
if (Object.keys(this.preloads).length === 0) {
|
2017-04-25 17:30:18 +09:00
|
|
|
log.info('No Plugin found. Skip loading');
|
2017-04-24 18:31:55 +09:00
|
|
|
this.loaded = true;
|
|
|
|
return;
|
2017-04-21 16:50:14 +09:00
|
|
|
}
|
2017-04-24 18:31:55 +09:00
|
|
|
|
|
|
|
return new Promise<void>(resolve => {
|
|
|
|
// In order not to prevent application's initial loading, load preload plugins
|
|
|
|
// on an idle callback.
|
|
|
|
window.requestIdleCallback(() => {
|
2017-04-25 17:30:18 +09:00
|
|
|
log.debug('Start loading plugins', this.preloads);
|
2017-04-24 18:31:55 +09:00
|
|
|
if (this.tryLoading()) {
|
|
|
|
return resolve();
|
|
|
|
}
|
|
|
|
this.observeAppPrepared(this.tryLoading).then(resolve);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
observeAppPrepared(callback: () => void) {
|
|
|
|
// TODO:
|
|
|
|
// Make an instance of MutationObserver to observe React root.
|
2017-04-25 17:30:18 +09:00
|
|
|
// But it may be unnecessary because application shell is rendered
|
|
|
|
// in server side.
|
2017-04-24 18:31:55 +09:00
|
|
|
return Promise.resolve(callback());
|
|
|
|
}
|
|
|
|
|
|
|
|
tryLoading = () => {
|
|
|
|
if (document.querySelector('[data-react-class="Mastodon"]') === null) {
|
|
|
|
log.info('Root element of react app was not found. App seems not to be loaded yet.');
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const key in this.preloads) {
|
2017-04-25 17:30:18 +09:00
|
|
|
const f = this.preloads[key].preload;
|
|
|
|
if (!f) {
|
|
|
|
log.info('Plugin does not have preload function. Skipped:', key);
|
|
|
|
continue;
|
|
|
|
}
|
2017-04-24 18:31:55 +09:00
|
|
|
try {
|
|
|
|
f(this.config, this.account);
|
|
|
|
} catch (e) {
|
2017-04-25 17:30:18 +09:00
|
|
|
log.error(`Error while loading plugin '${key}':`, e);
|
2017-04-24 18:31:55 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-25 17:30:18 +09:00
|
|
|
log.info('Plugins were loaded:', this.preloads);
|
2017-04-24 18:31:55 +09:00
|
|
|
return true;
|
2017-04-21 16:50:14 +09:00
|
|
|
}
|
2017-04-26 15:52:57 +09:00
|
|
|
|
|
|
|
findPluginByName(name: string): Plugin | null {
|
|
|
|
const pluginName = `mstdn-plugin-${name}`;
|
|
|
|
for (const p in this.preloads) {
|
|
|
|
if (p.endsWith(pluginName)) {
|
|
|
|
return this.preloads[p];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
runKeyShortcut(event: KeyboardEvent, name: string, action: string) {
|
|
|
|
const plugin = this.findPluginByName(name);
|
|
|
|
if (plugin === null) {
|
|
|
|
log.error(`While trying to execute key shortcut '${action}', plugin for '${name}' not found:`, this.preloads);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const f = (plugin.keymaps || {})[action];
|
|
|
|
if (!f) {
|
|
|
|
log.error(`There is no key shortcut action '${action}' in plugin '${name}'`, plugin);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
f(event, this.config, this.account);
|
|
|
|
} catch (e) {
|
|
|
|
log.error(`Error while executing plugin-defined key short action: ${action} with plugin '${name}'`, e);
|
|
|
|
}
|
|
|
|
}
|
2017-04-21 16:50:14 +09:00
|
|
|
}
|