diff options
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/plugins/plugin-manager.ts | 111 | ||||
-rw-r--r-- | server/lib/plugins/yarn.ts | 8 |
2 files changed, 79 insertions, 40 deletions
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index 8496979f8..3d8375acd 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import { PluginModel } from '../../models/server/plugin' | 1 | import { PluginModel } from '../../models/server/plugin' |
2 | import { logger } from '../../helpers/logger' | 2 | import { logger } from '../../helpers/logger' |
3 | import { RegisterHookOptions } from '../../../shared/models/plugins/register.model' | ||
4 | import { basename, join } from 'path' | 3 | import { basename, join } from 'path' |
5 | import { CONFIG } from '../../initializers/config' | 4 | import { CONFIG } from '../../initializers/config' |
6 | import { isLibraryCodeValid, isPackageJSONValid } from '../../helpers/custom-validators/plugins' | 5 | import { isLibraryCodeValid, isPackageJSONValid } from '../../helpers/custom-validators/plugins' |
@@ -11,7 +10,9 @@ import { PLUGIN_GLOBAL_CSS_PATH } from '../../initializers/constants' | |||
11 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | 10 | import { PluginType } from '../../../shared/models/plugins/plugin.type' |
12 | import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' | 11 | import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' |
13 | import { outputFile } from 'fs-extra' | 12 | import { outputFile } from 'fs-extra' |
14 | import { ServerConfigPlugin } from '../../../shared/models/server' | 13 | import { RegisterSettingOptions } from '../../../shared/models/plugins/register-setting.model' |
14 | import { RegisterHookOptions } from '../../../shared/models/plugins/register-hook.model' | ||
15 | import { PluginSettingsManager } from '../../../shared/models/plugins/plugin-settings-manager.model' | ||
15 | 16 | ||
16 | export interface RegisteredPlugin { | 17 | export interface RegisteredPlugin { |
17 | name: string | 18 | name: string |
@@ -43,26 +44,13 @@ export class PluginManager { | |||
43 | private static instance: PluginManager | 44 | private static instance: PluginManager |
44 | 45 | ||
45 | private registeredPlugins: { [ name: string ]: RegisteredPlugin } = {} | 46 | private registeredPlugins: { [ name: string ]: RegisteredPlugin } = {} |
47 | private settings: { [ name: string ]: RegisterSettingOptions[] } = {} | ||
46 | private hooks: { [ name: string ]: HookInformationValue[] } = {} | 48 | private hooks: { [ name: string ]: HookInformationValue[] } = {} |
47 | 49 | ||
48 | private constructor () { | 50 | private constructor () { |
49 | } | 51 | } |
50 | 52 | ||
51 | async registerPluginsAndThemes () { | 53 | // ###################### Getters ###################### |
52 | await this.resetCSSGlobalFile() | ||
53 | |||
54 | const plugins = await PluginModel.listEnabledPluginsAndThemes() | ||
55 | |||
56 | for (const plugin of plugins) { | ||
57 | try { | ||
58 | await this.registerPluginOrTheme(plugin) | ||
59 | } catch (err) { | ||
60 | logger.error('Cannot register plugin %s, skipping.', plugin.name, { err }) | ||
61 | } | ||
62 | } | ||
63 | |||
64 | this.sortHooksByPriority() | ||
65 | } | ||
66 | 54 | ||
67 | getRegisteredPluginOrTheme (name: string) { | 55 | getRegisteredPluginOrTheme (name: string) { |
68 | return this.registeredPlugins[name] | 56 | return this.registeredPlugins[name] |
@@ -92,6 +80,12 @@ export class PluginManager { | |||
92 | return this.getRegisteredPluginsOrThemes(PluginType.THEME) | 80 | return this.getRegisteredPluginsOrThemes(PluginType.THEME) |
93 | } | 81 | } |
94 | 82 | ||
83 | getSettings (name: string) { | ||
84 | return this.settings[name] || [] | ||
85 | } | ||
86 | |||
87 | // ###################### Hooks ###################### | ||
88 | |||
95 | async runHook (hookName: string, param?: any) { | 89 | async runHook (hookName: string, param?: any) { |
96 | let result = param | 90 | let result = param |
97 | 91 | ||
@@ -99,8 +93,11 @@ export class PluginManager { | |||
99 | 93 | ||
100 | for (const hook of this.hooks[hookName]) { | 94 | for (const hook of this.hooks[hookName]) { |
101 | try { | 95 | try { |
102 | if (wait) result = await hook.handler(param) | 96 | if (wait) { |
103 | else result = hook.handler() | 97 | result = await hook.handler(param) |
98 | } else { | ||
99 | result = hook.handler() | ||
100 | } | ||
104 | } catch (err) { | 101 | } catch (err) { |
105 | logger.error('Cannot run hook %s of plugin %s.', hookName, hook.pluginName, { err }) | 102 | logger.error('Cannot run hook %s of plugin %s.', hookName, hook.pluginName, { err }) |
106 | } | 103 | } |
@@ -109,6 +106,24 @@ export class PluginManager { | |||
109 | return result | 106 | return result |
110 | } | 107 | } |
111 | 108 | ||
109 | // ###################### Registration ###################### | ||
110 | |||
111 | async registerPluginsAndThemes () { | ||
112 | await this.resetCSSGlobalFile() | ||
113 | |||
114 | const plugins = await PluginModel.listEnabledPluginsAndThemes() | ||
115 | |||
116 | for (const plugin of plugins) { | ||
117 | try { | ||
118 | await this.registerPluginOrTheme(plugin) | ||
119 | } catch (err) { | ||
120 | logger.error('Cannot register plugin %s, skipping.', plugin.name, { err }) | ||
121 | } | ||
122 | } | ||
123 | |||
124 | this.sortHooksByPriority() | ||
125 | } | ||
126 | |||
112 | async unregister (name: string) { | 127 | async unregister (name: string) { |
113 | const plugin = this.getRegisteredPlugin(name) | 128 | const plugin = this.getRegisteredPlugin(name) |
114 | 129 | ||
@@ -133,7 +148,9 @@ export class PluginManager { | |||
133 | await this.regeneratePluginGlobalCSS() | 148 | await this.regeneratePluginGlobalCSS() |
134 | } | 149 | } |
135 | 150 | ||
136 | async install (toInstall: string, version: string, fromDisk = false) { | 151 | // ###################### Installation ###################### |
152 | |||
153 | async install (toInstall: string, version?: string, fromDisk = false) { | ||
137 | let plugin: PluginModel | 154 | let plugin: PluginModel |
138 | let name: string | 155 | let name: string |
139 | 156 | ||
@@ -206,6 +223,8 @@ export class PluginManager { | |||
206 | logger.info('Plugin %s uninstalled.', packageName) | 223 | logger.info('Plugin %s uninstalled.', packageName) |
207 | } | 224 | } |
208 | 225 | ||
226 | // ###################### Private register ###################### | ||
227 | |||
209 | private async registerPluginOrTheme (plugin: PluginModel) { | 228 | private async registerPluginOrTheme (plugin: PluginModel) { |
210 | logger.info('Registering plugin or theme %s.', plugin.name) | 229 | logger.info('Registering plugin or theme %s.', plugin.name) |
211 | 230 | ||
@@ -251,13 +270,25 @@ export class PluginManager { | |||
251 | }) | 270 | }) |
252 | } | 271 | } |
253 | 272 | ||
273 | const registerSetting = (options: RegisterSettingOptions) => { | ||
274 | if (!this.settings[plugin.name]) this.settings[plugin.name] = [] | ||
275 | |||
276 | this.settings[plugin.name].push(options) | ||
277 | } | ||
278 | |||
279 | const settingsManager: PluginSettingsManager = { | ||
280 | getSetting: (name: string) => PluginModel.getSetting(plugin.name, name), | ||
281 | |||
282 | setSetting: (name: string, value: string) => PluginModel.setSetting(plugin.name, name, value) | ||
283 | } | ||
284 | |||
254 | const library: PluginLibrary = require(join(pluginPath, packageJSON.library)) | 285 | const library: PluginLibrary = require(join(pluginPath, packageJSON.library)) |
255 | 286 | ||
256 | if (!isLibraryCodeValid(library)) { | 287 | if (!isLibraryCodeValid(library)) { |
257 | throw new Error('Library code is not valid (miss register or unregister function)') | 288 | throw new Error('Library code is not valid (miss register or unregister function)') |
258 | } | 289 | } |
259 | 290 | ||
260 | library.register({ registerHook }) | 291 | library.register({ registerHook, registerSetting, settingsManager }) |
261 | 292 | ||
262 | logger.info('Add plugin %s CSS to global file.', plugin.name) | 293 | logger.info('Add plugin %s CSS to global file.', plugin.name) |
263 | 294 | ||
@@ -266,13 +297,7 @@ export class PluginManager { | |||
266 | return library | 297 | return library |
267 | } | 298 | } |
268 | 299 | ||
269 | private sortHooksByPriority () { | 300 | // ###################### CSS ###################### |
270 | for (const hookName of Object.keys(this.hooks)) { | ||
271 | this.hooks[hookName].sort((a, b) => { | ||
272 | return b.priority - a.priority | ||
273 | }) | ||
274 | } | ||
275 | } | ||
276 | 301 | ||
277 | private resetCSSGlobalFile () { | 302 | private resetCSSGlobalFile () { |
278 | return outputFile(PLUGIN_GLOBAL_CSS_PATH, '') | 303 | return outputFile(PLUGIN_GLOBAL_CSS_PATH, '') |
@@ -296,6 +321,26 @@ export class PluginManager { | |||
296 | }) | 321 | }) |
297 | } | 322 | } |
298 | 323 | ||
324 | private async regeneratePluginGlobalCSS () { | ||
325 | await this.resetCSSGlobalFile() | ||
326 | |||
327 | for (const key of Object.keys(this.registeredPlugins)) { | ||
328 | const plugin = this.registeredPlugins[key] | ||
329 | |||
330 | await this.addCSSToGlobalFile(plugin.path, plugin.css) | ||
331 | } | ||
332 | } | ||
333 | |||
334 | // ###################### Utils ###################### | ||
335 | |||
336 | private sortHooksByPriority () { | ||
337 | for (const hookName of Object.keys(this.hooks)) { | ||
338 | this.hooks[hookName].sort((a, b) => { | ||
339 | return b.priority - a.priority | ||
340 | }) | ||
341 | } | ||
342 | } | ||
343 | |||
299 | private getPackageJSON (pluginName: string, pluginType: PluginType) { | 344 | private getPackageJSON (pluginName: string, pluginType: PluginType) { |
300 | const pluginPath = join(this.getPluginPath(pluginName, pluginType), 'package.json') | 345 | const pluginPath = join(this.getPluginPath(pluginName, pluginType), 'package.json') |
301 | 346 | ||
@@ -312,15 +357,7 @@ export class PluginManager { | |||
312 | return name.replace(/^peertube-((theme)|(plugin))-/, '') | 357 | return name.replace(/^peertube-((theme)|(plugin))-/, '') |
313 | } | 358 | } |
314 | 359 | ||
315 | private async regeneratePluginGlobalCSS () { | 360 | // ###################### Private getters ###################### |
316 | await this.resetCSSGlobalFile() | ||
317 | |||
318 | for (const key of Object.keys(this.registeredPlugins)) { | ||
319 | const plugin = this.registeredPlugins[key] | ||
320 | |||
321 | await this.addCSSToGlobalFile(plugin.path, plugin.css) | ||
322 | } | ||
323 | } | ||
324 | 361 | ||
325 | private getRegisteredPluginsOrThemes (type: PluginType) { | 362 | private getRegisteredPluginsOrThemes (type: PluginType) { |
326 | const plugins: RegisteredPlugin[] = [] | 363 | const plugins: RegisteredPlugin[] = [] |
diff --git a/server/lib/plugins/yarn.ts b/server/lib/plugins/yarn.ts index 35fe1625f..5fe1c5046 100644 --- a/server/lib/plugins/yarn.ts +++ b/server/lib/plugins/yarn.ts | |||
@@ -5,12 +5,14 @@ import { CONFIG } from '../../initializers/config' | |||
5 | import { outputJSON, pathExists } from 'fs-extra' | 5 | import { outputJSON, pathExists } from 'fs-extra' |
6 | import { join } from 'path' | 6 | import { join } from 'path' |
7 | 7 | ||
8 | async function installNpmPlugin (name: string, version: string) { | 8 | async function installNpmPlugin (name: string, version?: string) { |
9 | // Security check | 9 | // Security check |
10 | checkNpmPluginNameOrThrow(name) | 10 | checkNpmPluginNameOrThrow(name) |
11 | checkPluginVersionOrThrow(version) | 11 | if (version) checkPluginVersionOrThrow(version) |
12 | |||
13 | let toInstall = name | ||
14 | if (version) toInstall += `@${version}` | ||
12 | 15 | ||
13 | const toInstall = `${name}@${version}` | ||
14 | await execYarn('add ' + toInstall) | 16 | await execYarn('add ' + toInstall) |
15 | } | 17 | } |
16 | 18 | ||