diff options
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/plugins/plugin-manager.ts | 102 |
1 files changed, 62 insertions, 40 deletions
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index 8cdeff446..2fa80e878 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts | |||
@@ -15,6 +15,7 @@ import { RegisterHookOptions } from '../../../shared/models/plugins/register-hoo | |||
15 | import { PluginSettingsManager } from '../../../shared/models/plugins/plugin-settings-manager.model' | 15 | import { PluginSettingsManager } from '../../../shared/models/plugins/plugin-settings-manager.model' |
16 | 16 | ||
17 | export interface RegisteredPlugin { | 17 | export interface RegisteredPlugin { |
18 | npmName: string | ||
18 | name: string | 19 | name: string |
19 | version: string | 20 | version: string |
20 | description: string | 21 | description: string |
@@ -34,6 +35,7 @@ export interface RegisteredPlugin { | |||
34 | } | 35 | } |
35 | 36 | ||
36 | export interface HookInformationValue { | 37 | export interface HookInformationValue { |
38 | npmName: string | ||
37 | pluginName: string | 39 | pluginName: string |
38 | handler: Function | 40 | handler: Function |
39 | priority: number | 41 | priority: number |
@@ -52,12 +54,13 @@ export class PluginManager { | |||
52 | 54 | ||
53 | // ###################### Getters ###################### | 55 | // ###################### Getters ###################### |
54 | 56 | ||
55 | getRegisteredPluginOrTheme (name: string) { | 57 | getRegisteredPluginOrTheme (npmName: string) { |
56 | return this.registeredPlugins[name] | 58 | return this.registeredPlugins[npmName] |
57 | } | 59 | } |
58 | 60 | ||
59 | getRegisteredPlugin (name: string) { | 61 | getRegisteredPlugin (name: string) { |
60 | const registered = this.getRegisteredPluginOrTheme(name) | 62 | const npmName = PluginModel.buildNpmName(name, PluginType.PLUGIN) |
63 | const registered = this.getRegisteredPluginOrTheme(npmName) | ||
61 | 64 | ||
62 | if (!registered || registered.type !== PluginType.PLUGIN) return undefined | 65 | if (!registered || registered.type !== PluginType.PLUGIN) return undefined |
63 | 66 | ||
@@ -65,7 +68,8 @@ export class PluginManager { | |||
65 | } | 68 | } |
66 | 69 | ||
67 | getRegisteredTheme (name: string) { | 70 | getRegisteredTheme (name: string) { |
68 | const registered = this.getRegisteredPluginOrTheme(name) | 71 | const npmName = PluginModel.buildNpmName(name, PluginType.THEME) |
72 | const registered = this.getRegisteredPluginOrTheme(npmName) | ||
69 | 73 | ||
70 | if (!registered || registered.type !== PluginType.THEME) return undefined | 74 | if (!registered || registered.type !== PluginType.THEME) return undefined |
71 | 75 | ||
@@ -80,8 +84,8 @@ export class PluginManager { | |||
80 | return this.getRegisteredPluginsOrThemes(PluginType.THEME) | 84 | return this.getRegisteredPluginsOrThemes(PluginType.THEME) |
81 | } | 85 | } |
82 | 86 | ||
83 | getSettings (name: string) { | 87 | getRegisteredSettings (npmName: string) { |
84 | return this.settings[name] || [] | 88 | return this.settings[npmName] || [] |
85 | } | 89 | } |
86 | 90 | ||
87 | // ###################### Hooks ###################### | 91 | // ###################### Hooks ###################### |
@@ -126,35 +130,36 @@ export class PluginManager { | |||
126 | this.sortHooksByPriority() | 130 | this.sortHooksByPriority() |
127 | } | 131 | } |
128 | 132 | ||
129 | async unregister (name: string) { | 133 | // Don't need the plugin type since themes cannot register server code |
130 | const plugin = this.getRegisteredPlugin(name) | 134 | async unregister (npmName: string) { |
135 | logger.info('Unregister plugin %s.', npmName) | ||
136 | |||
137 | const plugin = this.getRegisteredPluginOrTheme(npmName) | ||
131 | 138 | ||
132 | if (!plugin) { | 139 | if (!plugin) { |
133 | throw new Error(`Unknown plugin ${name} to unregister`) | 140 | throw new Error(`Unknown plugin ${npmName} to unregister`) |
134 | } | 141 | } |
135 | 142 | ||
136 | if (plugin.type === PluginType.THEME) { | 143 | if (plugin.type === PluginType.PLUGIN) { |
137 | throw new Error(`Cannot unregister ${name}: this is a theme`) | 144 | await plugin.unregister() |
138 | } | ||
139 | 145 | ||
140 | await plugin.unregister() | 146 | // Remove hooks of this plugin |
147 | for (const key of Object.keys(this.hooks)) { | ||
148 | this.hooks[key] = this.hooks[key].filter(h => h.pluginName !== npmName) | ||
149 | } | ||
141 | 150 | ||
142 | // Remove hooks of this plugin | 151 | logger.info('Regenerating registered plugin CSS to global file.') |
143 | for (const key of Object.keys(this.hooks)) { | 152 | await this.regeneratePluginGlobalCSS() |
144 | this.hooks[key] = this.hooks[key].filter(h => h.pluginName !== name) | ||
145 | } | 153 | } |
146 | 154 | ||
147 | delete this.registeredPlugins[plugin.name] | 155 | delete this.registeredPlugins[plugin.npmName] |
148 | |||
149 | logger.info('Regenerating registered plugin CSS to global file.') | ||
150 | await this.regeneratePluginGlobalCSS() | ||
151 | } | 156 | } |
152 | 157 | ||
153 | // ###################### Installation ###################### | 158 | // ###################### Installation ###################### |
154 | 159 | ||
155 | async install (toInstall: string, version?: string, fromDisk = false) { | 160 | async install (toInstall: string, version?: string, fromDisk = false) { |
156 | let plugin: PluginModel | 161 | let plugin: PluginModel |
157 | let name: string | 162 | let npmName: string |
158 | 163 | ||
159 | logger.info('Installing plugin %s.', toInstall) | 164 | logger.info('Installing plugin %s.', toInstall) |
160 | 165 | ||
@@ -163,9 +168,9 @@ export class PluginManager { | |||
163 | ? await installNpmPluginFromDisk(toInstall) | 168 | ? await installNpmPluginFromDisk(toInstall) |
164 | : await installNpmPlugin(toInstall, version) | 169 | : await installNpmPlugin(toInstall, version) |
165 | 170 | ||
166 | name = fromDisk ? basename(toInstall) : toInstall | 171 | npmName = fromDisk ? basename(toInstall) : toInstall |
167 | const pluginType = PluginModel.getTypeFromNpmName(name) | 172 | const pluginType = PluginModel.getTypeFromNpmName(npmName) |
168 | const pluginName = PluginModel.normalizePluginName(name) | 173 | const pluginName = PluginModel.normalizePluginName(npmName) |
169 | 174 | ||
170 | const packageJSON = this.getPackageJSON(pluginName, pluginType) | 175 | const packageJSON = this.getPackageJSON(pluginName, pluginType) |
171 | if (!isPackageJSONValid(packageJSON, pluginType)) { | 176 | if (!isPackageJSONValid(packageJSON, pluginType)) { |
@@ -186,7 +191,7 @@ export class PluginManager { | |||
186 | logger.error('Cannot install plugin %s, removing it...', toInstall, { err }) | 191 | logger.error('Cannot install plugin %s, removing it...', toInstall, { err }) |
187 | 192 | ||
188 | try { | 193 | try { |
189 | await removeNpmPlugin(name) | 194 | await removeNpmPlugin(npmName) |
190 | } catch (err) { | 195 | } catch (err) { |
191 | logger.error('Cannot remove plugin %s after failed installation.', toInstall, { err }) | 196 | logger.error('Cannot remove plugin %s after failed installation.', toInstall, { err }) |
192 | } | 197 | } |
@@ -197,17 +202,28 @@ export class PluginManager { | |||
197 | logger.info('Successful installation of plugin %s.', toInstall) | 202 | logger.info('Successful installation of plugin %s.', toInstall) |
198 | 203 | ||
199 | await this.registerPluginOrTheme(plugin) | 204 | await this.registerPluginOrTheme(plugin) |
205 | |||
206 | return plugin | ||
207 | } | ||
208 | |||
209 | async update (toUpdate: string, version?: string, fromDisk = false) { | ||
210 | const npmName = fromDisk ? basename(toUpdate) : toUpdate | ||
211 | |||
212 | logger.info('Updating plugin %s.', npmName) | ||
213 | |||
214 | // Unregister old hooks | ||
215 | await this.unregister(npmName) | ||
216 | |||
217 | return this.install(toUpdate, version, fromDisk) | ||
200 | } | 218 | } |
201 | 219 | ||
202 | async uninstall (npmName: string) { | 220 | async uninstall (npmName: string) { |
203 | logger.info('Uninstalling plugin %s.', npmName) | 221 | logger.info('Uninstalling plugin %s.', npmName) |
204 | 222 | ||
205 | const pluginName = PluginModel.normalizePluginName(npmName) | ||
206 | |||
207 | try { | 223 | try { |
208 | await this.unregister(pluginName) | 224 | await this.unregister(npmName) |
209 | } catch (err) { | 225 | } catch (err) { |
210 | logger.warn('Cannot unregister plugin %s.', pluginName, { err }) | 226 | logger.warn('Cannot unregister plugin %s.', npmName, { err }) |
211 | } | 227 | } |
212 | 228 | ||
213 | const plugin = await PluginModel.loadByNpmName(npmName) | 229 | const plugin = await PluginModel.loadByNpmName(npmName) |
@@ -229,7 +245,9 @@ export class PluginManager { | |||
229 | // ###################### Private register ###################### | 245 | // ###################### Private register ###################### |
230 | 246 | ||
231 | private async registerPluginOrTheme (plugin: PluginModel) { | 247 | private async registerPluginOrTheme (plugin: PluginModel) { |
232 | logger.info('Registering plugin or theme %s.', plugin.name) | 248 | const npmName = PluginModel.buildNpmName(plugin.name, plugin.type) |
249 | |||
250 | logger.info('Registering plugin or theme %s.', npmName) | ||
233 | 251 | ||
234 | const packageJSON = this.getPackageJSON(plugin.name, plugin.type) | 252 | const packageJSON = this.getPackageJSON(plugin.name, plugin.type) |
235 | const pluginPath = this.getPluginPath(plugin.name, plugin.type) | 253 | const pluginPath = this.getPluginPath(plugin.name, plugin.type) |
@@ -248,7 +266,8 @@ export class PluginManager { | |||
248 | clientScripts[c.script] = c | 266 | clientScripts[c.script] = c |
249 | } | 267 | } |
250 | 268 | ||
251 | this.registeredPlugins[ plugin.name ] = { | 269 | this.registeredPlugins[ npmName ] = { |
270 | npmName, | ||
252 | name: plugin.name, | 271 | name: plugin.name, |
253 | type: plugin.type, | 272 | type: plugin.type, |
254 | version: plugin.version, | 273 | version: plugin.version, |
@@ -263,10 +282,13 @@ export class PluginManager { | |||
263 | } | 282 | } |
264 | 283 | ||
265 | private async registerPlugin (plugin: PluginModel, pluginPath: string, packageJSON: PluginPackageJson) { | 284 | private async registerPlugin (plugin: PluginModel, pluginPath: string, packageJSON: PluginPackageJson) { |
285 | const npmName = PluginModel.buildNpmName(plugin.name, plugin.type) | ||
286 | |||
266 | const registerHook = (options: RegisterHookOptions) => { | 287 | const registerHook = (options: RegisterHookOptions) => { |
267 | if (!this.hooks[options.target]) this.hooks[options.target] = [] | 288 | if (!this.hooks[options.target]) this.hooks[options.target] = [] |
268 | 289 | ||
269 | this.hooks[options.target].push({ | 290 | this.hooks[options.target].push({ |
291 | npmName, | ||
270 | pluginName: plugin.name, | 292 | pluginName: plugin.name, |
271 | handler: options.handler, | 293 | handler: options.handler, |
272 | priority: options.priority || 0 | 294 | priority: options.priority || 0 |
@@ -274,15 +296,15 @@ export class PluginManager { | |||
274 | } | 296 | } |
275 | 297 | ||
276 | const registerSetting = (options: RegisterSettingOptions) => { | 298 | const registerSetting = (options: RegisterSettingOptions) => { |
277 | if (!this.settings[plugin.name]) this.settings[plugin.name] = [] | 299 | if (!this.settings[npmName]) this.settings[npmName] = [] |
278 | 300 | ||
279 | this.settings[plugin.name].push(options) | 301 | this.settings[npmName].push(options) |
280 | } | 302 | } |
281 | 303 | ||
282 | const settingsManager: PluginSettingsManager = { | 304 | const settingsManager: PluginSettingsManager = { |
283 | getSetting: (name: string) => PluginModel.getSetting(plugin.name, name), | 305 | getSetting: (name: string) => PluginModel.getSetting(plugin.name, plugin.type, name), |
284 | 306 | ||
285 | setSetting: (name: string, value: string) => PluginModel.setSetting(plugin.name, name, value) | 307 | setSetting: (name: string, value: string) => PluginModel.setSetting(plugin.name, plugin.type, name, value) |
286 | } | 308 | } |
287 | 309 | ||
288 | const library: PluginLibrary = require(join(pluginPath, packageJSON.library)) | 310 | const library: PluginLibrary = require(join(pluginPath, packageJSON.library)) |
@@ -293,7 +315,7 @@ export class PluginManager { | |||
293 | 315 | ||
294 | library.register({ registerHook, registerSetting, settingsManager }) | 316 | library.register({ registerHook, registerSetting, settingsManager }) |
295 | 317 | ||
296 | logger.info('Add plugin %s CSS to global file.', plugin.name) | 318 | logger.info('Add plugin %s CSS to global file.', npmName) |
297 | 319 | ||
298 | await this.addCSSToGlobalFile(pluginPath, packageJSON.css) | 320 | await this.addCSSToGlobalFile(pluginPath, packageJSON.css) |
299 | 321 | ||
@@ -351,9 +373,9 @@ export class PluginManager { | |||
351 | } | 373 | } |
352 | 374 | ||
353 | private getPluginPath (pluginName: string, pluginType: PluginType) { | 375 | private getPluginPath (pluginName: string, pluginType: PluginType) { |
354 | const prefix = pluginType === PluginType.PLUGIN ? 'peertube-plugin-' : 'peertube-theme-' | 376 | const npmName = PluginModel.buildNpmName(pluginName, pluginType) |
355 | 377 | ||
356 | return join(CONFIG.STORAGE.PLUGINS_DIR, 'node_modules', prefix + pluginName) | 378 | return join(CONFIG.STORAGE.PLUGINS_DIR, 'node_modules', npmName) |
357 | } | 379 | } |
358 | 380 | ||
359 | // ###################### Private getters ###################### | 381 | // ###################### Private getters ###################### |
@@ -361,8 +383,8 @@ export class PluginManager { | |||
361 | private getRegisteredPluginsOrThemes (type: PluginType) { | 383 | private getRegisteredPluginsOrThemes (type: PluginType) { |
362 | const plugins: RegisteredPlugin[] = [] | 384 | const plugins: RegisteredPlugin[] = [] |
363 | 385 | ||
364 | for (const pluginName of Object.keys(this.registeredPlugins)) { | 386 | for (const npmName of Object.keys(this.registeredPlugins)) { |
365 | const plugin = this.registeredPlugins[ pluginName ] | 387 | const plugin = this.registeredPlugins[ npmName ] |
366 | if (plugin.type !== type) continue | 388 | if (plugin.type !== type) continue |
367 | 389 | ||
368 | plugins.push(plugin) | 390 | plugins.push(plugin) |