]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
add client hook filter:videojs.options
authorkontrollanten <6680299+kontrollanten@users.noreply.github.com>
Sat, 15 May 2021 04:30:24 +0000 (06:30 +0200)
committerChocobozzz <chocobozzz@cpy.re>
Mon, 14 Jun 2021 11:35:48 +0000 (13:35 +0200)
closes #4086

client/src/app/+videos/+video-watch/video-watch.component.ts
client/src/app/core/plugins/plugin.service.ts
client/src/app/core/theme/theme.service.ts
client/src/assets/player/peertube-player-manager.ts
client/src/root-helpers/index.ts
client/src/root-helpers/plugins-manager.ts [new file with mode: 0644]
client/src/root-helpers/plugins.ts [deleted file]
client/src/standalone/videos/embed.ts
shared/models/plugins/client/client-hook.model.ts

index 3fdbc018407bf729f85639f2d8fec1e9bdf455dc..a444dc51f8feea704e5b1b11423e7bf163ab5c4f 100644 (file)
@@ -12,6 +12,7 @@ import {
   MetaService,
   Notifier,
   PeerTubeSocket,
+  PluginService,
   RestExtractor,
   ScreenService,
   ServerService,
@@ -146,6 +147,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     private videoCaptionService: VideoCaptionService,
     private hotkeysService: HotkeysService,
     private hooks: HooksService,
+    private pluginService: PluginService,
     private peertubeSocket: PeerTubeSocket,
     private screenService: ScreenService,
     private location: PlatformLocation,
@@ -859,7 +861,9 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
 
       webtorrent: {
         videoFiles: video.files
-      }
+      },
+
+      pluginsManager: this.pluginService.getPluginsManager()
     }
 
     // Only set this if we're in a playlist
index ccbfd3e4db5d903fa151d91629454b2ee6a16c65..bfd5ba4ccd6629f4c8462e9149a3d4bca84979a7 100644 (file)
@@ -1,6 +1,5 @@
-import * as debug from 'debug'
-import { Observable, of, ReplaySubject } from 'rxjs'
-import { catchError, first, map, shareReplay } from 'rxjs/operators'
+import { Observable, of } from 'rxjs'
+import { catchError, map, shareReplay } from 'rxjs/operators'
 import { HttpClient } from '@angular/common/http'
 import { Inject, Injectable, LOCALE_ID, NgZone } from '@angular/core'
 import { VideoEditType } from '@app/+videos/+video-edit/shared/video-edit.type'
@@ -11,7 +10,7 @@ import { RestExtractor } from '@app/core/rest'
 import { ServerService } from '@app/core/server/server.service'
 import { getDevLocale, isOnDevLocale } from '@app/helpers'
 import { CustomModalComponent } from '@app/modal/custom-modal.component'
