diff options
author | Chocobozzz <me@florianbigard.com> | 2020-08-20 11:46:25 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-08-20 14:23:57 +0200 |
commit | f95628636b6ccdf3eae2449ca718e075b072f678 (patch) | |
tree | 35d51980c87b7d6747bdff6e37bdfe37e3c989dc | |
parent | a9f6802e7dac4f21599076bc1119bb6ff16961dc (diff) | |
download | PeerTube-f95628636b6ccdf3eae2449ca718e075b072f678.tar.gz PeerTube-f95628636b6ccdf3eae2449ca718e075b072f678.tar.zst PeerTube-f95628636b6ccdf3eae2449ca718e075b072f678.zip |
Support plugin hooks in embed
-rw-r--r-- | client/src/app/core/plugins/plugin.service.ts | 79 | ||||
-rw-r--r-- | client/src/app/helpers/utils.ts | 36 | ||||
-rw-r--r-- | client/src/environments/environment.ts | 6 | ||||
-rw-r--r-- | client/src/root-helpers/plugins.ts | 81 | ||||
-rw-r--r-- | client/src/root-helpers/utils.ts | 38 | ||||
-rw-r--r-- | client/src/standalone/videos/embed.ts | 81 | ||||
-rw-r--r-- | shared/models/plugins/client-hook.model.ts | 8 | ||||
-rw-r--r-- | shared/models/plugins/plugin-client-scope.type.ts | 2 |
8 files changed, 217 insertions, 114 deletions
diff --git a/client/src/app/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts index dc115c0e1..871613b89 100644 --- a/client/src/app/core/plugins/plugin.service.ts +++ b/client/src/app/core/plugins/plugin.service.ts | |||
@@ -7,38 +7,22 @@ import { Notifier } from '@app/core/notification' | |||
7 | import { MarkdownService } from '@app/core/renderer' | 7 | import { MarkdownService } from '@app/core/renderer' |
8 | import { RestExtractor } from '@app/core/rest' | 8 | import { RestExtractor } from '@app/core/rest' |
9 | import { ServerService } from '@app/core/server/server.service' | 9 | import { ServerService } from '@app/core/server/server.service' |
10 | import { getDevLocale, importModule, isOnDevLocale } from '@app/helpers' | 10 | import { getDevLocale, isOnDevLocale } from '@app/helpers' |
11 | import { CustomModalComponent } from '@app/modal/custom-modal.component' | 11 | import { CustomModalComponent } from '@app/modal/custom-modal.component' |
12 | import { Hooks, loadPlugin, PluginInfo, runHook } from '@root-helpers/plugins' | ||
12 | import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n' | 13 | import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n' |
13 | import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks' | ||
14 | import { | 14 | import { |
15 | ClientHook, | 15 | ClientHook, |
16 | ClientHookName, | 16 | ClientHookName, |
17 | clientHookObject, | ||
18 | ClientScript, | ||
19 | PluginClientScope, | 17 | PluginClientScope, |
20 | PluginTranslation, | 18 | PluginTranslation, |
21 | PluginType, | 19 | PluginType, |
22 | PublicServerSetting, | 20 | PublicServerSetting, |
23 | RegisterClientHookOptions, | ||
24 | ServerConfigPlugin | 21 | ServerConfigPlugin |
25 | } from '@shared/models' | 22 | } from '@shared/models' |
26 | import { environment } from '../../../environments/environment' | 23 | import { environment } from '../../../environments/environment' |
27 | import { ClientScript as ClientScriptModule } from '../../../types/client-script.model' | ||
28 | import { RegisterClientHelpers } from '../../../types/register-client-option.model' | 24 | import { RegisterClientHelpers } from '../../../types/register-client-option.model' |
29 | 25 | ||
30 | interface HookStructValue extends RegisterClientHookOptions { | ||
31 | plugin: ServerConfigPlugin | ||
32 | clientScript: ClientScript | ||
33 | } | ||
34 | |||
35 | type PluginInfo = { | ||
36 | plugin: ServerConfigPlugin | ||
37 | clientScript: ClientScript | ||
38 | pluginType: PluginType | ||
39 | isTheme: boolean | ||
40 | } | ||
41 | |||
42 | @Injectable() | 26 | @Injectable() |
43 | export class PluginService implements ClientHook { | 27 | export class PluginService implements ClientHook { |
44 | private static BASE_PLUGIN_API_URL = environment.apiUrl + '/api/v1/plugins' | 28 | private static BASE_PLUGIN_API_URL = environment.apiUrl + '/api/v1/plugins' |
@@ -51,7 +35,8 @@ export class PluginService implements ClientHook { | |||
51 | search: new ReplaySubject<boolean>(1), | 35 | search: new ReplaySubject<boolean>(1), |
52 | 'video-watch': new ReplaySubject<boolean>(1), | 36 | 'video-watch': new ReplaySubject<boolean>(1), |
53 | signup: new ReplaySubject<boolean>(1), | 37 | signup: new ReplaySubject<boolean>(1), |
54 | login: new ReplaySubject<boolean>(1) | 38 | login: new ReplaySubject<boolean>(1), |
39 | embed: new ReplaySubject<boolean>(1) | ||
55 | } | 40 | } |
56 | 41 | ||
57 | translationsObservable: Observable<PluginTranslation> | 42 | translationsObservable: Observable<PluginTranslation> |
@@ -64,7 +49,7 @@ export class PluginService implements ClientHook { | |||
64 | private loadedScopes: PluginClientScope[] = [] | 49 | private loadedScopes: PluginClientScope[] = [] |
65 | private loadingScopes: { [id in PluginClientScope]?: boolean } = {} | 50 | private loadingScopes: { [id in PluginClientScope]?: boolean } = {} |
66 | 51 | ||
67 | private hooks: { [ name: string ]: HookStructValue[] } = {} | 52 | private hooks: Hooks = {} |
68 | 53 | ||
69 | constructor ( | 54 | constructor ( |
70 | private authService: AuthService, | 55 | private authService: AuthService, |
@@ -120,7 +105,7 @@ export class PluginService implements ClientHook { | |||
120 | this.scopes[scope].push({ | 105 | this.scopes[scope].push({ |
121 | plugin, | 106 | plugin, |
122 | clientScript: { | 107 | clientScript: { |
123 | script: environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}`, | 108 | script: `${pathPrefix}/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}`, |
124 | scopes: clientScript.scopes | 109 | scopes: clientScript.scopes |
125 | }, | 110 | }, |
126 | pluginType: isTheme ? PluginType.THEME : PluginType.PLUGIN, | 111 | pluginType: isTheme ? PluginType.THEME : PluginType.PLUGIN, |
@@ -184,20 +169,8 @@ export class PluginService implements ClientHook { | |||
184 | } | 169 | } |
185 | 170 | ||
186 | runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> { | 171 | runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> { |
187 | return this.zone.runOutsideAngular(async () => { | 172 | return this.zone.runOutsideAngular(() => { |
188 | if (!this.hooks[ hookName ]) return result | 173 | return runHook(this.hooks, hookName, result, params) |
189 | |||
190 | const hookType = getHookType(hookName) | ||
191 | |||
192 | for (const hook of this.hooks[ hookName ]) { | ||
193 | console.log('Running hook %s of plugin %s.', hookName, hook.plugin.name) | ||
194 | |||
195 | result = await internalRunHook(hook.handler, hookType, result, params, err => { | ||
196 | console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.clientScript.script, hook.plugin.name, err) | ||
197 | }) | ||
198 | } | ||
199 | |||
200 | return result | ||
201 | }) | 174 | }) |
202 | } | 175 | } |
203 | 176 | ||
@@ -216,34 +189,8 @@ export class PluginService implements ClientHook { | |||
216 | } | 189 | } |
217 | 190 | ||
218 | private loadPlugin (pluginInfo: PluginInfo) { | 191 | private loadPlugin (pluginInfo: PluginInfo) { |
219 | const { plugin, clientScript } = pluginInfo | ||
220 | |||
221 | const registerHook = (options: RegisterClientHookOptions) => { | ||
222 | if (clientHookObject[options.target] !== true) { | ||
223 | console.error('Unknown hook %s of plugin %s. Skipping.', options.target, plugin.name) | ||
224 | return | ||
225 | } | ||
226 | |||
227 | if (!this.hooks[options.target]) this.hooks[options.target] = [] | ||
228 | |||
229 | this.hooks[options.target].push({ | ||
230 | plugin, | ||
231 | clientScript, | ||
232 | target: options.target, | ||
233 | handler: options.handler, | ||
234 | priority: options.priority || 0 | ||
235 | }) | ||
236 | } | ||
237 | |||
238 | const peertubeHelpers = this.buildPeerTubeHelpers(pluginInfo) | ||
239 | |||
240 | console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name) | ||
241 | |||
242 | return this.zone.runOutsideAngular(() => { | 192 | return this.zone.runOutsideAngular(() => { |
243 | return importModule(clientScript.script) | 193 | return loadPlugin(this.hooks, pluginInfo, pluginInfo => this.buildPeerTubeHelpers(pluginInfo)) |
244 | .then((script: ClientScriptModule) => script.register({ registerHook, peertubeHelpers })) | ||
245 | .then(() => this.sortHooksByPriority()) | ||
246 | .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err)) | ||
247 | }) | 194 | }) |
248 | } | 195 | } |
249 | 196 | ||
@@ -253,14 +200,6 @@ export class PluginService implements ClientHook { | |||
253 | } | 200 | } |
254 | } | 201 | } |
255 | 202 | ||
256 | private sortHooksByPriority () { | ||
257 | for (const hookName of Object.keys(this.hooks)) { | ||
258 | this.hooks[hookName].sort((a, b) => { | ||
259 | return b.priority - a.priority | ||
260 | }) | ||
261 | } | ||
262 | } | ||
263 | |||
264 | private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers { | 203 | private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers { |
265 | const { plugin } = pluginInfo | 204 | const { plugin } = pluginInfo |
266 | const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) | 205 | const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) |
diff --git a/client/src/app/helpers/utils.ts b/client/src/app/helpers/utils.ts index d05541ca9..d9007dd77 100644 --- a/client/src/app/helpers/utils.ts +++ b/client/src/app/helpers/utils.ts | |||
@@ -148,41 +148,6 @@ function scrollToTop () { | |||
148 | window.scroll(0, 0) | 148 | window.scroll(0, 0) |
149 | } | 149 | } |
150 | 150 | ||
151 | // Thanks: https://github.com/uupaa/dynamic-import-polyfill | ||
152 | function importModule (path: string) { | ||
153 | return new Promise((resolve, reject) => { | ||
154 | const vector = '$importModule$' + Math.random().toString(32).slice(2) | ||
155 | const script = document.createElement('script') | ||
156 | |||
157 | const destructor = () => { | ||
158 | delete window[ vector ] | ||
159 | script.onerror = null | ||
160 | script.onload = null | ||
161 | script.remove() | ||
162 | URL.revokeObjectURL(script.src) | ||
163 | script.src = '' | ||
164 | } | ||
165 | |||
166 | script.defer = true | ||
167 | script.type = 'module' | ||
168 | |||
169 | script.onerror = () => { | ||
170 | reject(new Error(`Failed to import: ${path}`)) | ||
171 | destructor() | ||
172 | } | ||
173 | script.onload = () => { | ||
174 | resolve(window[ vector ]) | ||
175 | destructor() | ||
176 | } | ||
177 | const absURL = (environment.apiUrl || window.location.origin) + path | ||
178 | const loader = `import * as m from "${absURL}"; window.${vector} = m;` // export Module | ||
179 | const blob = new Blob([ loader ], { type: 'text/javascript' }) | ||
180 | script.src = URL.createObjectURL(blob) | ||
181 | |||
182 | document.head.appendChild(script) | ||
183 | }) | ||
184 | } | ||
185 | |||
186 | function isInViewport (el: HTMLElement) { | 151 | function isInViewport (el: HTMLElement) { |
187 | const bounding = el.getBoundingClientRect() | 152 | const bounding = el.getBoundingClientRect() |
188 | return ( | 153 | return ( |
@@ -216,7 +181,6 @@ export { | |||
216 | getAbsoluteEmbedUrl, | 181 | getAbsoluteEmbedUrl, |
217 | objectLineFeedToHtml, | 182 | objectLineFeedToHtml, |
218 | removeElementFromArray, | 183 | removeElementFromArray, |
219 | importModule, | ||
220 | scrollToTop, | 184 | scrollToTop, |
221 | isInViewport, | 185 | isInViewport, |
222 | isXPercentInViewport | 186 | isXPercentInViewport |
diff --git a/client/src/environments/environment.ts b/client/src/environments/environment.ts index 4816e3060..e00523976 100644 --- a/client/src/environments/environment.ts +++ b/client/src/environments/environment.ts | |||
@@ -9,8 +9,8 @@ | |||
9 | import 'core-js/features/reflect' | 9 | import 'core-js/features/reflect' |
10 | 10 | ||
11 | export const environment = { | 11 | export const environment = { |
12 | production: false, | 12 | production: true, |
13 | hmr: false, | 13 | hmr: false, |
14 | apiUrl: 'http://localhost:9000', | 14 | apiUrl: '', |
15 | embedUrl: 'http://localhost:9000' | 15 | embedUrl: '' |
16 | } | 16 | } |
diff --git a/client/src/root-helpers/plugins.ts b/client/src/root-helpers/plugins.ts new file mode 100644 index 000000000..011721761 --- /dev/null +++ b/client/src/root-helpers/plugins.ts | |||
@@ -0,0 +1,81 @@ | |||
1 | import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks' | ||
2 | import { ClientHookName, ClientScript, RegisterClientHookOptions, ServerConfigPlugin, PluginType, clientHookObject } from '../../../shared/models' | ||
3 | import { RegisterClientHelpers } from 'src/types/register-client-option.model' | ||
4 | import { ClientScript as ClientScriptModule } from '../types/client-script.model' | ||
5 | import { importModule } from './utils' | ||
6 | |||
7 | interface HookStructValue extends RegisterClientHookOptions { | ||
8 | plugin: ServerConfigPlugin | ||
9 | clientScript: ClientScript | ||
10 | } | ||
11 | |||
12 | type Hooks = { [ name: string ]: HookStructValue[] } | ||
13 | |||
14 | type PluginInfo = { | ||
15 | plugin: ServerConfigPlugin | ||
16 | clientScript: ClientScript | ||
17 | pluginType: PluginType | ||
18 | isTheme: boolean | ||
19 | } | ||
20 | |||
21 | async function runHook<T> (hooks: Hooks, hookName: ClientHookName, result?: T, params?: any) { | ||
22 | if (!hooks[hookName]) return result | ||
23 | |||
24 | const hookType = getHookType(hookName) | ||
25 | |||
26 | for (const hook of hooks[hookName]) { | ||
27 | console.log('Running hook %s of plugin %s.', hookName, hook.plugin.name) | ||
28 | |||
29 | result = await internalRunHook(hook.handler, hookType, result, params, err => { | ||
30 | console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.clientScript.script, hook.plugin.name, err) | ||
31 | }) | ||
32 | } | ||
33 | |||
34 | return result | ||
35 | } | ||
36 | |||
37 | function loadPlugin (hooks: Hooks, pluginInfo: PluginInfo, peertubeHelpersFactory: (pluginInfo: PluginInfo) => RegisterClientHelpers) { | ||
38 | const { plugin, clientScript } = pluginInfo | ||
39 | |||
40 | const registerHook = (options: RegisterClientHookOptions) => { | ||
41 | if (clientHookObject[options.target] !== true) { | ||
42 | console.error('Unknown hook %s of plugin %s. Skipping.', options.target, plugin.name) | ||
43 | return | ||
44 | } | ||
45 | |||
46 | if (!hooks[options.target]) hooks[options.target] = [] | ||
47 | |||
48 | hooks[options.target].push({ | ||
49 | plugin, | ||
50 | clientScript, | ||
51 | target: options.target, | ||
52 | handler: options.handler, | ||
53 | priority: options.priority || 0 | ||
54 | }) | ||
55 | } | ||
56 | |||
57 | const peertubeHelpers = peertubeHelpersFactory(pluginInfo) | ||
58 | |||
59 | console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name) | ||
60 | |||
61 | return importModule(clientScript.script) | ||
62 | .then((script: ClientScriptModule) => script.register({ registerHook, peertubeHelpers })) | ||
63 | .then(() => sortHooksByPriority(hooks)) | ||
64 | .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err)) | ||
65 | } | ||
66 | |||
67 | export { | ||
68 | HookStructValue, | ||
69 | Hooks, | ||
70 | PluginInfo, | ||
71 | loadPlugin, | ||
72 | runHook | ||
73 | } | ||
74 | |||
75 | function sortHooksByPriority (hooks: Hooks) { | ||
76 | for (const hookName of Object.keys(hooks)) { | ||
77 | hooks[hookName].sort((a, b) => { | ||
78 | return b.priority - a.priority | ||
79 | }) | ||
80 | } | ||
81 | } | ||
diff --git a/client/src/root-helpers/utils.ts b/client/src/root-helpers/utils.ts index acfb565a3..6df151ad9 100644 --- a/client/src/root-helpers/utils.ts +++ b/client/src/root-helpers/utils.ts | |||
@@ -1,3 +1,5 @@ | |||
1 | import { environment } from '../environments/environment' | ||
2 | |||
1 | function objectToUrlEncoded (obj: any) { | 3 | function objectToUrlEncoded (obj: any) { |
2 | const str: string[] = [] | 4 | const str: string[] = [] |
3 | for (const key of Object.keys(obj)) { | 5 | for (const key of Object.keys(obj)) { |
@@ -7,6 +9,42 @@ function objectToUrlEncoded (obj: any) { | |||
7 | return str.join('&') | 9 | return str.join('&') |
8 | } | 10 | } |
9 | 11 | ||
12 | // Thanks: https://github.com/uupaa/dynamic-import-polyfill | ||
13 | function importModule (path: string) { | ||
14 | return new Promise((resolve, reject) => { | ||
15 | const vector = '$importModule$' + Math.random().toString(32).slice(2) | ||
16 | const script = document.createElement('script') | ||
17 | |||
18 | const destructor = () => { | ||
19 | delete window[ vector ] | ||
20 | script.onerror = null | ||
21 | script.onload = null | ||
22 | script.remove() | ||
23 | URL.revokeObjectURL(script.src) | ||
24 | script.src = '' | ||
25 | } | ||
26 | |||
27 | script.defer = true | ||
28 | script.type = 'module' | ||
29 | |||
30 | script.onerror = () => { | ||
31 | reject(new Error(`Failed to import: ${path}`)) | ||
32 | destructor() | ||
33 | } | ||
34 | script.onload = () => { | ||
35 | resolve(window[ vector ]) | ||
36 | destructor() | ||
37 | } | ||
38 | const absURL = (environment.apiUrl || window.location.origin) + path | ||
39 | const loader = `import * as m from "${absURL}"; window.${vector} = m;` // export Module | ||
40 | const blob = new Blob([ loader ], { type: 'text/javascript' }) | ||
41 | script.src = URL.createObjectURL(blob) | ||
42 | |||
43 | document.head.appendChild(script) | ||
44 | }) | ||
45 | } | ||
46 | |||
10 | export { | 47 | export { |
48 | importModule, | ||
11 | objectToUrlEncoded | 49 | objectToUrlEncoded |
12 | } | 50 | } |
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index adba32a31..fe65794f7 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -1,7 +1,5 @@ | |||
1 | import './embed.scss' | 1 | import './embed.scss' |
2 | import videojs from 'video.js' | 2 | import videojs from 'video.js' |
3 | import { objectToUrlEncoded, peertubeLocalStorage } from '@root-helpers/index' | ||
4 | import { Tokens } from '@root-helpers/users' | ||
5 | import { peertubeTranslate } from '../../../../shared/core-utils/i18n' | 3 | import { peertubeTranslate } from '../../../../shared/core-utils/i18n' |
6 | import { | 4 | import { |
7 | ResultList, | 5 | ResultList, |
@@ -11,12 +9,19 @@ import { | |||
11 | VideoDetails, | 9 | VideoDetails, |
12 | VideoPlaylist, | 10 | VideoPlaylist, |
13 | VideoPlaylistElement, | 11 | VideoPlaylistElement, |
14 | VideoStreamingPlaylistType | 12 | VideoStreamingPlaylistType, |
13 | PluginType, | ||
14 | ClientHookName | ||
15 | } from '../../../../shared/models' | 15 | } from '../../../../shared/models' |
16 | import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager' | 16 | import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager' |
17 | import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' | 17 | import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' |
18 | import { TranslationsManager } from '../../assets/player/translations-manager' | 18 | import { TranslationsManager } from '../../assets/player/translations-manager' |
19 | import { Hooks, loadPlugin, runHook } from '../../root-helpers/plugins' | ||
20 | import { Tokens } from '../../root-helpers/users' | ||
21 | import { peertubeLocalStorage } from '../../root-helpers/peertube-web-storage' | ||
22 | import { objectToUrlEncoded } from '../../root-helpers/utils' | ||
19 | import { PeerTubeEmbedApi } from './embed-api' | 23 | import { PeerTubeEmbedApi } from './embed-api' |
24 | import { RegisterClientHelpers } from '../../types/register-client-option.model' | ||
20 | 25 | ||
21 | type Translations = { [ id: string ]: string } | 26 | type Translations = { [ id: string ]: string } |
22 | 27 | ||
@@ -60,6 +65,9 @@ export class PeerTubeEmbed { | |||
60 | 65 | ||
61 | private wrapperElement: HTMLElement | 66 | private wrapperElement: HTMLElement |
62 | 67 | ||
68 | private peertubeHooks: Hooks = {} | ||
69 | private loadedScripts = new Set<string>() | ||
70 | |||
63 | static async main () { | 71 | static async main () { |
64 | const videoContainerId = 'video-wrapper' | 72 | const videoContainerId = 'video-wrapper' |
65 | const embed = new PeerTubeEmbed(videoContainerId) | 73 | const embed = new PeerTubeEmbed(videoContainerId) |
@@ -473,6 +481,8 @@ export class PeerTubeEmbed { | |||
473 | this.PeertubePlayerManagerModulePromise | 481 | this.PeertubePlayerManagerModulePromise |
474 | ]) | 482 | ]) |
475 | 483 | ||
484 | await this.ensurePluginsAreLoaded(config, serverTranslations) | ||
485 | |||
476 | const videoInfo: VideoDetails = videoInfoTmp | 486 | const videoInfo: VideoDetails = videoInfoTmp |
477 | 487 | ||
478 | const PeertubePlayerManager = PeertubePlayerManagerModule.PeertubePlayerManager | 488 | const PeertubePlayerManager = PeertubePlayerManagerModule.PeertubePlayerManager |
@@ -577,6 +587,8 @@ export class PeerTubeEmbed { | |||
577 | this.playNextVideo() | 587 | this.playNextVideo() |
578 | }) | 588 | }) |
579 | } | 589 | } |
590 | |||
591 | this.runHook('action:embed.player.loaded', undefined, { player: this.player }) | ||
580 | } | 592 | } |
581 | 593 | ||
582 | private async initCore () { | 594 | private async initCore () { |
@@ -714,6 +726,69 @@ export class PeerTubeEmbed { | |||
714 | private isPlaylistEmbed () { | 726 | private isPlaylistEmbed () { |
715 | return window.location.pathname.split('/')[1] === 'video-playlists' | 727 | return window.location.pathname.split('/')[1] === 'video-playlists' |
716 | } | 728 | } |
729 | |||
730 | private async ensurePluginsAreLoaded (config: ServerConfig, translations?: { [ id: string ]: string }) { | ||
731 | if (config.plugin.registered.length === 0) return | ||
732 | |||
733 | for (const plugin of config.plugin.registered) { | ||
734 | for (const key of Object.keys(plugin.clientScripts)) { | ||
735 | const clientScript = plugin.clientScripts[key] | ||
736 | |||
737 | if (clientScript.scopes.includes('embed') === false) continue | ||
738 | |||
739 | const script = `/plugins/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}` | ||
740 | |||
741 | if (this.loadedScripts.has(script)) continue | ||
742 | |||
743 | const pluginInfo = { | ||
744 | plugin, | ||
745 | clientScript: { | ||
746 | script, | ||
747 | scopes: clientScript.scopes | ||
748 | }, | ||
749 | pluginType: PluginType.PLUGIN, | ||
750 | isTheme: false | ||
751 | } | ||
752 | |||
753 | await loadPlugin(this.peertubeHooks, pluginInfo, _ => this.buildPeerTubeHelpers(translations)) | ||
754 | } | ||
755 | } | ||
756 | } | ||
757 | |||
758 | private buildPeerTubeHelpers (translations?: { [ id: string ]: string }): RegisterClientHelpers { | ||
759 | function unimplemented (): any { | ||
760 | throw new Error('This helper is not implemented in embed.') | ||
761 | } | ||
762 | |||
763 | return { | ||
764 | getBaseStaticRoute: unimplemented, | ||
765 | |||
766 | getSettings: unimplemented, | ||
767 | |||
768 | isLoggedIn: unimplemented, | ||
769 | |||
770 | notifier: { | ||
771 | info: unimplemented, | ||
772 | error: unimplemented, | ||
773 | success: unimplemented | ||
774 | }, | ||
775 | |||
776 | showModal: unimplemented, | ||
777 | |||
778 | markdownRenderer: { | ||
779 | textMarkdownToHTML: unimplemented, | ||
780 | enhancedMarkdownToHTML: unimplemented | ||
781 | }, | ||
782 | |||
783 | translate: (value: string) => { | ||
784 | return Promise.resolve(peertubeTranslate(value, translations)) | ||
785 | } | ||
786 | } | ||
787 | } | ||
788 | |||
789 | private runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> { | ||
790 | return runHook(this.peertubeHooks, hookName, result, params) | ||
791 | } | ||
717 | } | 792 | } |
718 | 793 | ||
719 | PeerTubeEmbed.main() | 794 | PeerTubeEmbed.main() |
diff --git a/shared/models/plugins/client-hook.model.ts b/shared/models/plugins/client-hook.model.ts index b53b8de99..193a3f646 100644 --- a/shared/models/plugins/client-hook.model.ts +++ b/shared/models/plugins/client-hook.model.ts | |||
@@ -80,7 +80,13 @@ export const clientActionHookObject = { | |||
80 | 'action:router.navigation-end': true, | 80 | 'action:router.navigation-end': true, |
81 | 81 | ||
82 | // Fired when the registration page is being initialized | 82 | // Fired when the registration page is being initialized |
83 | 'action:signup.register.init': true | 83 | 'action:signup.register.init': true, |
84 | |||
85 | // ####### Embed hooks ####### | ||
86 | // In embed scope, peertube helpers are not available | ||
87 | |||
88 | // Fired when the embed loaded the player | ||
89 | 'action:embed.player.loaded': true | ||
84 | } | 90 | } |
85 | 91 | ||
86 | export type ClientActionHookName = keyof typeof clientActionHookObject | 92 | export type ClientActionHookName = keyof typeof clientActionHookObject |
diff --git a/shared/models/plugins/plugin-client-scope.type.ts b/shared/models/plugins/plugin-client-scope.type.ts index d112434e8..a3c669fe7 100644 --- a/shared/models/plugins/plugin-client-scope.type.ts +++ b/shared/models/plugins/plugin-client-scope.type.ts | |||
@@ -1 +1 @@ | |||
export type PluginClientScope = 'common' | 'video-watch' | 'search' | 'signup' | 'login' | export type PluginClientScope = 'common' | 'video-watch' | 'search' | 'signup' | 'login' | 'embed' | ||