aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib/plugins/plugin-manager.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/lib/plugins/plugin-manager.ts')
-rw-r--r--server/lib/plugins/plugin-manager.ts102
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
15import { PluginSettingsManager } from '../../../shared/models/plugins/plugin-settings-manager.model' 15import { PluginSettingsManager } from '../../../shared/models/plugins/plugin-settings-manager.model'
16 16
17export interface RegisteredPlugin { 17export 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
36export interface HookInformationValue { 37export 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)