diff options
Diffstat (limited to 'client/src/app')
-rw-r--r-- | client/src/app/core/plugins/plugin.service.ts | 79 | ||||
-rw-r--r-- | client/src/app/helpers/utils.ts | 36 |
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' | |||
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 |