]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/lib/plugins/register-helpers.ts
Add ability to delete previously added constants
[github/Chocobozzz/PeerTube.git] / server / lib / plugins / register-helpers.ts
1 import * as express from 'express'
2 import { logger } from '@server/helpers/logger'
3 import {
4 VIDEO_CATEGORIES,
5 VIDEO_LANGUAGES,
6 VIDEO_LICENCES,
7 VIDEO_PLAYLIST_PRIVACIES,
8 VIDEO_PRIVACIES
9 } from '@server/initializers/constants'
10 import { onExternalUserAuthenticated } from '@server/lib/auth/external-auth'
11 import { PluginModel } from '@server/models/server/plugin'
12 import {
13 RegisterServerAuthExternalOptions,
14 RegisterServerAuthExternalResult,
15 RegisterServerAuthPassOptions,
16 RegisterServerExternalAuthenticatedResult,
17 RegisterServerOptions
18 } from '@server/types/plugins'
19 import {
20 EncoderOptionsBuilder,
21 PluginPlaylistPrivacyManager,
22 PluginSettingsManager,
23 PluginStorageManager,
24 PluginVideoCategoryManager,
25 PluginVideoLanguageManager,
26 PluginVideoLicenceManager,
27 PluginVideoPrivacyManager,
28 RegisterServerHookOptions,
29 RegisterServerSettingOptions,
30 serverHookObject
31 } from '@shared/models'
32 import { VideoTranscodingProfilesManager } from '../transcoding/video-transcoding-profiles'
33 import { buildPluginHelpers } from './plugin-helpers-builder'
34
35 type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy'
36 type VideoConstant = { [key in number | string]: string }
37
38 type UpdatedVideoConstant = {
39 [name in AlterableVideoConstant]: {
40 [ npmName: string]: {
41 added: { key: number | string, label: string }[]
42 deleted: { key: number | string, label: string }[]
43 }
44 }
45 }
46
47 export class RegisterHelpers {
48 private readonly updatedVideoConstants: UpdatedVideoConstant = {
49 playlistPrivacy: { },
50 privacy: { },
51 language: { },
52 licence: { },
53 category: { }
54 }
55
56 private readonly transcodingProfiles: {
57 [ npmName: string ]: {
58 type: 'vod' | 'live'
59 encoder: string
60 profile: string
61 }[]
62 } = {}
63
64 private readonly transcodingEncoders: {
65 [ npmName: string ]: {
66 type: 'vod' | 'live'
67 streamType: 'audio' | 'video'
68 encoder: string
69 priority: number
70 }[]
71 } = {}
72
73 private readonly settings: RegisterServerSettingOptions[] = []
74
75 private idAndPassAuths: RegisterServerAuthPassOptions[] = []
76 private externalAuths: RegisterServerAuthExternalOptions[] = []
77
78 private readonly onSettingsChangeCallbacks: ((settings: any) => Promise<any>)[] = []
79
80 private readonly router: express.Router
81
82 constructor (
83 private readonly npmName: string,
84 private readonly plugin: PluginModel,
85 private readonly onHookAdded: (options: RegisterServerHookOptions) => void
86 ) {
87 this.router = express.Router()
88 }
89
90 buildRegisterHelpers (): RegisterServerOptions {
91 const registerHook = this.buildRegisterHook()
92 const registerSetting = this.buildRegisterSetting()
93
94 const getRouter = this.buildGetRouter()
95
96 const settingsManager = this.buildSettingsManager()
97 const storageManager = this.buildStorageManager()
98
99 const videoLanguageManager = this.buildVideoLanguageManager()
100
101 const videoLicenceManager = this.buildVideoLicenceManager()
102 const videoCategoryManager = this.buildVideoCategoryManager()
103
104 const videoPrivacyManager = this.buildVideoPrivacyManager()
105 const playlistPrivacyManager = this.buildPlaylistPrivacyManager()
106
107 const transcodingManager = this.buildTranscodingManager()
108
109 const registerIdAndPassAuth = this.buildRegisterIdAndPassAuth()
110 const registerExternalAuth = this.buildRegisterExternalAuth()
111 const unregisterIdAndPassAuth = this.buildUnregisterIdAndPassAuth()
112 const unregisterExternalAuth = this.buildUnregisterExternalAuth()
113
114 const peertubeHelpers = buildPluginHelpers(this.plugin, this.npmName)
115
116 return {
117 registerHook,
118 registerSetting,
119
120 getRouter,
121
122 settingsManager,
123 storageManager,
124
125 videoLanguageManager,
126 videoCategoryManager,
127 videoLicenceManager,
128
129 videoPrivacyManager,
130 playlistPrivacyManager,
131
132 transcodingManager,
133
134 registerIdAndPassAuth,
135 registerExternalAuth,
136 unregisterIdAndPassAuth,
137 unregisterExternalAuth,
138
139 peertubeHelpers
140 }
141 }
142
143 reinitVideoConstants (npmName: string) {
144 const hash = {
145 language: VIDEO_LANGUAGES,
146 licence: VIDEO_LICENCES,
147 category: VIDEO_CATEGORIES,
148 privacy: VIDEO_PRIVACIES,
149 playlistPrivacy: VIDEO_PLAYLIST_PRIVACIES
150 }
151 const types: AlterableVideoConstant[] = [ 'language', 'licence', 'category', 'privacy', 'playlistPrivacy' ]
152
153 for (const type of types) {
154 const updatedConstants = this.updatedVideoConstants[type][npmName]
155 if (!updatedConstants) continue
156
157 for (const added of updatedConstants.added) {
158 delete hash[type][added.key]
159 }
160
161 for (const deleted of updatedConstants.deleted) {
162 hash[type][deleted.key] = deleted.label
163 }
164
165 delete this.updatedVideoConstants[type][npmName]
166 }
167 }
168
169 reinitTranscodingProfilesAndEncoders (npmName: string) {
170 const profiles = this.transcodingProfiles[npmName]
171 if (Array.isArray(profiles)) {
172 for (const profile of profiles) {
173 VideoTranscodingProfilesManager.Instance.removeProfile(profile)
174 }
175 }
176
177 const encoders = this.transcodingEncoders[npmName]
178 if (Array.isArray(encoders)) {
179 for (const o of encoders) {
180 VideoTranscodingProfilesManager.Instance.removeEncoderPriority(o.type, o.streamType, o.encoder, o.priority)
181 }
182 }
183 }
184
185 getSettings () {
186 return this.settings
187 }
188
189 getRouter () {
190 return this.router
191 }
192
193 getIdAndPassAuths () {
194 return this.idAndPassAuths
195 }
196
197 getExternalAuths () {
198 return this.externalAuths
199 }
200
201 getOnSettingsChangedCallbacks () {
202 return this.onSettingsChangeCallbacks
203 }
204
205 private buildGetRouter () {
206 return () => this.router
207 }
208
209 private buildRegisterSetting () {
210 return (options: RegisterServerSettingOptions) => {
211 this.settings.push(options)
212 }
213 }
214
215 private buildRegisterHook () {
216 return (options: RegisterServerHookOptions) => {
217 if (serverHookObject[options.target] !== true) {
218 logger.warn('Unknown hook %s of plugin %s. Skipping.', options.target, this.npmName)
219 return
220 }
221
222 return this.onHookAdded(options)
223 }
224 }
225
226 private buildRegisterIdAndPassAuth () {
227 return (options: RegisterServerAuthPassOptions) => {
228 if (!options.authName || typeof options.getWeight !== 'function' || typeof options.login !== 'function') {
229 logger.error('Cannot register auth plugin %s: authName, getWeight or login are not valid.', this.npmName, { options })
230 return
231 }
232
233 this.idAndPassAuths.push(options)
234 }
235 }
236
237 private buildRegisterExternalAuth () {
238 const self = this
239
240 return (options: RegisterServerAuthExternalOptions) => {
241 if (!options.authName || typeof options.authDisplayName !== 'function' || typeof options.onAuthRequest !== 'function') {
242 logger.error('Cannot register auth plugin %s: authName, authDisplayName or onAuthRequest are not valid.', this.npmName, { options })
243 return
244 }
245
246 this.externalAuths.push(options)
247
248 return {
249 userAuthenticated (result: RegisterServerExternalAuthenticatedResult): void {
250 onExternalUserAuthenticated({
251 npmName: self.npmName,
252 authName: options.authName,
253 authResult: result
254 }).catch(err => {
255 logger.error('Cannot execute onExternalUserAuthenticated.', { npmName: self.npmName, authName: options.authName, err })
256 })
257 }
258 } as RegisterServerAuthExternalResult
259 }
260 }
261
262 private buildUnregisterExternalAuth () {
263 return (authName: string) => {
264 this.externalAuths = this.externalAuths.filter(a => a.authName !== authName)
265 }
266 }
267
268 private buildUnregisterIdAndPassAuth () {
269 return (authName: string) => {
270 this.idAndPassAuths = this.idAndPassAuths.filter(a => a.authName !== authName)
271 }
272 }
273
274 private buildSettingsManager (): PluginSettingsManager {
275 return {
276 getSetting: (name: string) => PluginModel.getSetting(this.plugin.name, this.plugin.type, name, this.settings),
277
278 getSettings: (names: string[]) => PluginModel.getSettings(this.plugin.name, this.plugin.type, names, this.settings),
279
280 setSetting: (name: string, value: string) => PluginModel.setSetting(this.plugin.name, this.plugin.type, name, value),
281
282 onSettingsChange: (cb: (settings: any) => Promise<any>) => this.onSettingsChangeCallbacks.push(cb)
283 }
284 }
285
286 private buildStorageManager (): PluginStorageManager {
287 return {
288 getData: (key: string) => PluginModel.getData(this.plugin.name, this.plugin.type, key),
289
290 storeData: (key: string, data: any) => PluginModel.storeData(this.plugin.name, this.plugin.type, key, data)
291 }
292 }
293
294 private buildVideoLanguageManager (): PluginVideoLanguageManager {
295 return {
296 addLanguage: (key: string, label: string) => {
297 return this.addConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key, label })
298 },
299
300 deleteLanguage: (key: string) => {
301 return this.deleteConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key })
302 }
303 }
304 }
305
306 private buildVideoCategoryManager (): PluginVideoCategoryManager {
307 return {
308 addCategory: (key: number, label: string) => {
309 return this.addConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key, label })
310 },
311
312 deleteCategory: (key: number) => {
313 return this.deleteConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key })
314 }
315 }
316 }
317
318 private buildVideoPrivacyManager (): PluginVideoPrivacyManager {
319 return {
320 deletePrivacy: (key: number) => {
321 return this.deleteConstant({ npmName: this.npmName, type: 'privacy', obj: VIDEO_PRIVACIES, key })
322 }
323 }
324 }
325
326 private buildPlaylistPrivacyManager (): PluginPlaylistPrivacyManager {
327 return {
328 deletePlaylistPrivacy: (key: number) => {
329 return this.deleteConstant({ npmName: this.npmName, type: 'playlistPrivacy', obj: VIDEO_PLAYLIST_PRIVACIES, key })
330 }
331 }
332 }
333
334 private buildVideoLicenceManager (): PluginVideoLicenceManager {
335 return {
336 addLicence: (key: number, label: string) => {
337 return this.addConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key, label })
338 },
339
340 deleteLicence: (key: number) => {
341 return this.deleteConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key })
342 }
343 }
344 }
345
346 private addConstant<T extends string | number> (parameters: {
347 npmName: string
348 type: AlterableVideoConstant
349 obj: VideoConstant
350 key: T
351 label: string
352 }) {
353 const { npmName, type, obj, key, label } = parameters
354
355 if (obj[key]) {
356 logger.warn('Cannot add %s %s by plugin %s: key already exists.', type, npmName, key)
357 return false
358 }
359
360 if (!this.updatedVideoConstants[type][npmName]) {
361 this.updatedVideoConstants[type][npmName] = {
362 added: [],
363 deleted: []
364 }
365 }
366
367 this.updatedVideoConstants[type][npmName].added.push({ key, label })
368 obj[key] = label
369
370 return true
371 }
372
373 private deleteConstant<T extends string | number> (parameters: {
374 npmName: string
375 type: AlterableVideoConstant
376 obj: VideoConstant
377 key: T
378 }) {
379 const { npmName, type, obj, key } = parameters
380
381 if (!obj[key]) {
382 logger.warn('Cannot delete %s by plugin %s: key %s does not exist.', type, npmName, key)
383 return false
384 }
385
386 if (!this.updatedVideoConstants[type][npmName]) {
387 this.updatedVideoConstants[type][npmName] = {
388 added: [],
389 deleted: []
390 }
391 }
392
393 const updatedConstants = this.updatedVideoConstants[type][npmName]
394
395 const alreadyAdded = updatedConstants.added.find(a => a.key === key)
396 if (alreadyAdded) {
397 updatedConstants.added.filter(a => a.key !== key)
398 } else if (obj[key]) {
399 updatedConstants.deleted.push({ key, label: obj[key] })
400 }
401
402 delete obj[key]
403
404 return true
405 }
406
407 private buildTranscodingManager () {
408 const self = this
409
410 function addProfile (type: 'live' | 'vod', encoder: string, profile: string, builder: EncoderOptionsBuilder) {
411 if (profile === 'default') {
412 logger.error('A plugin cannot add a default live transcoding profile')
413 return false
414 }
415
416 VideoTranscodingProfilesManager.Instance.addProfile({
417 type,
418 encoder,
419 profile,
420 builder
421 })
422
423 if (!self.transcodingProfiles[self.npmName]) self.transcodingProfiles[self.npmName] = []
424 self.transcodingProfiles[self.npmName].push({ type, encoder, profile })
425
426 return true
427 }
428
429 function addEncoderPriority (type: 'live' | 'vod', streamType: 'audio' | 'video', encoder: string, priority: number) {
430 VideoTranscodingProfilesManager.Instance.addEncoderPriority(type, streamType, encoder, priority)
431
432 if (!self.transcodingEncoders[self.npmName]) self.transcodingEncoders[self.npmName] = []
433 self.transcodingEncoders[self.npmName].push({ type, streamType, encoder, priority })
434 }
435
436 return {
437 addLiveProfile (encoder: string, profile: string, builder: EncoderOptionsBuilder) {
438 return addProfile('live', encoder, profile, builder)
439 },
440
441 addVODProfile (encoder: string, profile: string, builder: EncoderOptionsBuilder) {
442 return addProfile('vod', encoder, profile, builder)
443 },
444
445 addLiveEncoderPriority (streamType: 'audio' | 'video', encoder: string, priority: number) {
446 return addEncoderPriority('live', streamType, encoder, priority)
447 },
448
449 addVODEncoderPriority (streamType: 'audio' | 'video', encoder: string, priority: number) {
450 return addEncoderPriority('vod', streamType, encoder, priority)
451 },
452
453 removeAllProfilesAndEncoderPriorities () {
454 return self.reinitTranscodingProfilesAndEncoders(self.npmName)
455 }
456 }
457 }
458 }