aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts12
-rw-r--r--client/src/app/+admin/plugins/shared/plugin-api.service.ts42
-rw-r--r--client/src/app/app.component.ts4
-rw-r--r--client/src/app/core/plugins/hooks.service.ts2
-rw-r--r--client/src/app/core/plugins/plugin.service.ts41
-rw-r--r--client/src/types/register-client-option.model.ts2
-rw-r--r--server/lib/plugins/plugin-manager.ts1
-rw-r--r--shared/models/plugins/client-hook.model.ts5
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'
7import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model' 7import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model'
8import { ActivatedRoute, Router } from '@angular/router' 8import { ActivatedRoute, Router } from '@angular/router'
9import { compareSemVer } from '@shared/core-utils/miscs/miscs' 9import { compareSemVer } from '@shared/core-utils/miscs/miscs'
10import { PluginService } from '@app/core/plugins/plugin.service'
10 11
11@Component({ 12@Component({
12 selector: 'my-plugin-list-installed', 13 selector: 'my-plugin-list-installed',
@@ -34,13 +35,14 @@ export class PluginListInstalledComponent implements OnInit {
34 35
35 constructor ( 36 constructor (
36 private i18n: I18n, 37 private i18n: I18n,
37 private pluginService: PluginApiService, 38 private pluginService: PluginService,
39 private pluginApiService: PluginApiService,
38 private notifier: Notifier, 40 private notifier: Notifier,
39 private confirmService: ConfirmService, 41 private confirmService: ConfirmService,
40 private router: Router, 42 private router: Router,
41 private route: ActivatedRoute 43 private route: ActivatedRoute
42 ) { 44 ) {
43 this.pluginTypeOptions = this.pluginService.getPluginTypeOptions() 45 this.pluginTypeOptions = this.pluginApiService.getPluginTypeOptions()
44 } 46 }
45 47
46 ngOnInit () { 48 ngOnInit () {
@@ -60,7 +62,7 @@ export class PluginListInstalledComponent implements OnInit {
60 } 62 }
61 63
62 loadMorePlugins () { 64 loadMorePlugins () {
63 this.pluginService.getPlugins(this.pluginType, this.pagination, this.sort) 65 this.pluginApiService.getPlugins(this.pluginType, this.pagination, this.sort)
64 .subscribe( 66 .subscribe(
65 res => { 67 res => {
66 this.plugins = this.plugins.concat(res.data) 68 this.plugins = this.plugins.concat(res.data)
@@ -106,7 +108,7 @@ export class PluginListInstalledComponent implements OnInit {
106 ) 108 )
107 if (res === false) return 109 if (res === false) return
108 110
109 this.pluginService.uninstall(plugin.name, plugin.type) 111 this.pluginApiService.uninstall(plugin.name, plugin.type)
110 .subscribe( 112 .subscribe(
111 () => { 113 () => {
112 this.notifier.success(this.i18n('{{pluginName}} uninstalled.', { pluginName: plugin.name })) 114 this.notifier.success(this.i18n('{{pluginName}} uninstalled.', { pluginName: plugin.name }))
@@ -125,7 +127,7 @@ export class PluginListInstalledComponent implements OnInit {
125 127
126 this.updating[updatingKey] = true 128 this.updating[updatingKey] = true
127 129
128 this.pluginService.update(plugin.name, plugin.type) 130 this.pluginApiService.update(plugin.name, plugin.type)
129 .pipe() 131 .pipe()
130 .subscribe( 132 .subscribe(
131 res => { 133 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'
12import { InstallOrUpdatePlugin } from '@shared/models/plugins/install-plugin.model' 12import { InstallOrUpdatePlugin } from '@shared/models/plugins/install-plugin.model'
13import { PeerTubePluginIndex } from '@shared/models/plugins/peertube-plugin-index.model' 13import { PeerTubePluginIndex } from '@shared/models/plugins/peertube-plugin-index.model'
14import { RegisterServerSettingOptions } from '@shared/models/plugins/register-server-setting.model' 14import { RegisterServerSettingOptions } from '@shared/models/plugins/register-server-setting.model'
15import { PluginService } from '@app/core/plugins/plugin.service'
15 16
16@Injectable() 17@Injectable()
17export class PluginApiService { 18export class PluginApiService {
18 private static BASE_APPLICATION_URL = environment.apiUrl + '/api/v1/plugins' 19 private static BASE_PLUGIN_URL = environment.apiUrl + '/api/v1/plugins'
19 20
20 constructor ( 21 constructor (
21 private authHttp: HttpClient, 22 private authHttp: HttpClient,
22 private restExtractor: RestExtractor, 23 private restExtractor: RestExtractor,
23 private restService: RestService, 24 private restService: RestService,
24 private i18n: I18n 25 private i18n: I18n,
26 private pluginService: PluginService
25 ) { } 27 ) { }
26 28
27 getPluginTypeOptions () { 29 getPluginTypeOptions () {
@@ -56,7 +58,7 @@ export class PluginApiService {
56 params = this.restService.addRestGetParams(params, pagination, sort) 58 params = this.restService.addRestGetParams(params, pagination, sort)
57 params = params.append('pluginType', pluginType.toString()) 59 params = params.append('pluginType', pluginType.toString())
58 60
59 return this.authHttp.get<ResultList<PeerTubePlugin>>(PluginApiService.BASE_APPLICATION_URL, { params }) 61 return this.authHttp.get<ResultList<PeerTubePlugin>>(PluginApiService.BASE_PLUGIN_URL, { params })
60 .pipe(catchError(res => this.restExtractor.handleError(res))) 62 .pipe(catchError(res => this.restExtractor.handleError(res)))
61 } 63 }
62 64
@@ -74,26 +76,28 @@ export class PluginApiService {
74 76
75 if (search) params = params.append('search', search) 77 if (search) params = params.append('search', search)
76 78
77 return this.authHttp.get<ResultList<PeerTubePluginIndex>>(PluginApiService.BASE_APPLICATION_URL + '/available', { params }) 79 return this.authHttp.get<ResultList<PeerTubePluginIndex>>(PluginApiService.BASE_PLUGIN_URL + '/available', { params })
78 .pipe(catchError(res => this.restExtractor.handleError(res))) 80 .pipe(catchError(res => this.restExtractor.handleError(res)))
79 } 81 }
80 82
81 getPlugin (npmName: string) { 83 getPlugin (npmName: string) {
82 const path = PluginApiService.BASE_APPLICATION_URL + '/' + npmName 84 const path = PluginApiService.BASE_PLUGIN_URL + '/' + npmName
83 85
84 return this.authHttp.get<PeerTubePlugin>(path) 86 return this.authHttp.get<PeerTubePlugin>(path)
85 .pipe(catchError(res => this.restExtractor.handleError(res))) 87 .pipe(catchError(res => this.restExtractor.handleError(res)))
86 } 88 }
87 89
88 getPluginRegisteredSettings (pluginName: string, pluginType: PluginType) { 90 getPluginRegisteredSettings (pluginName: string, pluginType: PluginType) {
89 const path = PluginApiService.BASE_APPLICATION_URL + '/' + this.nameToNpmName(pluginName, pluginType) + '/registered-settings' 91 const npmName = this.pluginService.nameToNpmName(pluginName, pluginType)
92 const path = PluginApiService.BASE_PLUGIN_URL + '/' + npmName + '/registered-settings'
90 93
91 return this.authHttp.get<{ settings: RegisterServerSettingOptions[] }>(path) 94 return this.authHttp.get<{ settings: RegisterServerSettingOptions[] }>(path)
92 .pipe(catchError(res => this.restExtractor.handleError(res))) 95 .pipe(catchError(res => this.restExtractor.handleError(res)))
93 } 96 }
94 97
95 updatePluginSettings (pluginName: string, pluginType: PluginType, settings: any) { 98 updatePluginSettings (pluginName: string, pluginType: PluginType, settings: any) {
96 const path = PluginApiService.BASE_APPLICATION_URL + '/' + this.nameToNpmName(pluginName, pluginType) + '/settings' 99 const npmName = this.pluginService.nameToNpmName(pluginName, pluginType)
100 const path = PluginApiService.BASE_PLUGIN_URL + '/' + npmName + '/settings'
97 101
98 return this.authHttp.put(path, { settings }) 102 return this.authHttp.put(path, { settings })
99 .pipe(catchError(res => this.restExtractor.handleError(res))) 103 .pipe(catchError(res => this.restExtractor.handleError(res)))
@@ -101,19 +105,19 @@ export class PluginApiService {
101 105
102 uninstall (pluginName: string, pluginType: PluginType) { 106 uninstall (pluginName: string, pluginType: PluginType) {
103 const body: ManagePlugin = { 107 const body: ManagePlugin = {
104 npmName: this.nameToNpmName(pluginName, pluginType) 108 npmName: this.pluginService.nameToNpmName(pluginName, pluginType)
105 } 109 }
106 110
107 return this.authHttp.post(PluginApiService.BASE_APPLICATION_URL + '/uninstall', body) 111 return this.authHttp.post(PluginApiService.BASE_PLUGIN_URL + '/uninstall', body)
108 .pipe(catchError(res => this.restExtractor.handleError(res))) 112 .pipe(catchError(res => this.restExtractor.handleError(res)))
109 } 113 }
110 114
111 update (pluginName: string, pluginType: PluginType) { 115 update (pluginName: string, pluginType: PluginType) {
112 const body: ManagePlugin = { 116 const body: ManagePlugin = {
113 npmName: this.nameToNpmName(pluginName, pluginType) 117 npmName: this.pluginService.nameToNpmName(pluginName, pluginType)
114 } 118 }
115 119
116 return this.authHttp.post(PluginApiService.BASE_APPLICATION_URL + '/update', body) 120 return this.authHttp.post(PluginApiService.BASE_PLUGIN_URL + '/update', body)
117 .pipe(catchError(res => this.restExtractor.handleError(res))) 121 .pipe(catchError(res => this.restExtractor.handleError(res)))
118 } 122 }
119 123
@@ -122,21 +126,7 @@ export class PluginApiService {
122 npmName 126 npmName
123 } 127 }
124 128
125 return this.authHttp.post(PluginApiService.BASE_APPLICATION_URL + '/install', body) 129 return this.authHttp.post(PluginApiService.BASE_PLUGIN_URL + '/install', body)
126 .pipe(catchError(res => this.restExtractor.handleError(res))) 130 .pipe(catchError(res => this.restExtractor.handleError(res)))
127 } 131 }
128
129 nameToNpmName (name: string, type: PluginType) {
130 const prefix = type === PluginType.PLUGIN
131 ? 'peertube-plugin-'
132 : 'peertube-theme-'
133
134 return prefix + name
135 }
136
137 pluginTypeFromNpmName (npmName: string) {
138 return npmName.startsWith('peertube-plugin-')
139 ? PluginType.PLUGIN
140 : PluginType.THEME
141 }
142} 132}
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 {
162 filter(pathname => !pathname || pathname === '/' || is18nPath(pathname)) 162 filter(pathname => !pathname || pathname === '/' || is18nPath(pathname))
163 ).subscribe(() => this.redirectService.redirectToHomepage(true)) 163 ).subscribe(() => this.redirectService.redirectToHomepage(true))
164 164
165 navigationEndEvent.subscribe(e => {
166 this.hooks.runAction('action:router.navigation-end', 'common', { path: e.url })
167 })
168
165 eventsObs.pipe( 169 eventsObs.pipe(
166 filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart), 170 filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart),
167 filter(() => this.screenService.isInSmallView()) 171 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 {
39 39
40 runAction<T, U extends ClientActionHookName> (hookName: U, scope: PluginClientScope, params?: T) { 40 runAction<T, U extends ClientActionHookName> (hookName: U, scope: PluginClientScope, params?: T) {
41 this.pluginService.ensurePluginsAreLoaded(scope) 41 this.pluginService.ensurePluginsAreLoaded(scope)
42 .then(() => this.pluginService.runHook(hookName, params)) 42 .then(() => this.pluginService.runHook(hookName, undefined, params))
43 .catch((err: any) => console.error('Fatal hook error.', { err })) 43 .catch((err: any) => console.error('Fatal hook error.', { err }))
44 } 44 }
45} 45}
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'
6import { ClientScript as ClientScriptModule } from '../../../types/client-script.model' 6import { ClientScript as ClientScriptModule } from '../../../types/client-script.model'
7import { environment } from '../../../environments/environment' 7import { environment } from '../../../environments/environment'
8import { ReplaySubject } from 'rxjs' 8import { ReplaySubject } from 'rxjs'
9import { first, shareReplay } from 'rxjs/operators' 9import { catchError, first, map, shareReplay } from 'rxjs/operators'
10import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks' 10import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks'
11import { ClientHook, ClientHookName, clientHookObject } from '@shared/models/plugins/client-hook.model' 11import { ClientHook, ClientHookName, clientHookObject } from '@shared/models/plugins/client-hook.model'
12import { PluginClientScope } from '@shared/models/plugins/plugin-client-scope.type' 12import { PluginClientScope } from '@shared/models/plugins/plugin-client-scope.type'
13import { RegisterClientHookOptions } from '@shared/models/plugins/register-client-hook.model' 13import { RegisterClientHookOptions } from '@shared/models/plugins/register-client-hook.model'
14import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model'
15import { HttpClient } from '@angular/common/http'
16import { RestExtractor } from '@app/shared/rest'
17import { PluginType } from '@shared/models/plugins/plugin.type'
14 18
15interface HookStructValue extends RegisterClientHookOptions { 19interface HookStructValue extends RegisterClientHookOptions {
16 plugin: ServerConfigPlugin 20 plugin: ServerConfigPlugin
@@ -20,11 +24,14 @@ interface HookStructValue extends RegisterClientHookOptions {
20type PluginInfo = { 24type PluginInfo = {
21 plugin: ServerConfigPlugin 25 plugin: ServerConfigPlugin
22 clientScript: ClientScript 26 clientScript: ClientScript
27 pluginType: PluginType
23 isTheme: boolean 28 isTheme: boolean
24} 29}
25 30
26@Injectable() 31@Injectable()
27export class PluginService implements ClientHook { 32export class PluginService implements ClientHook {
33 private static BASE_PLUGIN_URL = environment.apiUrl + '/api/v1/plugins'
34
28 pluginsBuilt = new ReplaySubject<boolean>(1) 35 pluginsBuilt = new ReplaySubject<boolean>(1)
29 36
30 pluginsLoaded: { [ scope in PluginClientScope ]: ReplaySubject<boolean> } = { 37 pluginsLoaded: { [ scope in PluginClientScope ]: ReplaySubject<boolean> } = {
@@ -43,7 +50,9 @@ export class PluginService implements ClientHook {
43 50
44 constructor ( 51 constructor (
45 private router: Router, 52 private router: Router,
46 private server: ServerService 53 private server: ServerService,
54 private authHttp: HttpClient,
55 private restExtractor: RestExtractor
47 ) { 56 ) {
48 } 57 }
49 58
@@ -87,6 +96,7 @@ export class PluginService implements ClientHook {
87 script: environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}`, 96 script: environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}`,
88 scopes: clientScript.scopes 97 scopes: clientScript.scopes
89 }, 98 },
99 pluginType: isTheme ? PluginType.THEME : PluginType.PLUGIN,
90 isTheme 100 isTheme
91 }) 101 })
92 102
@@ -162,6 +172,20 @@ export class PluginService implements ClientHook {
162 return result 172 return result
163 } 173 }
164 174
175 nameToNpmName (name: string, type: PluginType) {
176 const prefix = type === PluginType.PLUGIN
177 ? 'peertube-plugin-'
178 : 'peertube-theme-'
179
180 return prefix + name
181 }
182
183 pluginTypeFromNpmName (npmName: string) {
184 return npmName.startsWith('peertube-plugin-')
185 ? PluginType.PLUGIN
186 : PluginType.THEME
187 }
188
165 private loadPlugin (pluginInfo: PluginInfo) { 189 private loadPlugin (pluginInfo: PluginInfo) {
166 const { plugin, clientScript } = pluginInfo 190 const { plugin, clientScript } = pluginInfo
167 191
@@ -189,6 +213,7 @@ export class PluginService implements ClientHook {
189 return import(/* webpackIgnore: true */ clientScript.script) 213 return import(/* webpackIgnore: true */ clientScript.script)
190 .then((script: ClientScriptModule) => script.register({ registerHook, peertubeHelpers })) 214 .then((script: ClientScriptModule) => script.register({ registerHook, peertubeHelpers }))
191 .then(() => this.sortHooksByPriority()) 215 .then(() => this.sortHooksByPriority())
216 .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err))
192 } 217 }
193 218
194 private buildScopeStruct () { 219 private buildScopeStruct () {
@@ -212,6 +237,18 @@ export class PluginService implements ClientHook {
212 getBaseStaticRoute: () => { 237 getBaseStaticRoute: () => {
213 const pathPrefix = this.getPluginPathPrefix(pluginInfo.isTheme) 238 const pathPrefix = this.getPluginPathPrefix(pluginInfo.isTheme)
214 return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/static` 239 return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/static`
240 },
241
242 getSettings: () => {
243 const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType)
244 const path = PluginService.BASE_PLUGIN_URL + '/' + npmName
245
246 return this.authHttp.get<PeerTubePlugin>(path)
247 .pipe(
248 map(p => p.settings),
249 catchError(res => this.restExtractor.handleError(res))
250 )
251 .toPromise()
215 } 252 }
216 } 253 }
217 } 254 }
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 = {
5 5
6 peertubeHelpers: { 6 peertubeHelpers: {
7 getBaseStaticRoute: () => string 7 getBaseStaticRoute: () => string
8
9 getSettings: () => Promise<{ [ name: string ]: string }>
8 } 10 }
9} 11}
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 {
151 } 151 }
152 152
153 delete this.registeredPlugins[plugin.npmName] 153 delete this.registeredPlugins[plugin.npmName]
154 delete this.settings[plugin.npmName]
154 155
155 if (plugin.type === PluginType.PLUGIN) { 156 if (plugin.type === PluginType.PLUGIN) {
156 await plugin.unregister() 157 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 = {
49 'action:video-watch.video.loaded': true, 49 'action:video-watch.video.loaded': true,
50 50
51 // Fired when the search page is being initialized 51 // Fired when the search page is being initialized
52 'action:search.init': true 52 'action:search.init': true,
53
54 // Fired every time Angular URL changes
55 'action:router.navigation-end': true
53} 56}
54 57
55export type ClientActionHookName = keyof typeof clientActionHookObject 58export type ClientActionHookName = keyof typeof clientActionHookObject