-import { FormFields, Hooks, loadPlugin, PluginInfo, runHook } from '@root-helpers/plugins'
+import { PluginInfo, PluginsManager } from '@root-helpers/plugins-manager'
 import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n'
 import {
   ClientHook,
@@ -20,49 +19,39 @@ import {
   PluginTranslation,
   PluginType,
   PublicServerSetting,
+  RegisterClientFormFieldOptions,
   RegisterClientSettingsScript,
+  RegisterClientVideoFieldOptions,
   ServerConfigPlugin
 } from '@shared/models'
 import { environment } from '../../../environments/environment'
 import { RegisterClientHelpers } from '../../../types/register-client-option.model'
 
-const logger = debug('peertube:plugins')
+type FormFields = {
+  video: {
+    commonOptions: RegisterClientFormFieldOptions
+    videoFormOptions: RegisterClientVideoFieldOptions
+  }[]
+}
 
 @Injectable()
 export class PluginService implements ClientHook {
   private static BASE_PLUGIN_API_URL = environment.apiUrl + '/api/v1/plugins'
   private static BASE_PLUGIN_URL = environment.apiUrl + '/plugins'
 
-  pluginsLoaded: { [ scope in PluginClientScope ]: ReplaySubject<boolean> } = {
-    common: new ReplaySubject<boolean>(1),
-    'admin-plugin': new ReplaySubject<boolean>(1),
-    search: new ReplaySubject<boolean>(1),
-    'video-watch': new ReplaySubject<boolean>(1),
-    signup: new ReplaySubject<boolean>(1),
-    login: new ReplaySubject<boolean>(1),
-    'video-edit': new ReplaySubject<boolean>(1),
-    embed: new ReplaySubject<boolean>(1)
-  }
-
   translationsObservable: Observable<PluginTranslation>
 
   customModal: CustomModalComponent
 
-  private plugins: ServerConfigPlugin[] = []
   private helpers: { [ npmName: string ]: RegisterClientHelpers } = {}
 
-  private scopes: { [ scopeName: string ]: PluginInfo[] } = {}
-
-  private loadedScripts: { [ script: string ]: boolean } = {}
-  private loadedScopes: PluginClientScope[] = []
-  private loadingScopes: { [id in PluginClientScope]?: boolean } = {}
-
-  private hooks: Hooks = {}
   private formFields: FormFields = {
     video: []
   }
   private settingsScripts: { [ npmName: string ]: RegisterClientSettingsScript } = {}
 
+  private pluginsManager: PluginsManager
+
   constructor (
     private authService: AuthService,
     private notifier: Notifier,
@@ -74,111 +63,48 @@ export class PluginService implements ClientHook {
     @Inject(LOCALE_ID) private localeId: string
   ) {
     this.loadTranslations()
+
+    this.pluginsManager = new PluginsManager({
+      peertubeHelpersFactory: this.buildPeerTubeHelpers.bind(this),
+      onFormFields: this.onFormFields.bind(this),
+      onSettingsScripts: this.onSettingsScripts.bind(this)
+    })
   }
 
   initializePlugins () {
-    const config = this.server.getHTMLConfig()
-    this.plugins = config.plugin.registered
-
-    this.buildScopeStruct()
+    this.pluginsManager.loadPluginsList(this.server.getHTMLConfig())
 
-    this.ensurePluginsAreLoaded('common')
+    this.pluginsManager.ensurePluginsAreLoaded('common')
   }
 
   initializeCustomModal (customModal: CustomModalComponent) {
     this.customModal = customModal
   }
 
-  ensurePluginsAreLoaded (scope: PluginClientScope) {
-    this.loadPluginsByScope(scope)
-
-    return this.pluginsLoaded[scope].asObservable()
-               .pipe(first(), shareReplay())
-               .toPromise()
+  runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> {
+    return this.zone.runOutsideAngular(() => {
+      return this.pluginsManager.runHook(hookName, result, params)
+    })
   }
 
-  addPlugin (plugin: ServerConfigPlugin, isTheme = false) {
-    const pathPrefix = this.getPluginPathPrefix(isTheme)
-
-    for (const key of Object.keys(plugin.clientScripts)) {
-      const clientScript = plugin.clientScripts[key]
-
-      for (const scope of clientScript.scopes) {
-        if (!this.scopes[scope]) this.scopes[scope] = []
-
-        this.scopes[scope].push({
-          plugin,
-          clientScript: {
-            script: `${pathPrefix}/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}`,
-            scopes: clientScript.scopes
-          },
-          pluginType: isTheme ? PluginType.THEME : PluginType.PLUGIN,
-          isTheme
-        })
-
-        this.loadedScripts[clientScript.script] = false
-      }
-    }
+  ensurePluginsAreLoaded (scope: PluginClientScope) {
+    return this.pluginsManager.ensurePluginsAreLoaded(scope)
   }
 
-  removePlugin (plugin: ServerConfigPlugin) {
-    for (const key of Object.keys(this.scopes)) {
-      this.scopes[key] = this.scopes[key].filter(o => o.plugin.name !== plugin.name)
-    }
+  reloadLoadedScopes () {
+    return this.pluginsManager.reloadLoadedScopes()
   }
 
-  async reloadLoadedScopes () {
-    for (const scope of this.loadedScopes) {
-      await this.loadPluginsByScope(scope, true)
-    }
+  getPluginsManager () {
+    return this.pluginsManager
   }
 
-  async loadPluginsByScope (scope: PluginClientScope, isReload = false) {
-    if (this.loadingScopes[scope]) return
-    if (!isReload && this.loadedScopes.includes(scope)) return
-
-    this.loadingScopes[scope] = true
-
-    logger('Loading scope %s', scope)
-
-    try {
-      if (!isReload) this.loadedScopes.push(scope)
-
-      const toLoad = this.scopes[ scope ]
-      if (!Array.isArray(toLoad)) {
-        this.loadingScopes[scope] = false
-        this.pluginsLoaded[scope].next(true)
-
-        logger('Nothing to load for scope %s', scope)
-        return
-      }
-
-      const promises: Promise<any>[] = []
-      for (const pluginInfo of toLoad) {
-        const clientScript = pluginInfo.clientScript
-
-        if (this.loadedScripts[ clientScript.script ]) continue
-
-        promises.push(this.loadPlugin(pluginInfo))
-
-        this.loadedScripts[ clientScript.script ] = true
-      }
-
-      await Promise.all(promises)
-
-      this.pluginsLoaded[scope].next(true)
-      this.loadingScopes[scope] = false
-
-      logger('Scope %s loaded', scope)
-    } catch (err) {
-      console.error('Cannot load plugins by scope %s.', scope, err)
-    }
+  addPlugin (plugin: ServerConfigPlugin, isTheme = false) {
+    return this.pluginsManager.addPlugin(plugin, isTheme)
   }
 
-  runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> {
-    return this.zone.runOutsideAngular(() => {
-      return runHook(this.hooks, hookName, result, params)
-    })
+  removePlugin (plugin: ServerConfigPlugin) {
+    return this.pluginsManager.removePlugin(plugin)
   }
 
   nameToNpmName (name: string, type: PluginType) {
@@ -189,12 +115,6 @@ export class PluginService implements ClientHook {
     return prefix + name
   }
 
-  pluginTypeFromNpmName (npmName: string) {
-    return npmName.startsWith('peertube-plugin-')
-      ? PluginType.PLUGIN
-      : PluginType.THEME
-  }
-
   getRegisteredVideoFormFields (type: VideoEditType) {
     return this.formFields.video.filter(f => f.videoFormOptions.type === type)
   }
@@ -213,27 +133,17 @@ export class PluginService implements ClientHook {
     return helpers.translate(toTranslate)
   }
 
-  private loadPlugin (pluginInfo: PluginInfo) {
-    return this.zone.runOutsideAngular(() => {
-      const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType)
-
-      const helpers = this.buildPeerTubeHelpers(pluginInfo)
-      this.helpers[npmName] = helpers
-
-      return loadPlugin({
-        hooks: this.hooks,
-        formFields: this.formFields,
-        onSettingsScripts: options => this.settingsScripts[npmName] = options,
-        pluginInfo,
-        peertubeHelpersFactory: () => helpers
-      })
+  private onFormFields (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) {
+    this.formFields.video.push({
+      commonOptions,
+      videoFormOptions
     })
   }
 
-  private buildScopeStruct () {
-    for (const plugin of this.plugins) {
-      this.addPlugin(plugin)
-    }
+  private onSettingsScripts (pluginInfo: PluginInfo, options: RegisterClientSettingsScript) {
+    const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType)
+
+    this.settingsScripts[npmName] = options
   }
 
   private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers {
@@ -242,12 +152,12 @@ export class PluginService implements ClientHook {
 
     return {
       getBaseStaticRoute: () => {
-        const pathPrefix = this.getPluginPathPrefix(pluginInfo.isTheme)
+        const pathPrefix = PluginsManager.getPluginPathPrefix(pluginInfo.isTheme)
         return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/static`
       },
 
       getBaseRouterRoute: () => {
-        const pathPrefix = this.getPluginPathPrefix(pluginInfo.isTheme)
+        const pathPrefix = PluginsManager.getPluginPathPrefix(pluginInfo.isTheme)
         return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/router`
       },
 
@@ -324,8 +234,4 @@ export class PluginService implements ClientHook {
         .get<PluginTranslation>(PluginService.BASE_PLUGIN_URL + '/translations/' + completeLocale + '.json')
         .pipe(shareReplay())
   }
-
-  private getPluginPathPrefix (isTheme: boolean) {
-    return isTheme ? '/themes' : '/plugins'
-  }
 }
index 0c7dec0a127cbfdd3c78ae16ade88c8d2abb6cd6..c355487980385eae7ef9c3e6c15bc658d9b32ff1 100644 (file)
@@ -114,6 +114,7 @@ export class ThemeService {
     const theme = this.getTheme(currentTheme)
     if (theme) {
       console.log('Adding scripts of theme %s.', currentTheme)
+
       this.pluginService.addPlugin(theme, true)
 
       this.pluginService.reloadLoadedScopes()
index 62dff82859320aa1dc8b6e041a850ee022d9922b..81425318814dffebe956a06a305588e1479e6f17 100644 (file)
@@ -22,8 +22,10 @@ import './videojs-components/settings-panel-child'
 import './videojs-components/theater-button'
 import './playlist/playlist-plugin'
 import videojs from 'video.js'
+import { PluginsManager } from '@root-helpers/plugins-manager'
 import { isDefaultLocale } from '@shared/core-utils/i18n'
 import { VideoFile } from '@shared/models'
+import { copyToClipboard } from '../../root-helpers/utils'
 import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
 import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder'
 import { segmentValidatorFactory } from './p2p-media-loader/segment-validator'
@@ -37,8 +39,7 @@ import {
   VideoJSPluginOptions
 } from './peertube-videojs-typings'
 import { TranslationsManager } from './translations-manager'
-import { buildVideoOrPlaylistEmbed, buildVideoLink, getRtcConfig, isSafari, isIOS } from './utils'
-import { copyToClipboard } from '../../root-helpers/utils'
+import { buildVideoLink, buildVideoOrPlaylistEmbed, getRtcConfig, isIOS, isSafari } from './utils'
 
 // Change 'Playback Rate' to 'Speed' (smaller for our settings menu)
 (videojs.getComponent('PlaybackRateMenuButton') as any).prototype.controlText_ = 'Speed'
@@ -116,21 +117,26 @@ export interface CommonOptions extends CustomizationOptions {
 }
 
 export type PeertubePlayerManagerOptions = {
-  common: CommonOptions,
-  webtorrent: WebtorrentOptions,
+  common: CommonOptions
+  webtorrent: WebtorrentOptions
   p2pMediaLoader?: P2PMediaLoaderOptions
+
+  pluginsManager: PluginsManager
 }
 
 export class PeertubePlayerManager {
   private static playerElementClassName: string
   private static onPlayerChange: (player: videojs.Player) => void
   private static alreadyPlayed = false
+  private static pluginsManager: PluginsManager
 
   static initState () {
     PeertubePlayerManager.alreadyPlayed = false
   }
 
   static async initialize (mode: PlayerMode, options: PeertubePlayerManagerOptions, onPlayerChange: (player: videojs.Player) => void) {
+    this.pluginsManager = options.pluginsManager
+
     let p2pMediaLoader: any
 
     this.onPlayerChange = onPlayerChange
@@ -144,7 +150,7 @@ export class PeertubePlayerManager {
       ])
     }
 
-    const videojsOptions = this.getVideojsOptions(mode, options, p2pMediaLoader)
+    const videojsOptions = await this.getVideojsOptions(mode, options, p2pMediaLoader)
 
     await TranslationsManager.loadLocaleInVideoJS(options.common.serverUrl, options.common.language, videojs)
 
@@ -206,7 +212,7 @@ export class PeertubePlayerManager {
     await import('./webtorrent/webtorrent-plugin')
 
     const mode = 'webtorrent'
-    const videojsOptions = this.getVideojsOptions(mode, options)
+    const videojsOptions = await this.getVideojsOptions(mode, options)
 
     const self = this
     videojs(newVideoElement, videojsOptions, function (this: videojs.Player) {
@@ -218,16 +224,16 @@ export class PeertubePlayerManager {
     })
   }
 
-  private static getVideojsOptions (
+  private static async getVideojsOptions (
     mode: PlayerMode,
     options: PeertubePlayerManagerOptions,
     p2pMediaLoaderModule?: any
-  ): videojs.PlayerOptions {
+  ): Promise<videojs.PlayerOptions> {
     const commonOptions = options.common
     const isHLS = mode === 'p2p-media-loader'
 
     let autoplay = this.getAutoPlayValue(commonOptions.autoplay)
-    let html5 = {
+    const html5 = {
       preloadTextTracks: false
     }
 
@@ -306,7 +312,7 @@ export class PeertubePlayerManager {
       Object.assign(videojsOptions, { language: commonOptions.language })
     }
 
-    return videojsOptions
+    return this.pluginsManager.runHook('filter:internal.player.videojs.options.result', videojsOptions)
   }
 
   private static addP2PMediaLoaderOptions (
index 036a7677db7682d03dd4a4953736201717046498..d62f07f9eee6626461ff61b50343fa4ccd4bd83a 100644 (file)
@@ -2,3 +2,4 @@ export * from './users'
 export * from './bytes'
 export * from './peertube-web-storage'
 export * from './utils'
+export * from './plugins-manager'
diff --git a/client/src/root-helpers/plugins-manager.ts b/client/src/root-helpers/plugins-manager.ts
new file mode 100644 (file)
index 0000000..f919db8
--- /dev/null
@@ -0,0 +1,251 @@
+import * as debug from 'debug'
+import { ReplaySubject } from 'rxjs'
+import { first, shareReplay } from 'rxjs/operators'
+import { RegisterClientHelpers } from 'src/types/register-client-option.model'
+import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks'
+import {
+  ClientHookName,
+  clientHookObject,
+  ClientScript,
+  HTMLServerConfig,
+  PluginClientScope,
+  PluginType,
+  RegisterClientFormFieldOptions,
+  RegisterClientHookOptions,
+  RegisterClientSettingsScript,
+  RegisterClientVideoFieldOptions,
+  ServerConfigPlugin
+} from '../../../shared/models'
+import { environment } from '../environments/environment'
+import { ClientScript as ClientScriptModule } from '../types/client-script.model'
+
+interface HookStructValue extends RegisterClientHookOptions {
+  plugin: ServerConfigPlugin
+  clientScript: ClientScript
+}
+
+type Hooks = { [ name: string ]: HookStructValue[] }
+
+type PluginInfo = {
+  plugin: ServerConfigPlugin
+  clientScript: ClientScript
+  pluginType: PluginType
+  isTheme: boolean
+}
+
+type PeertubeHelpersFactory = (pluginInfo: PluginInfo) => RegisterClientHelpers
+type OnFormFields = (options: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void
+type OnSettingsScripts = (pluginInfo: PluginInfo, options: RegisterClientSettingsScript) => void
+
+const logger = debug('peertube:plugins')
+
+class PluginsManager {
+  private hooks: Hooks = {}
+
+  private scopes: { [ scopeName: string ]: PluginInfo[] } = {}
+
+  private loadedScripts: { [ script: string ]: boolean } = {}
+  private loadedScopes: PluginClientScope[] = []
+  private loadingScopes: { [id in PluginClientScope]?: boolean } = {}
+
+  private pluginsLoaded: { [ scope in PluginClientScope ]: ReplaySubject<boolean> } = {
+    common: new ReplaySubject<boolean>(1),
+    'admin-plugin': new ReplaySubject<boolean>(1),
+    search: new ReplaySubject<boolean>(1),
+    'video-watch': new ReplaySubject<boolean>(1),
+    signup: new ReplaySubject<boolean>(1),
+    login: new ReplaySubject<boolean>(1),
+    'video-edit': new ReplaySubject<boolean>(1),
+    embed: new ReplaySubject<boolean>(1)
+  }
+
+  private readonly peertubeHelpersFactory: PeertubeHelpersFactory
+  private readonly onFormFields: OnFormFields
+  private readonly onSettingsScripts: OnSettingsScripts
+
+  constructor (options: {
+    peertubeHelpersFactory: PeertubeHelpersFactory
+    onFormFields?: OnFormFields
+    onSettingsScripts?: OnSettingsScripts
+  }) {
+    this.peertubeHelpersFactory = options.peertubeHelpersFactory
+    this.onFormFields = options.onFormFields
+    this.onSettingsScripts = options.onSettingsScripts
+  }
+
+  static getPluginPathPrefix (isTheme: boolean) {
+    return isTheme ? '/themes' : '/plugins'
+  }
+
+  loadPluginsList (config: HTMLServerConfig) {
+    for (const plugin of config.plugin.registered) {
+      this.addPlugin(plugin)
+    }
+  }
+
+  async runHook<T> (hookName: ClientHookName, result?: T, params?: any) {
+    if (!this.hooks[hookName]) return result
+
+    const hookType = getHookType(hookName)
+
+    for (const hook of this.hooks[hookName]) {
+      console.log('Running hook %s of plugin %s.', hookName, hook.plugin.name)
+
+      result = await internalRunHook(hook.handler, hookType, result, params, err => {
+        console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.clientScript.script, hook.plugin.name, err)
+      })
+    }
+
+    return result
+  }
+
+  ensurePluginsAreLoaded (scope: PluginClientScope) {
+    this.loadPluginsByScope(scope)
+
+    return this.pluginsLoaded[scope].asObservable()
+               .pipe(first(), shareReplay())
+               .toPromise()
+  }
+
+  async reloadLoadedScopes () {
+    for (const scope of this.loadedScopes) {
+      await this.loadPluginsByScope(scope, true)
+    }
+  }
+
+  addPlugin (plugin: ServerConfigPlugin, isTheme = false) {
+    const pathPrefix = PluginsManager.getPluginPathPrefix(isTheme)
+
+    for (const key of Object.keys(plugin.clientScripts)) {
+      const clientScript = plugin.clientScripts[key]
+
+      for (const scope of clientScript.scopes) {
+        if (!this.scopes[scope]) this.scopes[scope] = []
+
+        this.scopes[scope].push({
+          plugin,
+          clientScript: {
+            script: `${pathPrefix}/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}`,
+            scopes: clientScript.scopes
+          },
+          pluginType: isTheme ? PluginType.THEME : PluginType.PLUGIN,
+          isTheme
+        })
+
+        this.loadedScripts[clientScript.script] = false
+      }
+    }
+  }
+
+  removePlugin (plugin: ServerConfigPlugin) {
+    for (const key of Object.keys(this.scopes)) {
+      this.scopes[key] = this.scopes[key].filter(o => o.plugin.name !== plugin.name)
+    }
+  }
+
+  async loadPluginsByScope (scope: PluginClientScope, isReload = false) {
+    if (this.loadingScopes[scope]) return
+    if (!isReload && this.loadedScopes.includes(scope)) return
+
+    this.loadingScopes[scope] = true
+
+    logger('Loading scope %s', scope)
+
+    try {
+      if (!isReload) this.loadedScopes.push(scope)
+
+      const toLoad = this.scopes[ scope ]
+      if (!Array.isArray(toLoad)) {
+        this.loadingScopes[scope] = false
+        this.pluginsLoaded[scope].next(true)
+
+        logger('Nothing to load for scope %s', scope)
+        return
+      }
+
+      const promises: Promise<any>[] = []
+      for (const pluginInfo of toLoad) {
+        const clientScript = pluginInfo.clientScript
+
+        if (this.loadedScripts[ clientScript.script ]) continue
+
+        promises.push(this.loadPlugin(pluginInfo))
+
+        this.loadedScripts[ clientScript.script ] = true
+      }
+
+      await Promise.all(promises)
+
+      this.pluginsLoaded[scope].next(true)
+      this.loadingScopes[scope] = false
+
+      logger('Scope %s loaded', scope)
+    } catch (err) {
+      console.error('Cannot load plugins by scope %s.', scope, err)
+    }
+  }
+
+  private loadPlugin (pluginInfo: PluginInfo) {
+    const { plugin, clientScript } = pluginInfo
+
+    const registerHook = (options: RegisterClientHookOptions) => {
+      if (clientHookObject[options.target] !== true) {
+        console.error('Unknown hook %s of plugin %s. Skipping.', options.target, plugin.name)
+        return
+      }
+
+      if (!this.hooks[options.target]) this.hooks[options.target] = []
+
+      this.hooks[options.target].push({
+        plugin,
+        clientScript,
+        target: options.target,
+        handler: options.handler,
+        priority: options.priority || 0
+      })
+    }
+
+    const registerVideoField = (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => {
+      if (!this.onFormFields) {
+        throw new Error('Video field registration is not supported')
+      }
+
+      return this.onFormFields(commonOptions, videoFormOptions)
+    }
+
+    const registerSettingsScript = (options: RegisterClientSettingsScript) => {
+      if (!this.onSettingsScripts) {
+        throw new Error('Registering settings script is not supported')
+      }
+
+      return this.onSettingsScripts(pluginInfo, options)
+    }
+
+    const peertubeHelpers = this.peertubeHelpersFactory(pluginInfo)
+
+    console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name)
+
+    const absURL = (environment.apiUrl || window.location.origin) + clientScript.script
+    return import(/* webpackIgnore: true */ absURL)
+      .then((script: ClientScriptModule) => script.register({ registerHook, registerVideoField, registerSettingsScript, peertubeHelpers }))
+      .then(() => this.sortHooksByPriority())
+      .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err))
+  }
+
+  private sortHooksByPriority () {
+    for (const hookName of Object.keys(this.hooks)) {
+      this.hooks[hookName].sort((a, b) => {
+        return b.priority - a.priority
+      })
+    }
+  }
+}
+
+export {
+  PluginsManager,
+
+  PluginInfo,
+  PeertubeHelpersFactory,
+  OnFormFields,
+  OnSettingsScripts
+}
diff --git a/client/src/root-helpers/plugins.ts b/client/src/root-helpers/plugins.ts
deleted file mode 100644 (file)
index 10c111a..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-import { RegisterClientHelpers } from 'src/types/register-client-option.model'
-import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks'
-import {
-  ClientHookName,
-  clientHookObject,
-  ClientScript,
-  PluginType,
-  RegisterClientFormFieldOptions,
-  RegisterClientHookOptions,
-  RegisterClientSettingsScript,
-  RegisterClientVideoFieldOptions,
-  ServerConfigPlugin
-} from '../../../shared/models'
-import { environment } from '../environments/environment'
-import { ClientScript as ClientScriptModule } from '../types/client-script.model'
-
-interface HookStructValue extends RegisterClientHookOptions {
-  plugin: ServerConfigPlugin
-  clientScript: ClientScript
-}
-
-type Hooks = { [ name: string ]: HookStructValue[] }
-
-type PluginInfo = {
-  plugin: ServerConfigPlugin
-  clientScript: ClientScript
-  pluginType: PluginType
-  isTheme: boolean
-}
-
-type FormFields = {
-  video: {
-    commonOptions: RegisterClientFormFieldOptions
-    videoFormOptions: RegisterClientVideoFieldOptions
-  }[]
-}
-
-async function runHook<T> (hooks: Hooks, hookName: ClientHookName, result?: T, params?: any) {
-  if (!hooks[hookName]) return result
-
-  const hookType = getHookType(hookName)
-
-  for (const hook of hooks[hookName]) {
-    console.log('Running hook %s of plugin %s.', hookName, hook.plugin.name)
-
-    result = await internalRunHook(hook.handler, hookType, result, params, err => {
-      console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.clientScript.script, hook.plugin.name, err)
-    })
-  }
-
-  return result
-}
-
-function loadPlugin (options: {
-  hooks: Hooks
-  pluginInfo: PluginInfo
-  peertubeHelpersFactory: (pluginInfo: PluginInfo) => RegisterClientHelpers
-  formFields?: FormFields
-  onSettingsScripts?: (options: RegisterClientSettingsScript) => void
-}) {
-  const { hooks, pluginInfo, peertubeHelpersFactory, formFields, onSettingsScripts } = options
-  const { plugin, clientScript } = pluginInfo
-
-  const registerHook = (options: RegisterClientHookOptions) => {
-    if (clientHookObject[options.target] !== true) {
-      console.error('Unknown hook %s of plugin %s. Skipping.', options.target, plugin.name)
-      return
-    }
-
-    if (!hooks[options.target]) hooks[options.target] = []
-
-    hooks[options.target].push({
-      plugin,
-      clientScript,
-      target: options.target,
-      handler: options.handler,
-      priority: options.priority || 0
-    })
-  }
-
-  const registerVideoField = (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => {
-    if (!formFields) {
-      throw new Error('Video field registration is not supported')
-    }
-
-    formFields.video.push({
-      commonOptions,
-      videoFormOptions
-    })
-  }
-
-  const registerSettingsScript = (options: RegisterClientSettingsScript) => {
-    if (!onSettingsScripts) {
-      throw new Error('Registering settings script is not supported')
-    }
-
-    return onSettingsScripts(options)
-  }
-
-  const peertubeHelpers = peertubeHelpersFactory(pluginInfo)
-
-  console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name)
-
-  const absURL = (environment.apiUrl || window.location.origin) + clientScript.script
-  return import(/* webpackIgnore: true */ absURL)
-    .then((script: ClientScriptModule) => script.register({ registerHook, registerVideoField, registerSettingsScript, peertubeHelpers }))
-    .then(() => sortHooksByPriority(hooks))
-    .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err))
-}
-
-export {
-  HookStructValue,
-  Hooks,
-  PluginInfo,
-  FormFields,
-  loadPlugin,
-  runHook
-}
-
-function sortHooksByPriority (hooks: Hooks) {
-  for (const hookName of Object.keys(hooks)) {
-    hooks[hookName].sort((a, b) => {
-      return b.priority - a.priority
-    })
-  }
-}
index a367feb8e529744ee05f1e618b07a3b86ba0e58a..dc9727049b77a7c4730f540f3516222518480bbb 100644 (file)
@@ -3,7 +3,6 @@ import videojs from 'video.js'
 import { peertubeTranslate } from '../../../../shared/core-utils/i18n'
 import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
 import {
-  ClientHookName,
   HTMLServerConfig,
   OAuth2ErrorCode,
   PluginType,
@@ -19,7 +18,7 @@ import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode } from
 import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
 import { TranslationsManager } from '../../assets/player/translations-manager'
 import { peertubeLocalStorage } from '../../root-helpers/peertube-web-storage'
-import { Hooks, loadPlugin, runHook } from '../../root-helpers/plugins'
+import { PluginsManager } from '../../root-helpers/plugins-manager'
 import { Tokens } from '../../root-helpers/users'
 import { objectToUrlEncoded } from '../../root-helpers/utils'
 import { RegisterClientHelpers } from '../../types/register-client-option.model'
@@ -68,8 +67,7 @@ export class PeerTubeEmbed {
 
   private wrapperElement: HTMLElement
 
-  private peertubeHooks: Hooks = {}
-  private loadedScripts = new Set<string>()
+  private pluginsManager: PluginsManager
 
   static async main () {
     const videoContainerId = 'video-wrapper'
@@ -489,7 +487,7 @@ export class PeerTubeEmbed {
       this.PeertubePlayerManagerModulePromise
     ])
 
-    await this.ensurePluginsAreLoaded(serverTranslations)
+    await this.loadPlugins(serverTranslations)
 
     const videoInfo: VideoDetails = videoInfoTmp
 
@@ -560,7 +558,9 @@ export class PeerTubeEmbed {
 
       webtorrent: {
         videoFiles: videoInfo.files
-      }
+      },
+
+      pluginsManager: this.pluginsManager
     }
 
     if (this.mode === 'p2p-media-loader') {
@@ -600,7 +600,7 @@ export class PeerTubeEmbed {
       })
     }
 
-    this.runHook('action:embed.player.loaded', undefined, { player: this.player, videojs, video: videoInfo })
+    this.pluginsManager.runHook('action:embed.player.loaded', undefined, { player: this.player, videojs, video: videoInfo })
   }
 
   private async initCore () {
@@ -740,37 +740,14 @@ export class PeerTubeEmbed {
     return window.location.pathname.split('/')[1] === 'video-playlists'
   }
 
-  private async ensurePluginsAreLoaded (translations?: { [ id: string ]: string }) {
-    if (this.config.plugin.registered.length === 0) return
-
-    for (const plugin of this.config.plugin.registered) {
-      for (const key of Object.keys(plugin.clientScripts)) {
-        const clientScript = plugin.clientScripts[key]
-
-        if (clientScript.scopes.includes('embed') === false) continue
-
-        const script = `/plugins/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}`
-
-        if (this.loadedScripts.has(script)) continue
+  private loadPlugins (translations?: { [ id: string ]: string }) {
+    this.pluginsManager = new PluginsManager({
+      peertubeHelpersFactory: _ => this.buildPeerTubeHelpers(translations)
+    })
 
-        const pluginInfo = {
-          plugin,
-          clientScript: {
-            script,
-            scopes: clientScript.scopes
-          },
-          pluginType: PluginType.PLUGIN,
-          isTheme: false
-        }
+    this.pluginsManager.loadPluginsList(this.config)
 
-        await loadPlugin({
-          hooks: this.peertubeHooks,
-          pluginInfo,
-          onSettingsScripts: () => undefined,
-          peertubeHelpersFactory: _ => this.buildPeerTubeHelpers(translations)
-        })
-      }
-    }
+    return this.pluginsManager.ensurePluginsAreLoaded('embed')
   }
 
   private buildPeerTubeHelpers (translations?: { [ id: string ]: string }): RegisterClientHelpers {
@@ -808,10 +785,6 @@ export class PeerTubeEmbed {
       }
     }
   }
-
-  private runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> {
-    return runHook(this.peertubeHooks, hookName, result, params)
-  }
 }
 
 PeerTubeEmbed.main()
index 3eedd670ba15a4fdc1e01230350146b706591837..546866845512f8f4e5985398e302d0943a7497bf 100644 (file)
@@ -53,7 +53,10 @@ export const clientFilterHookObject = {
   'filter:internal.common.svg-icons.get-content.result': true,
 
   // Filter left menu links
-  'filter:left-menu.links.create.result': true
+  'filter:left-menu.links.create.result': true,
+
+  // Filter videojs options built for PeerTube player
+  'filter:internal.player.videojs.options.result': true
 }
 
 export type ClientFilterHookName = keyof typeof clientFilterHookObject