mirror of https://github.com/keeweb/keeweb.git
plugins
This commit is contained in:
parent
87aebe9216
commit
f7da54dded
|
@ -1,7 +1,3 @@
|
||||||
/* eslint-disable import/first */
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
require('preact/debug');
|
|
||||||
}
|
|
||||||
import 'util/kdbxweb/protected-value';
|
import 'util/kdbxweb/protected-value';
|
||||||
import { h, render } from 'preact';
|
import { h, render } from 'preact';
|
||||||
import { App } from 'ui/app';
|
import { App } from 'ui/app';
|
||||||
|
@ -30,7 +26,8 @@ import { ConfigLoader } from 'comp/settings/config-loader';
|
||||||
import { WindowClass } from 'comp/browser/window-class';
|
import { WindowClass } from 'comp/browser/window-class';
|
||||||
import { FileManager } from 'models/file-manager';
|
import { FileManager } from 'models/file-manager';
|
||||||
import { Updater } from './comp/app/updater';
|
import { Updater } from './comp/app/updater';
|
||||||
/* eslint-enable */
|
import { Timeouts } from 'const/timeouts';
|
||||||
|
import { PluginManager } from 'plugins/plugin-manager';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -51,7 +48,7 @@ async function bootstrap() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await loadConfigs();
|
await loadConfigs();
|
||||||
initModules();
|
await initModules();
|
||||||
await loadRemoteConfig();
|
await loadRemoteConfig();
|
||||||
await ensureCanRun();
|
await ensureCanRun();
|
||||||
addWindowClasses();
|
addWindowClasses();
|
||||||
|
@ -95,7 +92,7 @@ async function bootstrap() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initModules() {
|
async function initModules() {
|
||||||
KeyHandler.init();
|
KeyHandler.init();
|
||||||
PopupNotifier.init();
|
PopupNotifier.init();
|
||||||
KdbxwebInit.init();
|
KdbxwebInit.init();
|
||||||
|
@ -103,7 +100,7 @@ async function bootstrap() {
|
||||||
// AutoType.init(); // TODO
|
// AutoType.init(); // TODO
|
||||||
ThemeWatcher.init();
|
ThemeWatcher.init();
|
||||||
SettingsManager.init();
|
SettingsManager.init();
|
||||||
// await PluginManager.init() // TODO
|
await PluginManager.init();
|
||||||
StartProfiler.milestone('initializing modules');
|
StartProfiler.milestone('initializing modules');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +159,10 @@ async function bootstrap() {
|
||||||
AppRightsChecker.init().catch(noop);
|
AppRightsChecker.init().catch(noop);
|
||||||
IdleTracker.init();
|
IdleTracker.init();
|
||||||
// BrowserExtensionConnector.init(appModel); // TODO
|
// BrowserExtensionConnector.init(appModel); // TODO
|
||||||
// PluginManager.runAutoUpdate(); // TODO: timeout
|
|
||||||
|
setTimeout(() => {
|
||||||
|
PluginManager.runAutoUpdate().catch(noop);
|
||||||
|
}, Timeouts.PluginsAutoUpdateAfterStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showView() {
|
function showView() {
|
||||||
|
|
|
@ -6,6 +6,8 @@ import { Locale } from 'util/locale';
|
||||||
import { FileManager } from 'models/file-manager';
|
import { FileManager } from 'models/file-manager';
|
||||||
import { FileInfo, FileStorageExtraOptions } from 'models/file-info';
|
import { FileInfo, FileStorageExtraOptions } from 'models/file-info';
|
||||||
import { IdGenerator } from 'util/generators/id-generator';
|
import { IdGenerator } from 'util/generators/id-generator';
|
||||||
|
import { PluginManager } from 'plugins/plugin-manager';
|
||||||
|
import { PluginManifest } from 'plugins/types';
|
||||||
|
|
||||||
const logger = new Logger('config-loader');
|
const logger = new Logger('config-loader');
|
||||||
|
|
||||||
|
@ -20,7 +22,7 @@ class ConfigLoader {
|
||||||
throw new Error('Invalid app config, no settings section');
|
throw new Error('Invalid app config, no settings section');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.applyUserConfig(config);
|
await this.applyUserConfig(config);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!AppSettings.cacheConfigSettings) {
|
if (!AppSettings.cacheConfigSettings) {
|
||||||
|
@ -86,7 +88,7 @@ class ConfigLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyUserConfig(config: Record<string, unknown>): void {
|
private async applyUserConfig(config: Record<string, unknown>): Promise<void> {
|
||||||
if (!config.settings) {
|
if (!config.settings) {
|
||||||
logger.error('Invalid app config, no settings section', config);
|
logger.error('Invalid app config, no settings section', config);
|
||||||
throw new Error('Invalid app config, no settings section');
|
throw new Error('Invalid app config, no settings section');
|
||||||
|
@ -141,14 +143,19 @@ class ConfigLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.plugins) {
|
if (Array.isArray(config.plugins)) {
|
||||||
// TODO: set plugins
|
const pluginPromises: Promise<void>[] = [];
|
||||||
// const pluginsPromises = config.plugins.map((plugin) =>
|
for (const plugin of config.plugins as Record<string, unknown>[]) {
|
||||||
// PluginManager.installIfNew(plugin.url, plugin.manifest, true)
|
if (typeof plugin.url === 'string' && typeof plugin.manifest === 'object') {
|
||||||
// );
|
const pluginPromise = PluginManager.installIfNew(
|
||||||
// return Promise.all(pluginsPromises).then(() => {
|
plugin.url,
|
||||||
// this.settings.set(config.settings);
|
plugin.manifest as PluginManifest,
|
||||||
// });
|
true
|
||||||
|
);
|
||||||
|
pluginPromises.push(pluginPromise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all(pluginPromises);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.advancedSearch) {
|
if (config.advancedSearch) {
|
||||||
|
|
|
@ -26,5 +26,7 @@ export const Timeouts = {
|
||||||
UpdateInterval: 1000 * 60 * 60 * 24,
|
UpdateInterval: 1000 * 60 * 60 * 24,
|
||||||
MinUpdateTimeout: 500,
|
MinUpdateTimeout: 500,
|
||||||
InputShake: 1000,
|
InputShake: 1000,
|
||||||
WindowResizeUpdateDebounce: 200
|
WindowResizeUpdateDebounce: 200,
|
||||||
|
PluginsAutoUpdateAfterStart: 2000,
|
||||||
|
PluginsUpdate: 1000 * 60 * 60 * 24 * 7
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
require('preact/debug');
|
||||||
|
}
|
||||||
|
require('./bootstrap');
|
|
@ -1,234 +0,0 @@
|
||||||
import { Model } from 'framework/model';
|
|
||||||
import { RuntimeInfo } from 'const/runtime-info';
|
|
||||||
import { SettingsStore } from 'comp/settings/settings-store';
|
|
||||||
import { Plugin, PluginStatus } from 'plugins/plugin';
|
|
||||||
import { PluginCollection } from 'plugins/plugin-collection';
|
|
||||||
import { PluginGallery } from 'plugins/plugin-gallery';
|
|
||||||
import { SignatureVerifier } from 'util/data/signature-verifier';
|
|
||||||
import { Logger } from 'util/logger';
|
|
||||||
import { noop } from 'util/fn';
|
|
||||||
|
|
||||||
const logger = new Logger('plugin-mgr');
|
|
||||||
|
|
||||||
class PluginManager extends Model {
|
|
||||||
static UpdateInterval = 1000 * 60 * 60 * 24 * 7;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super({
|
|
||||||
plugins: new PluginCollection()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
const ts = logger.ts();
|
|
||||||
return SettingsStore.load('plugins').then((state) => {
|
|
||||||
if (!state) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.set({
|
|
||||||
autoUpdateAppVersion: state.autoUpdateAppVersion,
|
|
||||||
autoUpdateDate: state.autoUpdateDate
|
|
||||||
});
|
|
||||||
if (!state || !state.plugins || !state.plugins.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return PluginGallery.getCachedGallery().then((gallery) => {
|
|
||||||
const promises = state.plugins.map((plugin) => this.loadPlugin(plugin, gallery));
|
|
||||||
return Promise.all(promises).then((loadedPlugins) => {
|
|
||||||
this.plugins.push(...loadedPlugins.filter((plugin) => plugin));
|
|
||||||
logger.info(`Loaded ${this.plugins.length} plugins`, logger.ts(ts));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
install(url, expectedManifest, skipSignatureValidation) {
|
|
||||||
this.emit('change');
|
|
||||||
return Plugin.loadFromUrl(url, expectedManifest)
|
|
||||||
.then((plugin) => {
|
|
||||||
return this.uninstall(plugin.id).then(() => {
|
|
||||||
if (skipSignatureValidation) {
|
|
||||||
plugin.skipSignatureValidation = true;
|
|
||||||
}
|
|
||||||
return plugin.install(true, false).then(() => {
|
|
||||||
this.plugins.push(plugin);
|
|
||||||
this.emit('change');
|
|
||||||
this.saveState();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
this.emit('change');
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
installIfNew(url, expectedManifest, skipSignatureValidation) {
|
|
||||||
const plugin = this.plugins.find((p) => p.url === url);
|
|
||||||
if (plugin && plugin.status !== 'invalid') {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
return this.install(url, expectedManifest, skipSignatureValidation);
|
|
||||||
}
|
|
||||||
|
|
||||||
uninstall(id) {
|
|
||||||
const plugin = this.plugins.get(id);
|
|
||||||
if (!plugin) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
this.emit('change');
|
|
||||||
return plugin.uninstall().then(() => {
|
|
||||||
this.plugins.remove(id);
|
|
||||||
this.emit('change');
|
|
||||||
this.saveState();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
disable(id) {
|
|
||||||
const plugin = this.plugins.get(id);
|
|
||||||
if (!plugin || plugin.status !== PluginStatus.STATUS_ACTIVE) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
this.emit('change');
|
|
||||||
return plugin.disable().then(() => {
|
|
||||||
this.emit('change');
|
|
||||||
this.saveState();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
activate(id) {
|
|
||||||
const plugin = this.plugins.get(id);
|
|
||||||
if (!plugin || plugin.status === PluginStatus.STATUS_ACTIVE) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
this.emit('change');
|
|
||||||
return plugin.install(true, true).then(() => {
|
|
||||||
this.emit('change');
|
|
||||||
this.saveState();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
update(id) {
|
|
||||||
const oldPlugin = this.plugins.get(id);
|
|
||||||
const validStatuses = [
|
|
||||||
PluginStatus.STATUS_ACTIVE,
|
|
||||||
PluginStatus.STATUS_INACTIVE,
|
|
||||||
PluginStatus.STATUS_NONE,
|
|
||||||
PluginStatus.STATUS_ERROR,
|
|
||||||
PluginStatus.STATUS_INVALID
|
|
||||||
];
|
|
||||||
if (!oldPlugin || validStatuses.indexOf(oldPlugin.status) < 0) {
|
|
||||||
return Promise.reject();
|
|
||||||
}
|
|
||||||
const url = oldPlugin.url;
|
|
||||||
this.emit('change');
|
|
||||||
return Plugin.loadFromUrl(url)
|
|
||||||
.then((newPlugin) => {
|
|
||||||
return oldPlugin
|
|
||||||
.update(newPlugin)
|
|
||||||
.then(() => {
|
|
||||||
this.emit('change');
|
|
||||||
this.saveState();
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
this.emit('change');
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
this.emit('change');
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setAutoUpdate(id, enabled) {
|
|
||||||
const plugin = this.plugins.get(id);
|
|
||||||
if (!plugin || plugin.autoUpdate === enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
plugin.setAutoUpdate(enabled);
|
|
||||||
this.emit('change');
|
|
||||||
this.saveState();
|
|
||||||
}
|
|
||||||
|
|
||||||
runAutoUpdate() {
|
|
||||||
const queue = this.plugins.filter((p) => p.autoUpdate).map((p) => p.id);
|
|
||||||
if (!queue.length) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
const anotherVersion = this.autoUpdateAppVersion !== RuntimeInfo.version;
|
|
||||||
const wasLongAgo =
|
|
||||||
!this.autoUpdateDate || Date.now() - this.autoUpdateDate > PluginManager.UpdateInterval;
|
|
||||||
const autoUpdateRequired = anotherVersion || wasLongAgo;
|
|
||||||
if (!autoUpdateRequired) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.info('Auto-updating plugins', queue.join(', '));
|
|
||||||
this.set({
|
|
||||||
autoUpdateAppVersion: RuntimeInfo.version,
|
|
||||||
autoUpdateDate: Date.now()
|
|
||||||
});
|
|
||||||
this.saveState();
|
|
||||||
const updateNext = () => {
|
|
||||||
const pluginId = queue.shift();
|
|
||||||
if (pluginId) {
|
|
||||||
return this.update(pluginId).catch(noop).then(updateNext);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return updateNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
loadPlugin(desc, gallery) {
|
|
||||||
const plugin = new Plugin({
|
|
||||||
manifest: desc.manifest,
|
|
||||||
url: desc.url,
|
|
||||||
autoUpdate: desc.autoUpdate
|
|
||||||
});
|
|
||||||
let enabled = desc.enabled;
|
|
||||||
if (enabled) {
|
|
||||||
const galleryPlugin = gallery
|
|
||||||
? gallery.plugins.find((pl) => pl.manifest.name === desc.manifest.name)
|
|
||||||
: null;
|
|
||||||
const expectedPublicKeys = galleryPlugin
|
|
||||||
? [galleryPlugin.manifest.publicKey]
|
|
||||||
: SignatureVerifier.getPublicKeys();
|
|
||||||
enabled = expectedPublicKeys.includes(desc.manifest.publicKey);
|
|
||||||
}
|
|
||||||
return plugin
|
|
||||||
.install(enabled, true)
|
|
||||||
.then(() => plugin)
|
|
||||||
.catch(() => plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveState() {
|
|
||||||
SettingsStore.save('plugins', {
|
|
||||||
autoUpdateAppVersion: this.autoUpdateAppVersion,
|
|
||||||
autoUpdateDate: this.autoUpdateDate,
|
|
||||||
plugins: this.plugins.map((plugin) => ({
|
|
||||||
manifest: plugin.manifest,
|
|
||||||
url: plugin.url,
|
|
||||||
enabled: plugin.status === 'active',
|
|
||||||
autoUpdate: plugin.autoUpdate
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getStatus(id) {
|
|
||||||
const plugin = this.plugins.get(id);
|
|
||||||
return plugin ? plugin.status : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
getPlugin(id) {
|
|
||||||
return this.plugins.get(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginManager.defineModelProperties({
|
|
||||||
plugins: null,
|
|
||||||
autoUpdateAppVersion: null,
|
|
||||||
autoUpdateDate: null
|
|
||||||
});
|
|
||||||
|
|
||||||
const instance = new PluginManager();
|
|
||||||
|
|
||||||
export { instance as PluginManager };
|
|
|
@ -0,0 +1,189 @@
|
||||||
|
import { Model } from 'util/model';
|
||||||
|
import { RuntimeInfo } from 'const/runtime-info';
|
||||||
|
import { SettingsStore } from 'comp/settings/settings-store';
|
||||||
|
import { Plugin, PluginStatus } from 'plugins/plugin';
|
||||||
|
import { PluginGallery } from 'plugins/plugin-gallery';
|
||||||
|
import { SignatureVerifier } from 'util/data/signature-verifier';
|
||||||
|
import { Logger } from 'util/logger';
|
||||||
|
import { PluginGalleryData, PluginManifest, StoredPlugin, StoredPlugins } from 'plugins/types';
|
||||||
|
import { Timeouts } from 'const/timeouts';
|
||||||
|
|
||||||
|
const logger = new Logger('plugin-mgr');
|
||||||
|
|
||||||
|
class PluginManager extends Model {
|
||||||
|
plugins: Plugin[] = [];
|
||||||
|
autoUpdateAppVersion?: string;
|
||||||
|
autoUpdateDate?: Date;
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
const ts = logger.ts();
|
||||||
|
const storedPlugins = (await SettingsStore.load('plugins')) as StoredPlugins;
|
||||||
|
if (!storedPlugins) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.batchSet(() => {
|
||||||
|
this.autoUpdateAppVersion = storedPlugins.autoUpdateAppVersion;
|
||||||
|
this.autoUpdateDate = storedPlugins.autoUpdateDate
|
||||||
|
? new Date(storedPlugins.autoUpdateDate)
|
||||||
|
: undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!storedPlugins.plugins?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const gallery = await PluginGallery.getCachedGallery();
|
||||||
|
const promises = storedPlugins.plugins.map((plugin) => this.loadPlugin(plugin, gallery));
|
||||||
|
const loadedPlugins = await Promise.all(promises);
|
||||||
|
|
||||||
|
this.plugins = this.plugins.concat(...loadedPlugins);
|
||||||
|
logger.info(`Loaded ${loadedPlugins.length} plugins`, logger.ts(ts));
|
||||||
|
}
|
||||||
|
|
||||||
|
async install(
|
||||||
|
url: string,
|
||||||
|
expectedManifest?: PluginManifest,
|
||||||
|
skipSignatureValidation?: boolean
|
||||||
|
): Promise<void> {
|
||||||
|
const plugin = await Plugin.loadFromUrl(url, expectedManifest);
|
||||||
|
await this.uninstall(plugin.id);
|
||||||
|
if (skipSignatureValidation) {
|
||||||
|
plugin.skipSignatureValidation = true;
|
||||||
|
}
|
||||||
|
await plugin.install(true, false);
|
||||||
|
this.plugins = this.plugins.concat(plugin);
|
||||||
|
await this.saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
installIfNew(url: string, expectedManifest: PluginManifest, skipSignatureValidation: boolean) {
|
||||||
|
const plugin = this.plugins.find((p) => p.url === url);
|
||||||
|
if (plugin && plugin.status !== 'invalid') {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return this.install(url, expectedManifest, skipSignatureValidation);
|
||||||
|
}
|
||||||
|
|
||||||
|
async uninstall(id: string): Promise<void> {
|
||||||
|
const plugin = this.getPlugin(id);
|
||||||
|
if (!plugin) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
await plugin.uninstall();
|
||||||
|
this.plugins = this.plugins.filter((p) => p.id !== id);
|
||||||
|
await this.saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
async disable(id: string): Promise<void> {
|
||||||
|
const plugin = this.getPlugin(id);
|
||||||
|
if (!plugin || plugin.status !== 'active') {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
await plugin.disable();
|
||||||
|
await this.saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
async activate(id: string): Promise<void> {
|
||||||
|
const plugin = this.getPlugin(id);
|
||||||
|
if (!plugin || plugin.status === 'active') {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
await plugin.install(true, true);
|
||||||
|
await this.saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: string): Promise<void> {
|
||||||
|
const oldPlugin = this.getPlugin(id);
|
||||||
|
const validStatuses: PluginStatus[] = ['active', 'inactive', 'error', 'invalid'];
|
||||||
|
if (!oldPlugin || (oldPlugin.status && !validStatuses.includes(oldPlugin.status))) {
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
const url = oldPlugin.url;
|
||||||
|
const newPlugin = await Plugin.loadFromUrl(url);
|
||||||
|
await oldPlugin.update(newPlugin);
|
||||||
|
await this.saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
async setAutoUpdate(id: string, enabled: boolean): Promise<void> {
|
||||||
|
const plugin = this.getPlugin(id);
|
||||||
|
if (!plugin || plugin.autoUpdate === enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
plugin.autoUpdate = enabled;
|
||||||
|
await this.saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
async runAutoUpdate(): Promise<void> {
|
||||||
|
const queue = this.plugins.filter((p) => p.autoUpdate).map((p) => p.id);
|
||||||
|
if (!queue.length) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
const anotherVersion = this.autoUpdateAppVersion !== RuntimeInfo.version;
|
||||||
|
const wasLongAgo =
|
||||||
|
!this.autoUpdateDate ||
|
||||||
|
Date.now() - this.autoUpdateDate.getTime() > Timeouts.PluginsUpdate;
|
||||||
|
const autoUpdateRequired = anotherVersion || wasLongAgo;
|
||||||
|
if (!autoUpdateRequired) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.info('Auto-updating plugins', queue.join(', '));
|
||||||
|
this.batchSet(() => {
|
||||||
|
this.autoUpdateAppVersion = RuntimeInfo.version;
|
||||||
|
this.autoUpdateDate = new Date();
|
||||||
|
});
|
||||||
|
await this.saveState();
|
||||||
|
|
||||||
|
while (queue.length) {
|
||||||
|
const pluginId = queue.shift();
|
||||||
|
if (!pluginId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.update(pluginId);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`Error updating plugin`, pluginId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadPlugin(desc: StoredPlugin, gallery: PluginGalleryData | undefined): Promise<Plugin> {
|
||||||
|
const plugin = new Plugin(desc.url, desc.manifest, desc.autoUpdate);
|
||||||
|
let enabled = desc.enabled;
|
||||||
|
if (enabled) {
|
||||||
|
const galleryPlugin = gallery
|
||||||
|
? gallery.plugins.find((pl) => pl.manifest.name === desc.manifest.name)
|
||||||
|
: null;
|
||||||
|
const expectedPublicKeys = galleryPlugin
|
||||||
|
? [galleryPlugin.manifest.publicKey]
|
||||||
|
: SignatureVerifier.getPublicKeys();
|
||||||
|
enabled = expectedPublicKeys.includes(desc.manifest.publicKey);
|
||||||
|
}
|
||||||
|
return plugin
|
||||||
|
.install(enabled, true)
|
||||||
|
.then(() => plugin)
|
||||||
|
.catch(() => plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveState() {
|
||||||
|
await SettingsStore.save('plugins', {
|
||||||
|
autoUpdateAppVersion: this.autoUpdateAppVersion,
|
||||||
|
autoUpdateDate: this.autoUpdateDate,
|
||||||
|
plugins: this.plugins.map((plugin) => ({
|
||||||
|
manifest: plugin.manifest,
|
||||||
|
url: plugin.url,
|
||||||
|
enabled: plugin.status === 'active',
|
||||||
|
autoUpdate: plugin.autoUpdate
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getStatus(id: string): PluginStatus | undefined {
|
||||||
|
return this.getPlugin(id)?.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlugin(id: string): Plugin | undefined {
|
||||||
|
return this.plugins.find((p) => p.id === id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = new PluginManager();
|
||||||
|
|
||||||
|
export { instance as PluginManager };
|
|
@ -49,7 +49,7 @@ class Plugin extends Model {
|
||||||
resources: Record<string, ArrayBuffer>;
|
resources: Record<string, ArrayBuffer>;
|
||||||
module?: { exports: Record<string, unknown> };
|
module?: { exports: Record<string, unknown> };
|
||||||
|
|
||||||
constructor(url: string, manifest: PluginManifest) {
|
constructor(url: string, manifest: PluginManifest, autoUpdate = false) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
const name = manifest.name;
|
const name = manifest.name;
|
||||||
|
@ -61,6 +61,7 @@ class Plugin extends Model {
|
||||||
this.manifest = manifest;
|
this.manifest = manifest;
|
||||||
this.name = manifest.name;
|
this.name = manifest.name;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
|
this.autoUpdate = autoUpdate;
|
||||||
this.logger = new Logger('plugin', name);
|
this.logger = new Logger('plugin', name);
|
||||||
this.resources = {};
|
this.resources = {};
|
||||||
}
|
}
|
||||||
|
@ -674,7 +675,7 @@ class Plugin extends Model {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async loadFromUrl(url: string, expectedManifest: PluginManifest): Promise<Plugin> {
|
static async loadFromUrl(url: string, expectedManifest?: PluginManifest): Promise<Plugin> {
|
||||||
if (url[url.length - 1] !== '/') {
|
if (url[url.length - 1] !== '/') {
|
||||||
url += '/';
|
url += '/';
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,3 +64,16 @@ export interface PluginSetting {
|
||||||
maxlength?: string;
|
maxlength?: string;
|
||||||
options?: PluginSettingOption[];
|
options?: PluginSettingOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StoredPlugin {
|
||||||
|
manifest: PluginManifest;
|
||||||
|
url: string;
|
||||||
|
enabled: boolean;
|
||||||
|
autoUpdate: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StoredPlugins {
|
||||||
|
autoUpdateAppVersion?: string;
|
||||||
|
autoUpdateDate?: string | number;
|
||||||
|
plugins?: StoredPlugin[];
|
||||||
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ function config(options) {
|
||||||
return {
|
return {
|
||||||
mode,
|
mode,
|
||||||
entry: {
|
entry: {
|
||||||
app: ['babel-helpers', 'bootstrap', 'main.scss']
|
app: ['babel-helpers', 'main', 'main.scss']
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve('.', 'tmp'),
|
path: path.resolve('.', 'tmp'),
|
||||||
|
|
Loading…
Reference in New Issue