cdcbcb528ec8931ffb677e3e6f9b2525b28aadd8
[oweals/peertube.git] / client / src / app / core / server / server.service.ts
1 import { first, map, share, shareReplay, switchMap, tap } from 'rxjs/operators'
2 import { HttpClient } from '@angular/common/http'
3 import { Inject, Injectable, LOCALE_ID } from '@angular/core'
4 import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage'
5 import { Observable, of, Subject } from 'rxjs'
6 import { getCompleteLocale, ServerConfig } from '../../../../../shared'
7 import { environment } from '../../../environments/environment'
8 import { VideoConstant } from '../../../../../shared/models/videos'
9 import { isDefaultLocale, peertubeTranslate } from '../../../../../shared/models/i18n'
10 import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils'
11 import { sortBy } from '@app/shared/misc/utils'
12
13 @Injectable()
14 export class ServerService {
15   private static BASE_CONFIG_URL = environment.apiUrl + '/api/v1/config/'
16   private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
17   private static BASE_VIDEO_PLAYLIST_URL = environment.apiUrl + '/api/v1/video-playlists/'
18   private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/'
19   private static CONFIG_LOCAL_STORAGE_KEY = 'server-config'
20
21   configReloaded = new Subject<void>()
22
23   private localeObservable: Observable<any>
24   private videoLicensesObservable: Observable<VideoConstant<number>[]>
25   private videoCategoriesObservable: Observable<VideoConstant<number>[]>
26   private videoPrivaciesObservable: Observable<VideoConstant<number>[]>
27   private videoPlaylistPrivaciesObservable: Observable<VideoConstant<number>[]>
28   private videoLanguagesObservable: Observable<VideoConstant<string>[]>
29   private configObservable: Observable<ServerConfig>
30
31   private configReset = false
32
33   private configLoaded = false
34   private config: ServerConfig = {
35     instance: {
36       name: 'PeerTube',
37       shortDescription: 'PeerTube, a federated (ActivityPub) video streaming platform  ' +
38                         'using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.',
39       defaultClientRoute: '',
40       isNSFW: false,
41       defaultNSFWPolicy: 'do_not_list' as 'do_not_list',
42       customizations: {
43         javascript: '',
44         css: ''
45       }
46     },
47     plugin: {
48       registered: []
49     },
50     theme: {
51       registered: [],
52       default: 'default'
53     },
54     email: {
55       enabled: false
56     },
57     contactForm: {
58       enabled: false
59     },
60     serverVersion: 'Unknown',
61     signup: {
62       allowed: false,
63       allowedForCurrentIP: false,
64       requiresEmailVerification: false
65     },
66     transcoding: {
67       enabledResolutions: [],
68       hls: {
69         enabled: false
70       },
71       webtorrent: {
72         enabled: true
73       }
74     },
75     avatar: {
76       file: {
77         size: { max: 0 },
78         extensions: []
79       }
80     },
81     video: {
82       image: {
83         size: { max: 0 },
84         extensions: []
85       },
86       file: {
87         extensions: []
88       }
89     },
90     videoCaption: {
91       file: {
92         size: { max: 0 },
93         extensions: []
94       }
95     },
96     user: {
97       videoQuota: -1,
98       videoQuotaDaily: -1
99     },
100     import: {
101       videos: {
102         http: {
103           enabled: false
104         },
105         torrent: {
106           enabled: false
107         }
108       }
109     },
110     trending: {
111       videos: {
112         intervalDays: 0
113       }
114     },
115     autoBlacklist: {
116       videos: {
117         ofUsers: {
118           enabled: false
119         }
120       }
121     },
122     tracker: {
123       enabled: true
124     },
125     followings: {
126       instance: {
127         autoFollowIndex: {
128           indexUrl: 'https://instances.joinpeertube.org'
129         }
130       }
131     }
132   }
133
134   constructor (
135     private http: HttpClient,
136     @Inject(LOCALE_ID) private localeId: string
137   ) {
138     this.loadConfigLocally()
139   }
140
141   getServerVersionAndCommit () {
142     const serverVersion = this.config.serverVersion
143     const commit = this.config.serverCommit || ''
144
145     let result = serverVersion
146     if (commit) result += '...' + commit
147
148     return result
149   }
150
151   resetConfig () {
152     this.configLoaded = false
153     this.configReset = true
154   }
155
156   getConfig () {
157     if (this.configLoaded) return of(this.config)
158
159     if (!this.configObservable) {
160       this.configObservable = this.http.get<ServerConfig>(ServerService.BASE_CONFIG_URL)
161                                   .pipe(
162                                     tap(this.saveConfigLocally),
163                                     tap(() => this.configLoaded = true),
164                                     tap(() => {
165                                       if (this.configReset) {
166                                         this.configReloaded.next()
167                                         this.configReset = false
168                                       }
169                                     }),
170                                     share()
171                                   )
172     }
173
174     return this.configObservable
175   }
176
177   getTmpConfig () {
178     return this.config
179   }
180
181   getVideoCategories () {
182     if (!this.videoCategoriesObservable) {
183       this.videoCategoriesObservable = this.loadAttributeEnum<number>(ServerService.BASE_VIDEO_URL, 'categories', true)
184     }
185
186     return this.videoCategoriesObservable.pipe(first())
187   }
188
189   getVideoLicences () {
190     if (!this.videoLicensesObservable) {
191       this.videoLicensesObservable = this.loadAttributeEnum<number>(ServerService.BASE_VIDEO_URL, 'licences')
192     }
193
194     return this.videoLicensesObservable.pipe(first())
195   }
196
197   getVideoLanguages () {
198     if (!this.videoLanguagesObservable) {
199       this.videoLanguagesObservable = this.loadAttributeEnum<string>(ServerService.BASE_VIDEO_URL, 'languages', true)
200     }
201
202     return this.videoLanguagesObservable.pipe(first())
203   }
204
205   getVideoPrivacies () {
206     if (!this.videoPrivaciesObservable) {
207       this.videoPrivaciesObservable = this.loadAttributeEnum<number>(ServerService.BASE_VIDEO_URL, 'privacies')
208     }
209
210     return this.videoPrivaciesObservable.pipe(first())
211   }
212
213   getVideoPlaylistPrivacies () {
214     if (!this.videoPlaylistPrivaciesObservable) {
215       this.videoPlaylistPrivaciesObservable = this.loadAttributeEnum<number>(ServerService.BASE_VIDEO_PLAYLIST_URL, 'privacies')
216     }
217
218     return this.videoPlaylistPrivaciesObservable.pipe(first())
219   }
220
221   getServerLocale () {
222     if (!this.localeObservable) {
223       const completeLocale = isOnDevLocale() ? getDevLocale() : getCompleteLocale(this.localeId)
224
225       // Default locale, nothing to translate
226       if (isDefaultLocale(completeLocale)) {
227         this.localeObservable = of({}).pipe(shareReplay())
228       } else {
229         this.localeObservable = this.http
230                                     .get(ServerService.BASE_LOCALE_URL + completeLocale + '/server.json')
231                                     .pipe(shareReplay())
232       }
233     }
234
235     return this.localeObservable.pipe(first())
236   }
237
238   private loadAttributeEnum <T extends string | number> (
239     baseUrl: string,
240     attributeName: 'categories' | 'licences' | 'languages' | 'privacies',
241     sort = false
242   ) {
243     return this.getServerLocale()
244                .pipe(
245                  switchMap(translations => {
246                    return this.http.get<{ [ id: string ]: string }>(baseUrl + attributeName)
247                               .pipe(map(data => ({ data, translations })))
248                  }),
249                  map(({ data, translations }) => {
250                    const hashToPopulate: VideoConstant<T>[] = []
251
252                    Object.keys(data)
253                          .forEach(dataKey => {
254                            const label = data[ dataKey ]
255
256                            hashToPopulate.push({
257                              id: (attributeName === 'languages' ? dataKey : parseInt(dataKey, 10)) as T,
258                              label: peertubeTranslate(label, translations)
259                            })
260                          })
261
262                    if (sort === true) sortBy(hashToPopulate, 'label')
263
264                    return hashToPopulate
265                  }),
266                  shareReplay()
267                )
268   }
269
270   private saveConfigLocally (config: ServerConfig) {
271     peertubeLocalStorage.setItem(ServerService.CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config))
272   }
273
274   private loadConfigLocally () {
275     const configString = peertubeLocalStorage.getItem(ServerService.CONFIG_LOCAL_STORAGE_KEY)
276
277     if (configString) {
278       try {
279         const parsed = JSON.parse(configString)
280         Object.assign(this.config, parsed)
281       } catch (err) {
282         console.error('Cannot parse config saved in local storage.', err)
283       }
284     }
285   }
286 }