From 23bdacf8ec24ce47a15529830e116911d7478598 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 25 Jul 2019 19:02:54 +0200 Subject: [PATCH] Add setting helper to client plugins --- .../plugin-list-installed.component.ts | 12 +++--- .../plugins/shared/plugin-api.service.ts | 42 +++++++------------ client/src/app/app.component.ts | 4 ++ client/src/app/core/plugins/hooks.service.ts | 2 +- client/src/app/core/plugins/plugin.service.ts | 41 +++++++++++++++++- .../src/types/register-client-option.model.ts | 2 + server/lib/plugins/plugin-manager.ts | 1 + shared/models/plugins/client-hook.model.ts | 5 ++- 8 files changed, 74 insertions(+), 35 deletions(-) diff --git a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts index 9809759db..dced14dee 100644 --- a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts +++ b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts @@ -7,6 +7,7 @@ import { ConfirmService, Notifier } from '@app/core' import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model' import { ActivatedRoute, Router } from '@angular/router' import { compareSemVer } from '@shared/core-utils/miscs/miscs' +import { PluginService } from '@app/core/plugins/plugin.service' @Component({ selector: 'my-plugin-list-installed', @@ -34,13 +35,14 @@ export class PluginListInstalledComponent implements OnInit { constructor ( private i18n: I18n, - private pluginService: PluginApiService, + private pluginService: PluginService, + private pluginApiService: PluginApiService, private notifier: Notifier, private confirmService: ConfirmService, private router: Router, private route: ActivatedRoute ) { - this.pluginTypeOptions = this.pluginService.getPluginTypeOptions() + this.pluginTypeOptions = this.pluginApiService.getPluginTypeOptions() } ngOnInit () { @@ -60,7 +62,7 @@ export class PluginListInstalledComponent implements OnInit { } loadMorePlugins () { - this.pluginService.getPlugins(this.pluginType, this.pagination, this.sort) + this.pluginApiService.getPlugins(this.pluginType, this.pagination, this.sort) .subscribe( res => { this.plugins = this.plugins.concat(res.data) @@ -106,7 +108,7 @@ export class PluginListInstalledComponent implements OnInit { ) if (res === false) return - this.pluginService.uninstall(plugin.name, plugin.type) + this.pluginApiService.uninstall(plugin.name, plugin.type) .subscribe( () => { this.notifier.success(this.i18n('{{pluginName}} uninstalled.', { pluginName: plugin.name })) @@ -125,7 +127,7 @@ export class PluginListInstalledComponent implements OnInit { this.updating[updatingKey] = true - this.pluginService.update(plugin.name, plugin.type) + this.pluginApiService.update(plugin.name, plugin.type) .pipe() .subscribe( res => { diff --git a/client/src/app/+admin/plugins/shared/plugin-api.service.ts b/client/src/app/+admin/plugins/shared/plugin-api.service.ts index bfcaec011..343eb57b2 100644 --- a/client/src/app/+admin/plugins/shared/plugin-api.service.ts +++ b/client/src/app/+admin/plugins/shared/plugin-api.service.ts @@ -12,16 +12,18 @@ import { ManagePlugin } from '@shared/models/plugins/manage-plugin.model' import { InstallOrUpdatePlugin } from '@shared/models/plugins/install-plugin.model' import { PeerTubePluginIndex } from '@shared/models/plugins/peertube-plugin-index.model' import { RegisterServerSettingOptions } from '@shared/models/plugins/register-server-setting.model' +import { PluginService } from '@app/core/plugins/plugin.service' @Injectable() export class PluginApiService { - private static BASE_APPLICATION_URL = environment.apiUrl + '/api/v1/plugins' + private static BASE_PLUGIN_URL = environment.apiUrl + '/api/v1/plugins' constructor ( private authHttp: HttpClient, private restExtractor: RestExtractor, private restService: RestService, - private i18n: I18n + private i18n: I18n, + private pluginService: PluginService ) { } getPluginTypeOptions () { @@ -56,7 +58,7 @@ export class PluginApiService { params = this.restService.addRestGetParams(params, pagination, sort) params = params.append('pluginType', pluginType.toString()) - return this.authHttp.get>(PluginApiService.BASE_APPLICATION_URL, { params }) + return this.authHttp.get>(PluginApiService.BASE_PLUGIN_URL, { params }) .pipe(catchError(res => this.restExtractor.handleError(res))) } @@ -74,26 +76,28 @@ export class PluginApiService { if (search) params = params.append('search', search) - return this.authHttp.get>(PluginApiService.BASE_APPLICATION_URL + '/available', { params }) + return this.authHttp.get>(PluginApiService.BASE_PLUGIN_URL + '/available', { params }) .pipe(catchError(res => this.restExtractor.handleError(res))) } getPlugin (npmName: string) { - const path = PluginApiService.BASE_APPLICATION_URL + '/' + npmName + const path = PluginApiService.BASE_PLUGIN_URL + '/' + npmName return this.authHttp.get(path) .pipe(catchError(res => this.restExtractor.handleError(res))) } getPluginRegisteredSettings (pluginName: string, pluginType: PluginType) { - const path = PluginApiService.BASE_APPLICATION_URL + '/' + this.nameToNpmName(pluginName, pluginType) + '/registered-settings' + const npmName = this.pluginService.nameToNpmName(pluginName, pluginType) + const path = PluginApiService.BASE_PLUGIN_URL + '/' + npmName + '/registered-settings' return this.authHttp.get<{ settings: RegisterServerSettingOptions[] }>(path) .pipe(catchError(res => this.restExtractor.handleError(res))) } updatePluginSettings (pluginName: string, pluginType: PluginType, settings: any) { - const path = PluginApiService.BASE_APPLICATION_URL + '/' + this.nameToNpmName(pluginName, pluginType) + '/settings' + const npmName = this.pluginService.nameToNpmName(pluginName, pluginType) + const path = PluginApiService.BASE_PLUGIN_URL + '/' + npmName + '/settings' return this.authHttp.put(path, { settings }) .pipe(catchError(res => this.restExtractor.handleError(res))) @@ -101,19 +105,19 @@ export class PluginApiService { uninstall (pluginName: string, pluginType: PluginType) { const body: ManagePlugin = { - npmName: this.nameToNpmName(pluginName, pluginType) + npmName: this.pluginService.nameToNpmName(pluginName, pluginType) } - return this.authHttp.post(PluginApiService.BASE_APPLICATION_URL + '/uninstall', body) + return this.authHttp.post(PluginApiService.BASE_PLUGIN_URL + '/uninstall', body) .pipe(catchError(res => this.restExtractor.handleError(res))) } update (pluginName: string, pluginType: PluginType) { const body: ManagePlugin = { - npmName: this.nameToNpmName(pluginName, pluginType) + npmName: this.pluginService.nameToNpmName(pluginName, pluginType) } - return this.authHttp.post(PluginApiService.BASE_APPLICATION_URL + '/update', body) + return this.authHttp.post(PluginApiService.BASE_PLUGIN_URL + '/update', body) .pipe(catchError(res => this.restExtractor.handleError(res))) } @@ -122,21 +126,7 @@ export class PluginApiService { npmName } - return this.authHttp.post(PluginApiService.BASE_APPLICATION_URL + '/install', body) + return this.authHttp.post(PluginApiService.BASE_PLUGIN_URL + '/install', body) .pipe(catchError(res => this.restExtractor.handleError(res))) } - - nameToNpmName (name: string, type: PluginType) { - const prefix = type === PluginType.PLUGIN - ? 'peertube-plugin-' - : 'peertube-theme-' - - return prefix + name - } - - pluginTypeFromNpmName (npmName: string) { - return npmName.startsWith('peertube-plugin-') - ? PluginType.PLUGIN - : PluginType.THEME - } } diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 14fd27784..db1f91f8c 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -162,6 +162,10 @@ export class AppComponent implements OnInit { filter(pathname => !pathname || pathname === '/' || is18nPath(pathname)) ).subscribe(() => this.redirectService.redirectToHomepage(true)) + navigationEndEvent.subscribe(e => { + this.hooks.runAction('action:router.navigation-end', 'common', { path: e.url }) + }) + eventsObs.pipe( filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart), filter(() => this.screenService.isInSmallView()) diff --git a/client/src/app/core/plugins/hooks.service.ts b/client/src/app/core/plugins/hooks.service.ts index 257e27e6b..93dac1167 100644 --- a/client/src/app/core/plugins/hooks.service.ts +++ b/client/src/app/core/plugins/hooks.service.ts @@ -39,7 +39,7 @@ export class HooksService { runAction (hookName: U, scope: PluginClientScope, params?: T) { this.pluginService.ensurePluginsAreLoaded(scope) - .then(() => this.pluginService.runHook(hookName, params)) + .then(() => this.pluginService.runHook(hookName, undefined, 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 1294edd7d..45d8088a4 100644 --- a/client/src/app/core/plugins/plugin.service.ts +++ b/client/src/app/core/plugins/plugin.service.ts @@ -6,11 +6,15 @@ import { ClientScript } from '@shared/models/plugins/plugin-package-json.model' import { ClientScript as ClientScriptModule } from '../../../types/client-script.model' import { environment } from '../../../environments/environment' import { ReplaySubject } from 'rxjs' -import { first, shareReplay } from 'rxjs/operators' +import { catchError, first, map, shareReplay } from 'rxjs/operators' import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks' import { ClientHook, ClientHookName, clientHookObject } from '@shared/models/plugins/client-hook.model' import { PluginClientScope } from '@shared/models/plugins/plugin-client-scope.type' import { RegisterClientHookOptions } from '@shared/models/plugins/register-client-hook.model' +import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model' +import { HttpClient } from '@angular/common/http' +import { RestExtractor } from '@app/shared/rest' +import { PluginType } from '@shared/models/plugins/plugin.type' interface HookStructValue extends RegisterClientHookOptions { plugin: ServerConfigPlugin @@ -20,11 +24,14 @@ interface HookStructValue extends RegisterClientHookOptions { type PluginInfo = { plugin: ServerConfigPlugin clientScript: ClientScript + pluginType: PluginType isTheme: boolean } @Injectable() export class PluginService implements ClientHook { + private static BASE_PLUGIN_URL = environment.apiUrl + '/api/v1/plugins' + pluginsBuilt = new ReplaySubject(1) pluginsLoaded: { [ scope in PluginClientScope ]: ReplaySubject } = { @@ -43,7 +50,9 @@ export class PluginService implements ClientHook { constructor ( private router: Router, - private server: ServerService + private server: ServerService, + private authHttp: HttpClient, + private restExtractor: RestExtractor ) { } @@ -87,6 +96,7 @@ export class PluginService implements ClientHook { script: environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}`, scopes: clientScript.scopes }, + pluginType: isTheme ? PluginType.THEME : PluginType.PLUGIN, isTheme }) @@ -162,6 +172,20 @@ export class PluginService implements ClientHook { return result } + nameToNpmName (name: string, type: PluginType) { + const prefix = type === PluginType.PLUGIN + ? 'peertube-plugin-' + : 'peertube-theme-' + + return prefix + name + } + + pluginTypeFromNpmName (npmName: string) { + return npmName.startsWith('peertube-plugin-') + ? PluginType.PLUGIN + : PluginType.THEME + } + private loadPlugin (pluginInfo: PluginInfo) { const { plugin, clientScript } = pluginInfo @@ -189,6 +213,7 @@ export class PluginService implements ClientHook { return import(/* webpackIgnore: true */ clientScript.script) .then((script: ClientScriptModule) => script.register({ registerHook, peertubeHelpers })) .then(() => this.sortHooksByPriority()) + .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err)) } private buildScopeStruct () { @@ -212,6 +237,18 @@ export class PluginService implements ClientHook { getBaseStaticRoute: () => { const pathPrefix = this.getPluginPathPrefix(pluginInfo.isTheme) return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/static` + }, + + getSettings: () => { + const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) + const path = PluginService.BASE_PLUGIN_URL + '/' + npmName + + return this.authHttp.get(path) + .pipe( + map(p => p.settings), + catchError(res => this.restExtractor.handleError(res)) + ) + .toPromise() } } } diff --git a/client/src/types/register-client-option.model.ts b/client/src/types/register-client-option.model.ts index 42d689403..473c2500f 100644 --- a/client/src/types/register-client-option.model.ts +++ b/client/src/types/register-client-option.model.ts @@ -5,5 +5,7 @@ export type RegisterClientOptions = { peertubeHelpers: { getBaseStaticRoute: () => string + + getSettings: () => Promise<{ [ name: string ]: string }> } } diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index 381a89473..78e8d758f 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts @@ -151,6 +151,7 @@ export class PluginManager implements ServerHook { } delete this.registeredPlugins[plugin.npmName] + delete this.settings[plugin.npmName] if (plugin.type === PluginType.PLUGIN) { await plugin.unregister() diff --git a/shared/models/plugins/client-hook.model.ts b/shared/models/plugins/client-hook.model.ts index 87e8092c0..cfa2653c6 100644 --- a/shared/models/plugins/client-hook.model.ts +++ b/shared/models/plugins/client-hook.model.ts @@ -49,7 +49,10 @@ export const clientActionHookObject = { 'action:video-watch.video.loaded': true, // Fired when the search page is being initialized - 'action:search.init': true + 'action:search.init': true, + + // Fired every time Angular URL changes + 'action:router.navigation-end': true } export type ClientActionHookName = keyof typeof clientActionHookObject -- 2.41.0