import { MarkdownService } from '@app/core/renderer'
import { RestExtractor } from '@app/core/rest'
import { ServerService } from '@app/core/server/server.service'
-import { getDevLocale, importModule, isOnDevLocale } from '@app/helpers'
+import { getDevLocale, isOnDevLocale } from '@app/helpers'
import { CustomModalComponent } from '@app/modal/custom-modal.component'
+import { Hooks, loadPlugin, PluginInfo, runHook } from '@root-helpers/plugins'
import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n'
-import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks'
import {
ClientHook,
ClientHookName,
- clientHookObject,
- ClientScript,
PluginClientScope,
PluginTranslation,
PluginType,
PublicServerSetting,
- RegisterClientHookOptions,
ServerConfigPlugin
} from '@shared/models'
import { environment } from '../../../environments/environment'
-import { ClientScript as ClientScriptModule } from '../../../types/client-script.model'
import { RegisterClientHelpers } from '../../../types/register-client-option.model'
-interface HookStructValue extends RegisterClientHookOptions {
- plugin: ServerConfigPlugin
- clientScript: ClientScript
-}
-
-type PluginInfo = {
- plugin: ServerConfigPlugin
- clientScript: ClientScript
- pluginType: PluginType
- isTheme: boolean
-}
-
@Injectable()
export class PluginService implements ClientHook {
private static BASE_PLUGIN_API_URL = environment.apiUrl + '/api/v1/plugins'
search: new ReplaySubject<boolean>(1),
'video-watch': new ReplaySubject<boolean>(1),
signup: new ReplaySubject<boolean>(1),
- login: new ReplaySubject<boolean>(1)
+ login: new ReplaySubject<boolean>(1),
+ embed: new ReplaySubject<boolean>(1)
}
translationsObservable: Observable<PluginTranslation>
private loadedScopes: PluginClientScope[] = []
private loadingScopes: { [id in PluginClientScope]?: boolean } = {}
- private hooks: { [ name: string ]: HookStructValue[] } = {}
+ private hooks: Hooks = {}
constructor (
private authService: AuthService,
this.scopes[scope].push({
plugin,
clientScript: {
- script: environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}`,
+ script: `${pathPrefix}/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}`,
scopes: clientScript.scopes
},
pluginType: isTheme ? PluginType.THEME : PluginType.PLUGIN,
}
runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> {
- return this.zone.runOutsideAngular(async () => {
- 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
+ return this.zone.runOutsideAngular(() => {
+ return runHook(this.hooks, hookName, result, params)
})
}
}
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 peertubeHelpers = this.buildPeerTubeHelpers(pluginInfo)
-
- console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name)
-
return this.zone.runOutsideAngular(() => {
- return importModule(clientScript.script)
- .then((script: ClientScriptModule) => script.register({ registerHook, peertubeHelpers }))
- .then(() => this.sortHooksByPriority())
- .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err))
+ return loadPlugin(this.hooks, pluginInfo, pluginInfo => this.buildPeerTubeHelpers(pluginInfo))
})
}
}
}
- private sortHooksByPriority () {
- for (const hookName of Object.keys(this.hooks)) {
- this.hooks[hookName].sort((a, b) => {
- return b.priority - a.priority
- })
- }
- }
-
private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers {
const { plugin } = pluginInfo
const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType)
window.scroll(0, 0)
}
-// Thanks: https://github.com/uupaa/dynamic-import-polyfill
-function importModule (path: string) {
- return new Promise((resolve, reject) => {
- const vector = '$importModule$' + Math.random().toString(32).slice(2)
- const script = document.createElement('script')
-
- const destructor = () => {
- delete window[ vector ]
- script.onerror = null
- script.onload = null
- script.remove()
- URL.revokeObjectURL(script.src)
- script.src = ''
- }
-
- script.defer = true
- script.type = 'module'
-
- script.onerror = () => {
- reject(new Error(`Failed to import: ${path}`))
- destructor()
- }
- script.onload = () => {
- resolve(window[ vector ])
- destructor()
- }
- const absURL = (environment.apiUrl || window.location.origin) + path
- const loader = `import * as m from "${absURL}"; window.${vector} = m;` // export Module
- const blob = new Blob([ loader ], { type: 'text/javascript' })
- script.src = URL.createObjectURL(blob)
-
- document.head.appendChild(script)
- })
-}
-
function isInViewport (el: HTMLElement) {
const bounding = el.getBoundingClientRect()
return (
getAbsoluteEmbedUrl,
objectLineFeedToHtml,
removeElementFromArray,
- importModule,
scrollToTop,
isInViewport,
isXPercentInViewport
import 'core-js/features/reflect'
export const environment = {
- production: false,
+ production: true,
hmr: false,
- apiUrl: 'http://localhost:9000',
- embedUrl: 'http://localhost:9000'
+ apiUrl: '',
+ embedUrl: ''
}
--- /dev/null
+import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks'
+import { ClientHookName, ClientScript, RegisterClientHookOptions, ServerConfigPlugin, PluginType, clientHookObject } from '../../../shared/models'
+import { RegisterClientHelpers } from 'src/types/register-client-option.model'
+import { ClientScript as ClientScriptModule } from '../types/client-script.model'
+import { importModule } from './utils'
+
+interface HookStructValue extends RegisterClientHookOptions {
+ plugin: ServerConfigPlugin
+ clientScript: ClientScript
+}
+
+type Hooks = { [ name: string ]: HookStructValue[] }
+
+type PluginInfo = {
+ plugin: ServerConfigPlugin
+ clientScript: ClientScript
+ pluginType: PluginType
+ isTheme: boolean
+}
+
+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 (hooks: Hooks, pluginInfo: PluginInfo, peertubeHelpersFactory: (pluginInfo: PluginInfo) => RegisterClientHelpers) {
+ 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 peertubeHelpers = peertubeHelpersFactory(pluginInfo)
+
+ console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name)
+
+ return importModule(clientScript.script)
+ .then((script: ClientScriptModule) => script.register({ registerHook, peertubeHelpers }))
+ .then(() => sortHooksByPriority(hooks))
+ .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err))
+}
+
+export {
+ HookStructValue,
+ Hooks,
+ PluginInfo,
+ loadPlugin,
+ runHook
+}
+
+function sortHooksByPriority (hooks: Hooks) {
+ for (const hookName of Object.keys(hooks)) {
+ hooks[hookName].sort((a, b) => {
+ return b.priority - a.priority
+ })
+ }
+}
+import { environment } from '../environments/environment'
+
function objectToUrlEncoded (obj: any) {
const str: string[] = []
for (const key of Object.keys(obj)) {
return str.join('&')
}
+// Thanks: https://github.com/uupaa/dynamic-import-polyfill
+function importModule (path: string) {
+ return new Promise((resolve, reject) => {
+ const vector = '$importModule$' + Math.random().toString(32).slice(2)
+ const script = document.createElement('script')
+
+ const destructor = () => {
+ delete window[ vector ]
+ script.onerror = null
+ script.onload = null
+ script.remove()
+ URL.revokeObjectURL(script.src)
+ script.src = ''
+ }
+
+ script.defer = true
+ script.type = 'module'
+
+ script.onerror = () => {
+ reject(new Error(`Failed to import: ${path}`))
+ destructor()
+ }
+ script.onload = () => {
+ resolve(window[ vector ])
+ destructor()
+ }
+ const absURL = (environment.apiUrl || window.location.origin) + path
+ const loader = `import * as m from "${absURL}"; window.${vector} = m;` // export Module
+ const blob = new Blob([ loader ], { type: 'text/javascript' })
+ script.src = URL.createObjectURL(blob)
+
+ document.head.appendChild(script)
+ })
+}
+
export {
+ importModule,
objectToUrlEncoded
}
import './embed.scss'
import videojs from 'video.js'
-import { objectToUrlEncoded, peertubeLocalStorage } from '@root-helpers/index'
-import { Tokens } from '@root-helpers/users'
import { peertubeTranslate } from '../../../../shared/core-utils/i18n'
import {
ResultList,
VideoDetails,
VideoPlaylist,
VideoPlaylistElement,
- VideoStreamingPlaylistType
+ VideoStreamingPlaylistType,
+ PluginType,
+ ClientHookName
} from '../../../../shared/models'
import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager'
import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
import { TranslationsManager } from '../../assets/player/translations-manager'
+import { Hooks, loadPlugin, runHook } from '../../root-helpers/plugins'
+import { Tokens } from '../../root-helpers/users'
+import { peertubeLocalStorage } from '../../root-helpers/peertube-web-storage'
+import { objectToUrlEncoded } from '../../root-helpers/utils'
import { PeerTubeEmbedApi } from './embed-api'
+import { RegisterClientHelpers } from '../../types/register-client-option.model'
type Translations = { [ id: string ]: string }
private wrapperElement: HTMLElement
+ private peertubeHooks: Hooks = {}
+ private loadedScripts = new Set<string>()
+
static async main () {
const videoContainerId = 'video-wrapper'
const embed = new PeerTubeEmbed(videoContainerId)
this.PeertubePlayerManagerModulePromise
])
+ await this.ensurePluginsAreLoaded(config, serverTranslations)
+
const videoInfo: VideoDetails = videoInfoTmp
const PeertubePlayerManager = PeertubePlayerManagerModule.PeertubePlayerManager
this.playNextVideo()
})
}
+
+ this.runHook('action:embed.player.loaded', undefined, { player: this.player })
}
private async initCore () {
private isPlaylistEmbed () {
return window.location.pathname.split('/')[1] === 'video-playlists'
}
+
+ private async ensurePluginsAreLoaded (config: ServerConfig, translations?: { [ id: string ]: string }) {
+ if (config.plugin.registered.length === 0) return
+
+ for (const plugin of 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
+
+ const pluginInfo = {
+ plugin,
+ clientScript: {
+ script,
+ scopes: clientScript.scopes
+ },
+ pluginType: PluginType.PLUGIN,
+ isTheme: false
+ }
+
+ await loadPlugin(this.peertubeHooks, pluginInfo, _ => this.buildPeerTubeHelpers(translations))
+ }
+ }
+ }
+
+ private buildPeerTubeHelpers (translations?: { [ id: string ]: string }): RegisterClientHelpers {
+ function unimplemented (): any {
+ throw new Error('This helper is not implemented in embed.')
+ }
+
+ return {
+ getBaseStaticRoute: unimplemented,
+
+ getSettings: unimplemented,
+
+ isLoggedIn: unimplemented,
+
+ notifier: {
+ info: unimplemented,
+ error: unimplemented,
+ success: unimplemented
+ },
+
+ showModal: unimplemented,
+
+ markdownRenderer: {
+ textMarkdownToHTML: unimplemented,
+ enhancedMarkdownToHTML: unimplemented
+ },
+
+ translate: (value: string) => {
+ return Promise.resolve(peertubeTranslate(value, translations))
+ }
+ }
+ }
+
+ private runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> {
+ return runHook(this.peertubeHooks, hookName, result, params)
+ }
}
PeerTubeEmbed.main()
'action:router.navigation-end': true,
// Fired when the registration page is being initialized
- 'action:signup.register.init': true
+ 'action:signup.register.init': true,
+
+ // ####### Embed hooks #######
+ // In embed scope, peertube helpers are not available
+
+ // Fired when the embed loaded the player
+ 'action:embed.player.loaded': true
}
export type ClientActionHookName = keyof typeof clientActionHookObject
-export type PluginClientScope = 'common' | 'video-watch' | 'search' | 'signup' | 'login'
+export type PluginClientScope = 'common' | 'video-watch' | 'search' | 'signup' | 'login' | 'embed'