aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app')
-rw-r--r--client/src/app/core/plugins/plugin.service.ts79
-rw-r--r--client/src/app/helpers/utils.ts36
2 files changed, 9 insertions, 106 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'
7import { MarkdownService } from '@app/core/renderer' 7import { MarkdownService } from '@app/core/renderer'
8import { RestExtractor } from '@app/core/rest' 8import { RestExtractor } from '@app/core/rest'
9import { ServerService } from '@app/core/server/server.service' 9import { ServerService } from '@app/core/server/server.service'
10import { getDevLocale, importModule, isOnDevLocale } from '@app/helpers' 10import { getDevLocale, isOnDevLocale } from '@app/helpers'
11import { CustomModalComponent } from '@app/modal/custom-modal.component' 11import { CustomModalComponent } from '@app/modal/custom-modal.component'
12import { Hooks, loadPlugin, PluginInfo, runHook } from '@root-helpers/plugins'
12import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n' 13import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n'
13import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks'
14import { 14import {
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'
26import { environment } from '../../../environments/environment' 23import { environment } from '../../../environments/environment'
27import { ClientScript as ClientScriptModule } from '../../../types/client-script.model'
28import { RegisterClientHelpers } from '../../../types/register-client-option.model' 24import { RegisterClientHelpers } from '../../../types/register-client-option.model'
29 25
30interface HookStructValue extends RegisterClientHookOptions {
31 plugin: ServerConfigPlugin
32 clientScript: ClientScript
33}
34
35type PluginInfo = {
36 plugin: ServerConfigPlugin
37 clientScript: ClientScript
38 pluginType: PluginType
39 isTheme: boolean
40}
41
42@Injectable() 26@Injectable()
43export class PluginService implements ClientHook { 27export 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
152function 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
186function isInViewport (el: HTMLElement) { 151function 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