From 93cae47925e4dd68b7d34a41927b2740b4fab1b4 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 22 Jul 2019 15:40:13 +0200 Subject: Add client hooks --- client/src/app/core/core.module.ts | 2 + client/src/app/core/plugins/hooks.service.ts | 44 ++++++++++++++++++++ client/src/app/core/plugins/plugin.service.ts | 60 ++++++++++++++------------- 3 files changed, 78 insertions(+), 28 deletions(-) create mode 100644 client/src/app/core/plugins/hooks.service.ts (limited to 'client/src/app/core') diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts index 436c0dfb8..5943af4da 100644 --- a/client/src/app/core/core.module.ts +++ b/client/src/app/core/core.module.ts @@ -22,6 +22,7 @@ import { UserNotificationSocket } from '@app/core/notification/user-notification import { ServerConfigResolver } from './routing/server-config-resolver.service' import { UnloggedGuard } from '@app/core/routing/unlogged-guard.service' import { PluginService } from '@app/core/plugins/plugin.service' +import { HooksService } from '@app/core/plugins/hooks.service' @NgModule({ imports: [ @@ -63,6 +64,7 @@ import { PluginService } from '@app/core/plugins/plugin.service' UnloggedGuard, PluginService, + HooksService, RedirectService, Notifier, 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 @@ +import { Injectable } from '@angular/core' +import { PluginService } from '@app/core/plugins/plugin.service' +import { ClientActionHookName, ClientFilterHookName } from '@shared/models/plugins/client-hook.model' +import { from, Observable } from 'rxjs' +import { mergeMap, switchMap } from 'rxjs/operators' +import { ServerService } from '@app/core/server' +import { PluginClientScope } from '@shared/models/plugins/plugin-client-scope.type' + +type RawFunction = (params: U) => T +type ObservableFunction = RawFunction> + +@Injectable() +export class HooksService { + constructor ( + private server: ServerService, + private pluginService: PluginService + ) { } + + wrapObject (result: T, hookName: U) { + return this.pluginService.runHook(hookName, result) + } + + wrapObsFun + + (fun: ObservableFunction, params: P, scope: PluginClientScope, hookParamName: H1, hookResultName: H2) { + return from(this.pluginService.ensurePluginsAreLoaded(scope)) + .pipe( + mergeMap(() => this.wrapObject(params, hookParamName)), + switchMap(params => fun(params)), + mergeMap(result => this.pluginService.runHook(hookResultName, result, params)) + ) + } + + async wrapFun (fun: RawFunction, params: U, hookName: V) { + const result = fun(params) + + return this.pluginService.runHook(hookName, result, params) + } + + runAction (hookName: U, params?: T) { + this.pluginService.runHook(hookName, params) + .catch((err: any) => console.error('Fatal hook error.', { err })) + } +} 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' import { ServerConfigPlugin } from '@shared/models' import { ServerService } from '@app/core/server/server.service' import { ClientScript } from '@shared/models/plugins/plugin-package-json.model' -import { PluginScope } from '@shared/models/plugins/plugin-scope.type' import { environment } from '../../../environments/environment' import { RegisterHookOptions } from '@shared/models/plugins/register-hook.model' import { ReplaySubject } from 'rxjs' import { first, shareReplay } from 'rxjs/operators' +import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks' +import { ClientHook, ClientHookName } from '@shared/models/plugins/client-hook.model' +import { PluginClientScope } from '@shared/models/plugins/plugin-client-scope.type' interface HookStructValue extends RegisterHookOptions { plugin: ServerConfigPlugin @@ -21,14 +23,18 @@ type PluginInfo = { } @Injectable() -export class PluginService { - pluginsLoaded = new ReplaySubject(1) +export class PluginService implements ClientHook { + pluginsBuilt = new ReplaySubject(1) + + pluginsLoaded: { [ scope in PluginClientScope ]: ReplaySubject } = { + common: new ReplaySubject(1), + 'video-watch': new ReplaySubject(1) + } private plugins: ServerConfigPlugin[] = [] private scopes: { [ scopeName: string ]: PluginInfo[] } = {} - private loadedPlugins: { [ name: string ]: boolean } = {} private loadedScripts: { [ script: string ]: boolean } = {} - private loadedScopes: PluginScope[] = [] + private loadedScopes: PluginClientScope[] = [] private hooks: { [ name: string ]: HookStructValue[] } = {} @@ -45,12 +51,18 @@ export class PluginService { this.buildScopeStruct() - this.pluginsLoaded.next(true) + this.pluginsBuilt.next(true) }) } - ensurePluginsAreLoaded () { - return this.pluginsLoaded.asObservable() + ensurePluginsAreBuilt () { + return this.pluginsBuilt.asObservable() + .pipe(first(), shareReplay()) + .toPromise() + } + + ensurePluginsAreLoaded (scope: PluginClientScope) { + return this.pluginsLoaded[scope].asObservable() .pipe(first(), shareReplay()) .toPromise() } @@ -90,9 +102,9 @@ export class PluginService { } } - async loadPluginsByScope (scope: PluginScope, isReload = false) { + async loadPluginsByScope (scope: PluginClientScope, isReload = false) { try { - await this.ensurePluginsAreLoaded() + await this.ensurePluginsAreBuilt() if (!isReload) this.loadedScopes.push(scope) @@ -111,32 +123,24 @@ export class PluginService { } await Promise.all(promises) + + this.pluginsLoaded[scope].next(true) } catch (err) { console.error('Cannot load plugins by scope %s.', scope, err) } } - async runHook (hookName: string, param?: any) { - let result = param - - if (!this.hooks[hookName]) return result + async runHook (hookName: ClientHookName, result?: T, params?: any): Promise { + if (!this.hooks[hookName]) return Promise.resolve(result) - const wait = hookName.startsWith('static:') + const hookType = getHookType(hookName) for (const hook of this.hooks[hookName]) { - try { - const p = hook.handler(param) - - if (wait) { - result = await p - } else if (p.catch) { - p.catch((err: Error) => { - console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.plugin, hook.clientScript, err) - }) - } - } catch (err) { - console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.plugin, hook.clientScript, err) - } + 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 -- cgit v1.2.3