aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/core/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/core/plugins')
-rw-r--r--client/src/app/core/plugins/hooks.service.ts44
-rw-r--r--client/src/app/core/plugins/plugin.service.ts60
2 files changed, 76 insertions, 28 deletions
diff --git a/client/src/app/core/plugins/hooks.service.ts b/client/src/app/core/plugins/hooks.service.ts
new file mode 100644
index 000000000..80c57869c
--- /dev/null
+++ b/client/src/app/core/plugins/hooks.service.ts
@@ -0,0 +1,44 @@
1import { Injectable } from '@angular/core'
2import { PluginService } from '@app/core/plugins/plugin.service'
3import { ClientActionHookName, ClientFilterHookName } from '@shared/models/plugins/client-hook.model'
4import { from, Observable } from 'rxjs'
5import { mergeMap, switchMap } from 'rxjs/operators'
6import { ServerService } from '@app/core/server'
7import { PluginClientScope } from '@shared/models/plugins/plugin-client-scope.type'
8
9type RawFunction<U, T> = (params: U) => T
10type ObservableFunction<U, T> = RawFunction<U, Observable<T>>
11
12@Injectable()
13export class HooksService {
14 constructor (
15 private server: ServerService,
16 private pluginService: PluginService
17 ) { }
18
19 wrapObject<T, U extends ClientFilterHookName> (result: T, hookName: U) {
20 return this.pluginService.runHook(hookName, result)
21 }
22
23 wrapObsFun
24 <P, R, H1 extends ClientFilterHookName, H2 extends ClientFilterHookName>
25 (fun: ObservableFunction<P, R>, params: P, scope: PluginClientScope, hookParamName: H1, hookResultName: H2) {
26 return from(this.pluginService.ensurePluginsAreLoaded(scope))
27 .pipe(
28 mergeMap(() => this.wrapObject(params, hookParamName)),
29 switchMap(params => fun(params)),
30 mergeMap(result => this.pluginService.runHook(hookResultName, result, params))
31 )
32 }
33
34 async wrapFun<U, T, V extends ClientFilterHookName> (fun: RawFunction<U, T>, params: U, hookName: V) {
35 const result = fun(params)
36
37 return this.pluginService.runHook(hookName, result, params)
38 }
39
40 runAction<T, U extends ClientActionHookName> (hookName: U, params?: T) {
41 this.pluginService.runHook(hookName, params)
42 .catch((err: any) => console.error('Fatal hook error.', { err }))
43 }
44}
diff --git a/client/src/app/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts
index af330c2eb..14310f093 100644
--- a/client/src/app/core/plugins/plugin.service.ts
+++ b/client/src/app/core/plugins/plugin.service.ts
@@ -3,11 +3,13 @@ import { Router } from '@angular/router'
3import { ServerConfigPlugin } from '@shared/models' 3import { ServerConfigPlugin } from '@shared/models'
4import { ServerService } from '@app/core/server/server.service' 4import { ServerService } from '@app/core/server/server.service'
5import { ClientScript } from '@shared/models/plugins/plugin-package-json.model' 5import { ClientScript } from '@shared/models/plugins/plugin-package-json.model'
6import { PluginScope } from '@shared/models/plugins/plugin-scope.type'
7import { environment } from '../../../environments/environment' 6import { environment } from '../../../environments/environment'
8import { RegisterHookOptions } from '@shared/models/plugins/register-hook.model' 7import { RegisterHookOptions } from '@shared/models/plugins/register-hook.model'
9import { ReplaySubject } from 'rxjs' 8import { ReplaySubject } from 'rxjs'
10import { first, shareReplay } from 'rxjs/operators' 9import { first, shareReplay } from 'rxjs/operators'
10import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks'
11import { ClientHook, ClientHookName } from '@shared/models/plugins/client-hook.model'
12import { PluginClientScope } from '@shared/models/plugins/plugin-client-scope.type'
11 13
12interface HookStructValue extends RegisterHookOptions { 14interface HookStructValue extends RegisterHookOptions {
13 plugin: ServerConfigPlugin 15 plugin: ServerConfigPlugin
@@ -21,14 +23,18 @@ type PluginInfo = {
21} 23}
22 24
23@Injectable() 25@Injectable()
24export class PluginService { 26export class PluginService implements ClientHook {
25 pluginsLoaded = new ReplaySubject<boolean>(1) 27 pluginsBuilt = new ReplaySubject<boolean>(1)
28
29 pluginsLoaded: { [ scope in PluginClientScope ]: ReplaySubject<boolean> } = {
30 common: new ReplaySubject<boolean>(1),
31 'video-watch': new ReplaySubject<boolean>(1)
32 }
26 33
27 private plugins: ServerConfigPlugin[] = [] 34 private plugins: ServerConfigPlugin[] = []
28 private scopes: { [ scopeName: string ]: PluginInfo[] } = {} 35 private scopes: { [ scopeName: string ]: PluginInfo[] } = {}
29 private loadedPlugins: { [ name: string ]: boolean } = {}
30 private loadedScripts: { [ script: string ]: boolean } = {} 36 private loadedScripts: { [ script: string ]: boolean } = {}
31 private loadedScopes: PluginScope[] = [] 37 private loadedScopes: PluginClientScope[] = []
32 38
33 private hooks: { [ name: string ]: HookStructValue[] } = {} 39 private hooks: { [ name: string ]: HookStructValue[] } = {}
34 40
@@ -45,12 +51,18 @@ export class PluginService {
45 51
46 this.buildScopeStruct() 52 this.buildScopeStruct()
47 53
48 this.pluginsLoaded.next(true) 54 this.pluginsBuilt.next(true)
49 }) 55 })
50 } 56 }
51 57
52 ensurePluginsAreLoaded () { 58 ensurePluginsAreBuilt () {
53 return this.pluginsLoaded.asObservable() 59 return this.pluginsBuilt.asObservable()
60 .pipe(first(), shareReplay())
61 .toPromise()
62 }
63
64 ensurePluginsAreLoaded (scope: PluginClientScope) {
65 return this.pluginsLoaded[scope].asObservable()
54 .pipe(first(), shareReplay()) 66 .pipe(first(), shareReplay())
55 .toPromise() 67 .toPromise()
56 } 68 }
@@ -90,9 +102,9 @@ export class PluginService {
90 } 102 }
91 } 103 }
92 104
93 async loadPluginsByScope (scope: PluginScope, isReload = false) { 105 async loadPluginsByScope (scope: PluginClientScope, isReload = false) {
94 try { 106 try {
95 await this.ensurePluginsAreLoaded() 107 await this.ensurePluginsAreBuilt()
96 108
97 if (!isReload) this.loadedScopes.push(scope) 109 if (!isReload) this.loadedScopes.push(scope)
98 110
@@ -111,32 +123,24 @@ export class PluginService {
111 } 123 }
112 124
113 await Promise.all(promises) 125 await Promise.all(promises)
126
127 this.pluginsLoaded[scope].next(true)
114 } catch (err) { 128 } catch (err) {
115 console.error('Cannot load plugins by scope %s.', scope, err) 129 console.error('Cannot load plugins by scope %s.', scope, err)
116 } 130 }
117 } 131 }
118 132
119 async runHook (hookName: string, param?: any) { 133 async runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> {
120 let result = param 134 if (!this.hooks[hookName]) return Promise.resolve(result)
121
122 if (!this.hooks[hookName]) return result
123 135
124 const wait = hookName.startsWith('static:') 136 const hookType = getHookType(hookName)
125 137
126 for (const hook of this.hooks[hookName]) { 138 for (const hook of this.hooks[hookName]) {
127 try { 139 console.log('Running hook %s of plugin %s.', hookName, hook.plugin.name)
128 const p = hook.handler(param) 140
129 141 result = await internalRunHook(hook.handler, hookType, result, params, err => {
130 if (wait) { 142 console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.clientScript.script, hook.plugin.name, err)
131 result = await p 143 })
132 } else if (p.catch) {
133 p.catch((err: Error) => {
134 console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.plugin, hook.clientScript, err)
135 })
136 }
137 } catch (err) {
138 console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.plugin, hook.clientScript, err)
139 }
140 } 144 }
141 145
142 return result 146 return result