Add ability for admins to set default p2p policy

pull/4654/head
Chocobozzz 2021-12-15 15:58:10 +01:00
parent c77fdc605b
commit a9bfa85d2c
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
59 changed files with 789 additions and 415 deletions

View File

@ -0,0 +1,21 @@
import { getCheckbox } from '../utils'
export class AnonymousSettingsPage {
async openSettings () {
const link = await $$('.menu-link').filter(async i => {
return await i.getText() === 'My settings'
}).then(links => links[0])
await link.click()
await $('my-user-video-settings').waitForDisplayed()
}
async clickOnP2PCheckbox () {
const p2p = getCheckbox('p2pEnabled')
await p2p.waitForClickable()
await p2p.click()
}
}

View File

@ -1,4 +1,4 @@
import { go } from '../utils' import { getCheckbox, go } from '../utils'
export class MyAccountPage { export class MyAccountPage {
@ -27,6 +27,21 @@ export class MyAccountPage {
await nsfw.scrollIntoView(false) // Avoid issues with fixed header on firefox await nsfw.scrollIntoView(false) // Avoid issues with fixed header on firefox
await nsfw.selectByAttribute('value', newValue) await nsfw.selectByAttribute('value', newValue)
await this.submitVideoSettings()
}
async clickOnP2PCheckbox () {
const p2p = getCheckbox('p2pEnabled')
await p2p.waitForClickable()
await p2p.scrollIntoView(false) // Avoid issues with fixed header on firefox
await p2p.click()
await this.submitVideoSettings()
}
private async submitVideoSettings () {
const submit = $('my-user-video-settings input[type=submit]') const submit = $('my-user-video-settings input[type=submit]')
await submit.scrollIntoView(false) await submit.scrollIntoView(false)
await submit.click() await submit.click()

View File

@ -1,5 +1,5 @@
import { join } from 'path' import { join } from 'path'
import { clickOnCheckbox } from '../utils' import { getCheckbox, selectCustomSelect } from '../utils'
export class VideoUploadPage { export class VideoUploadPage {
async navigateTo () { async navigateTo () {
@ -32,7 +32,7 @@ export class VideoUploadPage {
} }
setAsNSFW () { setAsNSFW () {
return clickOnCheckbox('nsfw') return getCheckbox('nsfw').click()
} }
async validSecondUploadStep (videoName: string) { async validSecondUploadStep (videoName: string) {
@ -47,6 +47,10 @@ export class VideoUploadPage {
}) })
} }
setAsPublic () {
return selectCustomSelect('privacy', 'Public')
}
private getSecondStepSubmitButton () { private getSecondStepSubmitButton () {
return $('.submit-container my-button') return $('.submit-container my-button')
} }

View File

@ -39,12 +39,23 @@ export class VideoWatchPage {
return $('my-video-comment-add').isExisting() return $('my-video-comment-add').isExisting()
} }
isPrivacyWarningDisplayed () {
return $('my-privacy-concerns').isDisplayed()
}
async goOnAssociatedEmbed () { async goOnAssociatedEmbed () {
let url = await browser.getUrl() let url = await browser.getUrl()
url = url.replace('/w/', '/videos/embed/') url = url.replace('/w/', '/videos/embed/')
url = url.replace(':3333', ':9001') url = url.replace(':3333', ':9001')
return go(url) await go(url)
await $('.vjs-big-play-button').waitForDisplayed()
}
async isEmbedWarningDisplayed () {
const text = await $('.vjs-dock-description').getText()
return !!text.trim()
} }
goOnP2PMediaLoaderEmbed () { goOnP2PMediaLoaderEmbed () {

View File

@ -1,5 +1,5 @@
import { LoginPage } from '../po/login.po' import { LoginPage } from '../po/login.po'
import { MyAccountPage } from '../po/my-account' import { MyAccountPage } from '../po/my-account.po'
import { PlayerPage } from '../po/player.po' import { PlayerPage } from '../po/player.po'
import { VideoListPage } from '../po/video-list.po' import { VideoListPage } from '../po/video-list.po'
import { VideoUpdatePage } from '../po/video-update.po' import { VideoUpdatePage } from '../po/video-update.po'

View File

@ -1,7 +1,7 @@
import { LoginPage } from '../po/login.po' import { LoginPage } from '../po/login.po'
import { VideoUploadPage } from '../po/video-upload.po' import { VideoUploadPage } from '../po/video-upload.po'
import { VideoWatchPage } from '../po/video-watch.po' import { VideoWatchPage } from '../po/video-watch.po'
import { isMobileDevice, isSafari, waitServerUp } from '../utils' import { go, isMobileDevice, isSafari, waitServerUp } from '../utils'
describe('Custom server defaults', () => { describe('Custom server defaults', () => {
let videoUploadPage: VideoUploadPage let videoUploadPage: VideoUploadPage
@ -10,9 +10,7 @@ describe('Custom server defaults', () => {
before(async () => { before(async () => {
await waitServerUp() await waitServerUp()
})
beforeEach(async () => {
loginPage = new LoginPage() loginPage = new LoginPage()
videoUploadPage = new VideoUploadPage() videoUploadPage = new VideoUploadPage()
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari()) videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
@ -20,18 +18,69 @@ describe('Custom server defaults', () => {
await browser.maximizeWindow() await browser.maximizeWindow()
}) })
it('Should upload a video with custom default values', async function () { describe('Publish default values', function () {
await loginPage.loginAsRootUser() before(async function () {
await videoUploadPage.navigateTo() await loginPage.loginAsRootUser()
await videoUploadPage.uploadVideo() })
await videoUploadPage.validSecondUploadStep('video')
await videoWatchPage.waitWatchVideoName('video') it('Should upload a video with custom default values', async function () {
await videoUploadPage.navigateTo()
await videoUploadPage.uploadVideo()
await videoUploadPage.validSecondUploadStep('video')
expect(await videoWatchPage.getPrivacy()).toBe('Internal') await videoWatchPage.waitWatchVideoName('video')
expect(await videoWatchPage.getLicence()).toBe('Attribution - Non Commercial')
expect(await videoWatchPage.isDownloadEnabled()).toBeFalsy() expect(await videoWatchPage.getPrivacy()).toBe('Internal')
expect(await videoWatchPage.areCommentsEnabled()).toBeFalsy() expect(await videoWatchPage.getLicence()).toBe('Attribution - Non Commercial')
expect(await videoWatchPage.isDownloadEnabled()).toBeFalsy()
expect(await videoWatchPage.areCommentsEnabled()).toBeFalsy()
})
after(async function () {
await loginPage.logout()
})
}) })
describe('P2P', function () {
let videoUrl: string
async function goOnVideoWatchPage () {
await go(videoUrl)
await videoWatchPage.waitWatchVideoName('video')
}
async function checkP2P (enabled: boolean) {
await goOnVideoWatchPage()
expect(await videoWatchPage.isPrivacyWarningDisplayed()).toEqual(enabled)
await videoWatchPage.goOnAssociatedEmbed()
expect(await videoWatchPage.isEmbedWarningDisplayed()).toEqual(enabled)
}
before(async () => {
await loginPage.loginAsRootUser()
await videoUploadPage.navigateTo()
await videoUploadPage.uploadVideo()
await videoUploadPage.setAsPublic()
await videoUploadPage.validSecondUploadStep('video')
await videoWatchPage.waitWatchVideoName('video')
videoUrl = await browser.getUrl()
})
beforeEach(async function () {
await goOnVideoWatchPage()
})
it('Should have P2P disabled for a logged in user', async function () {
await checkP2P(false)
})
it('Should have P2P disabled for anonymous users', async function () {
await loginPage.logout()
await checkP2P(false)
})
})
}) })

View File

@ -0,0 +1,82 @@
import { AnonymousSettingsPage } from '../po/anonymous-settings.po'
import { LoginPage } from '../po/login.po'
import { MyAccountPage } from '../po/my-account.po'
import { VideoUploadPage } from '../po/video-upload.po'
import { VideoWatchPage } from '../po/video-watch.po'
import { go, isMobileDevice, isSafari, waitServerUp } from '../utils'
describe('User settings', () => {
let videoUploadPage: VideoUploadPage
let loginPage: LoginPage
let videoWatchPage: VideoWatchPage
let myAccountPage: MyAccountPage
let anonymousSettingsPage: AnonymousSettingsPage
before(async () => {
await waitServerUp()
loginPage = new LoginPage()
videoUploadPage = new VideoUploadPage()
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
myAccountPage = new MyAccountPage()
anonymousSettingsPage = new AnonymousSettingsPage()
await browser.maximizeWindow()
})
describe('P2P', function () {
let videoUrl: string
async function goOnVideoWatchPage () {
await go(videoUrl)
await videoWatchPage.waitWatchVideoName('video')
}
async function checkP2P (enabled: boolean) {
await goOnVideoWatchPage()
expect(await videoWatchPage.isPrivacyWarningDisplayed()).toEqual(enabled)
await videoWatchPage.goOnAssociatedEmbed()
expect(await videoWatchPage.isEmbedWarningDisplayed()).toEqual(enabled)
}
before(async () => {
await loginPage.loginAsRootUser()
await videoUploadPage.navigateTo()
await videoUploadPage.uploadVideo()
await videoUploadPage.validSecondUploadStep('video')
await videoWatchPage.waitWatchVideoName('video')
videoUrl = await browser.getUrl()
})
beforeEach(async function () {
await goOnVideoWatchPage()
})
it('Should have P2P enabled for a logged in user', async function () {
await checkP2P(true)
})
it('Should disable P2P for a logged in user', async function () {
await myAccountPage.navigateToMySettings()
await myAccountPage.clickOnP2PCheckbox()
await checkP2P(false)
})
it('Should have P2P enabled for anonymous users', async function () {
await loginPage.logout()
await checkP2P(true)
})
it('Should disable P2P for an anonymous user', async function () {
await anonymousSettingsPage.openSettings()
await anonymousSettingsPage.clickOnP2PCheckbox()
await checkP2P(false)
})
})
})

View File

@ -1,6 +1,6 @@
import { AdminConfigPage } from '../po/admin-config.po' import { AdminConfigPage } from '../po/admin-config.po'
import { LoginPage } from '../po/login.po' import { LoginPage } from '../po/login.po'
import { MyAccountPage } from '../po/my-account' import { MyAccountPage } from '../po/my-account.po'
import { VideoListPage } from '../po/video-list.po' import { VideoListPage } from '../po/video-list.po'
import { VideoSearchPage } from '../po/video-search.po' import { VideoSearchPage } from '../po/video-search.po'
import { VideoUploadPage } from '../po/video-upload.po' import { VideoUploadPage } from '../po/video-upload.po'

View File

@ -1,7 +1,22 @@
function clickOnCheckbox (name: string) { function getCheckbox (name: string) {
return $(`my-peertube-checkbox[inputname=${name}] label`).click() return $(`my-peertube-checkbox[inputname=${name}] label`)
}
async function selectCustomSelect (id: string, valueLabel: string) {
await $(`[formcontrolname=${id}] .ng-arrow-wrapper`).click()
const option = await $$(`[formcontrolname=${id}] .ng-option`).filter(async o => {
const text = await o.getText()
return text.trimStart().startsWith(valueLabel)
}).then(options => options[0])
await option.waitForDisplayed()
return option.click()
} }
export { export {
clickOnCheckbox getCheckbox,
selectCustomSelect
} }

View File

@ -55,6 +55,9 @@ function buildConfig (suiteFile: string = undefined) {
comments_enabled: false, comments_enabled: false,
privacy: 4, privacy: 4,
licence: 4 licence: 4
},
p2p: {
enabled: false
} }
} }
} }

View File

@ -1,9 +1,8 @@
import { Component, Input, OnInit } from '@angular/core' import { Component, Input, OnInit } from '@angular/core'
import { ServerService } from '@app/core' import { ServerService, User, UserService } from '@app/core'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { HTMLServerConfig, Video } from '@shared/models' import { HTMLServerConfig, Video } from '@shared/models'
import { getStoredP2PEnabled } from '../../../../../assets/player/peertube-player-local-storage' import { isP2PEnabled } from '../../../../../assets/player/utils'
import { isWebRTCDisabled } from '../../../../../assets/player/utils'
@Component({ @Component({
selector: 'my-privacy-concerns', selector: 'my-privacy-concerns',
@ -15,33 +14,32 @@ export class PrivacyConcernsComponent implements OnInit {
@Input() video: Video @Input() video: Video
display = true display = false
private serverConfig: HTMLServerConfig private serverConfig: HTMLServerConfig
constructor ( constructor (
private serverService: ServerService private serverService: ServerService,
private userService: UserService
) { } ) { }
ngOnInit () { ngOnInit () {
this.serverConfig = this.serverService.getHTMLConfig() this.serverConfig = this.serverService.getHTMLConfig()
if (isWebRTCDisabled() || this.isTrackerDisabled() || this.isP2PDisabled() || this.alreadyAccepted()) { this.userService.getAnonymousOrLoggedUser()
this.display = false .subscribe(user => this.updateDisplay(user))
}
} }
acceptedPrivacyConcern () { acceptedPrivacyConcern () {
peertubeLocalStorage.setItem(PrivacyConcernsComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'true') peertubeLocalStorage.setItem(PrivacyConcernsComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'true')
this.display = false this.display = false
} }
private isTrackerDisabled () { private updateDisplay (user: User) {
return this.video.isLocal && this.serverConfig.tracker.enabled === false if (isP2PEnabled(this.video, this.serverConfig, user.p2pEnabled) && !this.alreadyAccepted()) {
} this.display = true
}
private isP2PDisabled () {
return getStoredP2PEnabled() === false
} }
private alreadyAccepted () { private alreadyAccepted () {

View File

@ -1,16 +1,9 @@
import { Component, EventEmitter, Input, Output } from '@angular/core' import { Component, EventEmitter, Input, Output } from '@angular/core'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { import { AuthService, ComponentPagination, HooksService, Notifier, SessionStorageService, UserService } from '@app/core'
AuthService,
ComponentPagination,
HooksService,
LocalStorageService,
Notifier,
SessionStorageService,
UserService
} from '@app/core'
import { VideoPlaylist, VideoPlaylistElement, VideoPlaylistService } from '@app/shared/shared-video-playlist' import { VideoPlaylist, VideoPlaylistElement, VideoPlaylistService } from '@app/shared/shared-video-playlist'
import { peertubeLocalStorage, peertubeSessionStorage } from '@root-helpers/peertube-web-storage' import { peertubeSessionStorage } from '@root-helpers/peertube-web-storage'
import { getBoolOrDefault } from '@root-helpers/local-storage-utils'
import { VideoPlaylistPrivacy } from '@shared/models' import { VideoPlaylistPrivacy } from '@shared/models'
@Component({ @Component({
@ -19,8 +12,7 @@ import { VideoPlaylistPrivacy } from '@shared/models'
styleUrls: [ './video-watch-playlist.component.scss' ] styleUrls: [ './video-watch-playlist.component.scss' ]
}) })
export class VideoWatchPlaylistComponent { export class VideoWatchPlaylistComponent {
static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'auto_play_video_playlist' static SESSION_STORAGE_LOOP_PLAYLIST = 'loop_playlist'
static SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'loop_playlist'
@Input() playlist: VideoPlaylist @Input() playlist: VideoPlaylist
@ -47,19 +39,15 @@ export class VideoWatchPlaylistComponent {
private auth: AuthService, private auth: AuthService,
private notifier: Notifier, private notifier: Notifier,
private videoPlaylist: VideoPlaylistService, private videoPlaylist: VideoPlaylistService,
private localStorageService: LocalStorageService,
private sessionStorage: SessionStorageService, private sessionStorage: SessionStorageService,
private router: Router private router: Router
) { ) {
// defaults to true this.userService.getAnonymousOrLoggedUser()
this.autoPlayNextVideoPlaylist = this.auth.isLoggedIn() .subscribe(user => this.autoPlayNextVideoPlaylist = user.autoPlayNextVideoPlaylist)
? this.auth.getUser().autoPlayNextVideoPlaylist
: this.localStorageService.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false'
this.setAutoPlayNextVideoPlaylistSwitchText() this.setAutoPlayNextVideoPlaylistSwitchText()
// defaults to false this.loopPlaylist = getBoolOrDefault(this.sessionStorage.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_LOOP_PLAYLIST), false)
this.loopPlaylist = this.sessionStorage.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true'
this.setLoopPlaylistSwitchText() this.setLoopPlaylistSwitchText()
} }
@ -201,16 +189,9 @@ export class VideoWatchPlaylistComponent {
this.autoPlayNextVideoPlaylist = !this.autoPlayNextVideoPlaylist this.autoPlayNextVideoPlaylist = !this.autoPlayNextVideoPlaylist
this.setAutoPlayNextVideoPlaylistSwitchText() this.setAutoPlayNextVideoPlaylistSwitchText()
peertubeLocalStorage.setItem( const details = { autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist }
VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST,
this.autoPlayNextVideoPlaylist.toString()
)
if (this.auth.isLoggedIn()) { if (this.auth.isLoggedIn()) {
const details = {
autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist
}
this.userService.updateMyProfile(details) this.userService.updateMyProfile(details)
.subscribe({ .subscribe({
next: () => { next: () => {
@ -219,6 +200,8 @@ export class VideoWatchPlaylistComponent {
error: err => this.notifier.error(err.message) error: err => this.notifier.error(err.message)
}) })
} else {
this.userService.updateMyAnonymousProfile(details)
} }
} }
@ -227,7 +210,7 @@ export class VideoWatchPlaylistComponent {
this.setLoopPlaylistSwitchText() this.setLoopPlaylistSwitchText()
peertubeSessionStorage.setItem( peertubeSessionStorage.setItem(
VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST, VideoWatchPlaylistComponent.SESSION_STORAGE_LOOP_PLAYLIST,
this.loopPlaylist.toString() this.loopPlaylist.toString()
) )
} }

View File

@ -1,10 +1,9 @@
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core' import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'
import { AuthService, Notifier, SessionStorageService, User, UserService } from '@app/core' import { AuthService, Notifier, User, UserService } from '@app/core'
import { Video } from '@app/shared/shared-main' import { Video } from '@app/shared/shared-main'
import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature' import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature'
import { VideoPlaylist } from '@app/shared/shared-video-playlist' import { VideoPlaylist } from '@app/shared/shared-video-playlist'
import { UserLocalStorageKeys } from '@root-helpers/users'
import { RecommendationInfo } from './recommendation-info.model' import { RecommendationInfo } from './recommendation-info.model'
import { RecommendedVideosStore } from './recommended-videos.store' import { RecommendedVideosStore } from './recommended-videos.store'
@ -39,24 +38,14 @@ export class RecommendedVideosComponent implements OnInit, OnChanges {
private userService: UserService, private userService: UserService,
private authService: AuthService, private authService: AuthService,
private notifier: Notifier, private notifier: Notifier,
private store: RecommendedVideosStore, private store: RecommendedVideosStore
private sessionStorageService: SessionStorageService
) { ) {
this.videos$ = this.store.recommendations$ this.videos$ = this.store.recommendations$
this.hasVideos$ = this.store.hasRecommendations$ this.hasVideos$ = this.store.hasRecommendations$
this.videos$.subscribe(videos => this.gotRecommendations.emit(videos)) this.videos$.subscribe(videos => this.gotRecommendations.emit(videos))
if (this.authService.isLoggedIn()) { this.userService.getAnonymousOrLoggedUser()
this.autoPlayNextVideo = this.authService.getUser().autoPlayNextVideo .subscribe(user => this.autoPlayNextVideo = user.autoPlayNextVideo)
} else {
this.autoPlayNextVideo = this.sessionStorageService.getItem(UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
this.sessionStorageService.watch([ UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO ]).subscribe(
() => {
this.autoPlayNextVideo = this.sessionStorageService.getItem(UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
}
)
}
this.autoPlayNextVideoTooltip = $localize`When active, the next video is automatically played after the current one.` this.autoPlayNextVideoTooltip = $localize`When active, the next video is automatically played after the current one.`
} }
@ -77,13 +66,9 @@ export class RecommendedVideosComponent implements OnInit, OnChanges {
} }
switchAutoPlayNextVideo () { switchAutoPlayNextVideo () {
this.sessionStorageService.setItem(UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO, this.autoPlayNextVideo.toString()) const details = { autoPlayNextVideo: this.autoPlayNextVideo }
if (this.authService.isLoggedIn()) { if (this.authService.isLoggedIn()) {
const details = {
autoPlayNextVideo: this.autoPlayNextVideo
}
this.userService.updateMyProfile(details) this.userService.updateMyProfile(details)
.subscribe({ .subscribe({
next: () => { next: () => {
@ -92,6 +77,8 @@ export class RecommendedVideosComponent implements OnInit, OnChanges {
error: err => this.notifier.error(err.message) error: err => this.notifier.error(err.message)
}) })
} else {
this.userService.updateMyAnonymousProfile(details)
} }
} }
} }

View File

@ -1,5 +1,6 @@
import { Hotkey, HotkeysService } from 'angular2-hotkeys' import { Hotkey, HotkeysService } from 'angular2-hotkeys'
import { forkJoin, Subscription } from 'rxjs' import { forkJoin, Subscription } from 'rxjs'
import { isP2PEnabled } from 'src/assets/player/utils'
import { PlatformLocation } from '@angular/common' import { PlatformLocation } from '@angular/common'
import { Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' import { Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
@ -14,6 +15,7 @@ import {
RestExtractor, RestExtractor,
ScreenService, ScreenService,
ServerService, ServerService,
User,
UserService UserService
} from '@app/core' } from '@app/core'
import { HooksService } from '@app/core/plugins/hooks.service' import { HooksService } from '@app/core/plugins/hooks.service'
@ -237,31 +239,34 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
'filter:api.video-watch.video.get.result' 'filter:api.video-watch.video.get.result'
) )
forkJoin([ videoObs, this.videoCaptionService.listCaptions(videoId) ]) forkJoin([
.subscribe({ videoObs,
next: ([ video, captionsResult ]) => { this.videoCaptionService.listCaptions(videoId),
const queryParams = this.route.snapshot.queryParams this.userService.getAnonymousOrLoggedUser()
]).subscribe({
next: ([ video, captionsResult, loggedInOrAnonymousUser ]) => {
const queryParams = this.route.snapshot.queryParams
const urlOptions = { const urlOptions = {
resume: queryParams.resume, resume: queryParams.resume,
startTime: queryParams.start, startTime: queryParams.start,
stopTime: queryParams.stop, stopTime: queryParams.stop,
muted: queryParams.muted, muted: queryParams.muted,
loop: queryParams.loop, loop: queryParams.loop,
subtitle: queryParams.subtitle, subtitle: queryParams.subtitle,
playerMode: queryParams.mode, playerMode: queryParams.mode,
peertubeLink: false peertubeLink: false
} }
this.onVideoFetched(video, captionsResult.data, urlOptions) this.onVideoFetched({ video, videoCaptions: captionsResult.data, loggedInOrAnonymousUser, urlOptions })
.catch(err => this.handleGlobalError(err)) .catch(err => this.handleGlobalError(err))
}, },
error: err => this.handleRequestError(err) error: err => this.handleRequestError(err)
}) })
} }
private loadPlaylist (playlistId: string) { private loadPlaylist (playlistId: string) {
@ -323,11 +328,14 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.notifier.error(errorMessage) this.notifier.error(errorMessage)
} }
private async onVideoFetched ( private async onVideoFetched (options: {
video: VideoDetails, video: VideoDetails
videoCaptions: VideoCaption[], videoCaptions: VideoCaption[]
urlOptions: URLOptions urlOptions: URLOptions
) { loggedInOrAnonymousUser: User
}) {
const { video, videoCaptions, urlOptions, loggedInOrAnonymousUser } = options
this.subscribeToLiveEventsIfNeeded(this.video, video) this.subscribeToLiveEventsIfNeeded(this.video, video)
this.video = video this.video = video
@ -346,7 +354,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
if (res === false) return this.location.back() if (res === false) return this.location.back()
} }
this.buildPlayer(urlOptions) this.buildPlayer(urlOptions, loggedInOrAnonymousUser)
.catch(err => console.error('Cannot build the player', err)) .catch(err => console.error('Cannot build the player', err))
this.setOpenGraphTags() this.setOpenGraphTags()
@ -359,7 +367,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.hooks.runAction('action:video-watch.video.loaded', 'video-watch', hookOptions) this.hooks.runAction('action:video-watch.video.loaded', 'video-watch', hookOptions)
} }
private async buildPlayer (urlOptions: URLOptions) { private async buildPlayer (urlOptions: URLOptions, loggedInOrAnonymousUser: User) {
// Flush old player if needed // Flush old player if needed
this.flushPlayer() this.flushPlayer()
@ -380,6 +388,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
video: this.video, video: this.video,
videoCaptions: this.videoCaptions, videoCaptions: this.videoCaptions,
urlOptions, urlOptions,
loggedInOrAnonymousUser,
user: this.user user: this.user
} }
const { playerMode, playerOptions } = await this.hooks.wrapFun( const { playerMode, playerOptions } = await this.hooks.wrapFun(
@ -517,9 +526,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
video: VideoDetails video: VideoDetails
videoCaptions: VideoCaption[] videoCaptions: VideoCaption[]
urlOptions: CustomizationOptions & { playerMode: PlayerMode } urlOptions: CustomizationOptions & { playerMode: PlayerMode }
loggedInOrAnonymousUser: User
user?: AuthUser user?: AuthUser
}) { }) {
const { video, videoCaptions, urlOptions, user } = params const { video, videoCaptions, urlOptions, loggedInOrAnonymousUser, user } = params
const getStartTime = () => { const getStartTime = () => {
const byUrl = urlOptions.startTime !== undefined const byUrl = urlOptions.startTime !== undefined
@ -547,6 +557,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
const options: PeertubePlayerManagerOptions = { const options: PeertubePlayerManagerOptions = {
common: { common: {
autoplay: this.isAutoplay(), autoplay: this.isAutoplay(),
p2pEnabled: isP2PEnabled(video, this.serverConfig, loggedInOrAnonymousUser.p2pEnabled),
nextVideo: () => this.playNextVideoInAngularZone(), nextVideo: () => this.playNextVideoInAngularZone(),
playerElement: this.playerElement, playerElement: this.playerElement,

View File

@ -14,7 +14,8 @@ import {
ScrollService, ScrollService,
ServerService, ServerService,
ThemeService, ThemeService,
User User,
UserLocalStorageService
} from '@app/core' } from '@app/core'
import { HooksService } from '@app/core/plugins/hooks.service' import { HooksService } from '@app/core/plugins/hooks.service'
import { PluginService } from '@app/core/plugins/plugin.service' import { PluginService } from '@app/core/plugins/plugin.service'
@ -70,6 +71,7 @@ export class AppComponent implements OnInit, AfterViewInit {
private ngbConfig: NgbConfig, private ngbConfig: NgbConfig,
private loadingBar: LoadingBarService, private loadingBar: LoadingBarService,
private scrollService: ScrollService, private scrollService: ScrollService,
private userLocalStorage: UserLocalStorageService,
public menu: MenuService public menu: MenuService
) { ) {
this.ngbConfig.animation = false this.ngbConfig.animation = false
@ -86,6 +88,8 @@ export class AppComponent implements OnInit, AfterViewInit {
ngOnInit () { ngOnInit () {
document.getElementById('incompatible-browser').className += ' browser-ok' document.getElementById('incompatible-browser').className += ' browser-ok'
this.loadUser()
this.serverConfig = this.serverService.getHTMLConfig() this.serverConfig = this.serverService.getHTMLConfig()
this.hooks.runAction('action:application.init', 'common') this.hooks.runAction('action:application.init', 'common')
@ -300,4 +304,15 @@ export class AppComponent implements OnInit, AfterViewInit {
}, undefined, $localize`Go to the videos upload page`) }, undefined, $localize`Go to the videos upload page`)
]) ])
} }
private loadUser () {
const tokens = this.userLocalStorage.getTokens()
if (!tokens) return
const user = this.userLocalStorage.getLoggedInUser()
if (!user) return
// Initialize user
this.authService.buildAuthUser(user, tokens)
}
} }

View File

@ -1,13 +1,7 @@
import { Observable, of } from 'rxjs' import { Observable, of } from 'rxjs'
import { map } from 'rxjs/operators' import { map } from 'rxjs/operators'
import { User } from '@app/core/users/user.model' import { User } from '@app/core/users/user.model'
import { import { UserTokens } from '@root-helpers/users'
flushUserInfoFromLocalStorage,
getUserInfoFromLocalStorage,
saveUserInfoIntoLocalStorage,
TokenOptions,
Tokens
} from '@root-helpers/users'
import { hasUserRight } from '@shared/core-utils/users' import { hasUserRight } from '@shared/core-utils/users'
import { import {
MyUser as ServerMyUserModel, MyUser as ServerMyUserModel,
@ -19,31 +13,15 @@ import {
} from '@shared/models' } from '@shared/models'
export class AuthUser extends User implements ServerMyUserModel { export class AuthUser extends User implements ServerMyUserModel {
tokens: Tokens tokens: UserTokens
specialPlaylists: MyUserSpecialPlaylist[] specialPlaylists: MyUserSpecialPlaylist[]
canSeeVideosLink = true canSeeVideosLink = true
static load () { constructor (userHash: Partial<ServerMyUserModel>, hashTokens: Partial<UserTokens>) {
const tokens = Tokens.load()
if (!tokens) return null
const userInfo = getUserInfoFromLocalStorage()
if (!userInfo) return null
return new AuthUser(userInfo, tokens)
}
static flush () {
flushUserInfoFromLocalStorage()
Tokens.flush()
}
constructor (userHash: Partial<ServerMyUserModel>, hashTokens: TokenOptions) {
super(userHash) super(userHash)
this.tokens = new Tokens(hashTokens) this.tokens = new UserTokens(hashTokens)
this.specialPlaylists = userHash.specialPlaylists this.specialPlaylists = userHash.specialPlaylists
} }
@ -77,20 +55,6 @@ export class AuthUser extends User implements ServerMyUserModel {
return user.role === UserRole.USER return user.role === UserRole.USER
} }
save () {
saveUserInfoIntoLocalStorage({
id: this.id,
username: this.username,
email: this.email,
role: this.role,
nsfwPolicy: this.nsfwPolicy,
webTorrentEnabled: this.webTorrentEnabled,
autoPlayVideo: this.autoPlayVideo
})
this.tokens.save()
}
computeCanSeeVideosLink (quotaObservable: Observable<UserVideoQuota>): Observable<boolean> { computeCanSeeVideosLink (quotaObservable: Observable<UserVideoQuota>): Observable<boolean> {
if (!this.isUploadDisabled()) { if (!this.isUploadDisabled()) {
this.canSeeVideosLink = true this.canSeeVideosLink = true

View File

@ -5,7 +5,7 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { Notifier } from '@app/core/notification/notifier.service' import { Notifier } from '@app/core/notification/notifier.service'
import { objectToUrlEncoded, peertubeLocalStorage } from '@root-helpers/index' import { objectToUrlEncoded, peertubeLocalStorage, UserTokens } from '@root-helpers/index'
import { HttpStatusCode, MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models' import { HttpStatusCode, MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
import { RestExtractor } from '../rest/rest-extractor.service' import { RestExtractor } from '../rest/rest-extractor.service'
@ -34,6 +34,7 @@ export class AuthService {
loginChangedSource: Observable<AuthStatus> loginChangedSource: Observable<AuthStatus>
userInformationLoaded = new ReplaySubject<boolean>(1) userInformationLoaded = new ReplaySubject<boolean>(1)
tokensRefreshed = new ReplaySubject<void>(1)
hotkeys: Hotkey[] hotkeys: Hotkey[]
private clientId: string = peertubeLocalStorage.getItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID) private clientId: string = peertubeLocalStorage.getItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID)
@ -52,9 +53,6 @@ export class AuthService {
this.loginChanged = new Subject<AuthStatus>() this.loginChanged = new Subject<AuthStatus>()
this.loginChangedSource = this.loginChanged.asObservable() this.loginChangedSource = this.loginChanged.asObservable()
// Return null if there is nothing to load
this.user = AuthUser.load()
// Set HotKeys // Set HotKeys
this.hotkeys = [ this.hotkeys = [
new Hotkey('m s', (event: KeyboardEvent): boolean => { new Hotkey('m s', (event: KeyboardEvent): boolean => {
@ -76,6 +74,10 @@ export class AuthService {
] ]
} }
buildAuthUser (userInfo: Partial<User>, tokens: UserTokens) {
this.user = new AuthUser(userInfo, tokens)
}
loadClientCredentials () { loadClientCredentials () {
// Fetch the client_id/client_secret // Fetch the client_id/client_secret
this.http.get<OAuthClientLocal>(AuthService.BASE_CLIENT_URL) this.http.get<OAuthClientLocal>(AuthService.BASE_CLIENT_URL)
@ -180,8 +182,6 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
this.user = null this.user = null
AuthUser.flush()
this.setStatus(AuthStatus.LoggedOut) this.setStatus(AuthStatus.LoggedOut)
this.hotkeysService.remove(this.hotkeys) this.hotkeysService.remove(this.hotkeys)
@ -239,7 +239,6 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
.subscribe({ .subscribe({
next: res => { next: res => {
this.user.patch(res) this.user.patch(res)
this.user.save()
this.userInformationLoaded.next(true) this.userInformationLoaded.next(true)
} }
@ -262,7 +261,6 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
} }
this.user = new AuthUser(obj, hashTokens) this.user = new AuthUser(obj, hashTokens)
this.user.save()
this.setStatus(AuthStatus.LoggedIn) this.setStatus(AuthStatus.LoggedIn)
this.userInformationLoaded.next(true) this.userInformationLoaded.next(true)
@ -272,7 +270,7 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
private handleRefreshToken (obj: UserRefreshToken) { private handleRefreshToken (obj: UserRefreshToken) {
this.user.refreshTokens(obj.access_token, obj.refresh_token) this.user.refreshTokens(obj.access_token, obj.refresh_token)
this.user.save() this.tokensRefreshed.next()
} }
private setStatus (status: AuthStatus) { private setStatus (status: AuthStatus) {

View File

@ -30,7 +30,7 @@ import { ServerConfigResolver } from './routing/server-config-resolver.service'
import { ScopedTokensService } from './scoped-tokens' import { ScopedTokensService } from './scoped-tokens'
import { ServerService } from './server' import { ServerService } from './server'
import { ThemeService } from './theme' import { ThemeService } from './theme'
import { UserService } from './users' import { UserLocalStorageService, UserService } from './users'
import { LocalStorageService, ScreenService, SessionStorageService } from './wrappers' import { LocalStorageService, ScreenService, SessionStorageService } from './wrappers'
@NgModule({ @NgModule({
@ -79,6 +79,7 @@ import { LocalStorageService, ScreenService, SessionStorageService } from './wra
RestService, RestService,
UserService, UserService,
UserLocalStorageService,
ScreenService, ScreenService,
LocalStorageService, LocalStorageService,

View File

@ -1,2 +1,3 @@
export * from './user-local-storage.service'
export * from './user.model' export * from './user.model'
export * from './user.service' export * from './user.service'

View File

@ -0,0 +1,186 @@
import { filter, throttleTime } from 'rxjs'
import { Injectable } from '@angular/core'
import { AuthService, AuthStatus } from '@app/core/auth'
import { UserLocalStorageKeys, UserTokens } from '@root-helpers/users'
import { getBoolOrDefault } from '@root-helpers/local-storage-utils'
import { UserRole, UserUpdateMe } from '@shared/models'
import { NSFWPolicyType } from '@shared/models/videos'
import { ServerService } from '../server'
import { LocalStorageService } from '../wrappers/storage.service'
@Injectable()
export class UserLocalStorageService {
constructor (
private authService: AuthService,
private server: ServerService,
private localStorageService: LocalStorageService
) {
this.authService.userInformationLoaded.subscribe({
next: () => {
const user = this.authService.getUser()
this.setLoggedInUser(user)
this.setUserInfo(user)
this.setTokens(user.tokens)
}
})
this.authService.loginChangedSource
.pipe(filter(status => status === AuthStatus.LoggedOut))
.subscribe({
next: () => {
this.flushLoggedInUser()
this.flushUserInfo()
this.flushTokens()
}
})
this.authService.tokensRefreshed
.subscribe({
next: () => {
const user = this.authService.getUser()
this.setTokens(user.tokens)
}
})
}
// ---------------------------------------------------------------------------
getLoggedInUser () {
const usernameLocalStorage = this.localStorageService.getItem(UserLocalStorageKeys.USERNAME)
if (!usernameLocalStorage) return undefined
return {
id: parseInt(this.localStorageService.getItem(UserLocalStorageKeys.ID), 10),
username: this.localStorageService.getItem(UserLocalStorageKeys.USERNAME),
email: this.localStorageService.getItem(UserLocalStorageKeys.EMAIL),
role: parseInt(this.localStorageService.getItem(UserLocalStorageKeys.ROLE), 10) as UserRole,
...this.getUserInfo()
}
}
setLoggedInUser (user: {
id: number
username: string
email: string
role: UserRole
}) {
this.localStorageService.setItem(UserLocalStorageKeys.ID, user.id.toString())
this.localStorageService.setItem(UserLocalStorageKeys.USERNAME, user.username)
this.localStorageService.setItem(UserLocalStorageKeys.EMAIL, user.email)
this.localStorageService.setItem(UserLocalStorageKeys.ROLE, user.role.toString())
}
flushLoggedInUser () {
this.localStorageService.removeItem(UserLocalStorageKeys.ID)
this.localStorageService.removeItem(UserLocalStorageKeys.USERNAME)
this.localStorageService.removeItem(UserLocalStorageKeys.EMAIL)
this.localStorageService.removeItem(UserLocalStorageKeys.ROLE)
}
// ---------------------------------------------------------------------------
getUserInfo () {
let videoLanguages: string[]
try {
const languagesString = this.localStorageService.getItem(UserLocalStorageKeys.VIDEO_LANGUAGES)
videoLanguages = languagesString && languagesString !== 'undefined'
? JSON.parse(languagesString)
: null
} catch (err) {
videoLanguages = null
console.error('Cannot parse desired video languages from localStorage.', err)
}
const htmlConfig = this.server.getHTMLConfig()
const defaultNSFWPolicy = htmlConfig.instance.defaultNSFWPolicy
const defaultP2PEnabled = htmlConfig.defaults.p2p.enabled
return {
nsfwPolicy: this.localStorageService.getItem<NSFWPolicyType>(UserLocalStorageKeys.NSFW_POLICY) || defaultNSFWPolicy,
p2pEnabled: getBoolOrDefault(this.localStorageService.getItem(UserLocalStorageKeys.P2P_ENABLED), defaultP2PEnabled),
theme: this.localStorageService.getItem(UserLocalStorageKeys.THEME) || 'instance-default',
videoLanguages,
autoPlayVideo: getBoolOrDefault(this.localStorageService.getItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO), true),
autoPlayNextVideo: getBoolOrDefault(this.localStorageService.getItem(UserLocalStorageKeys.AUTO_PLAY_NEXT_VIDEO), false),
autoPlayNextVideoPlaylist: getBoolOrDefault(this.localStorageService.getItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST), true)
}
}
setUserInfo (profile: UserUpdateMe) {
const localStorageKeys: { [ id in keyof UserUpdateMe ]: string } = {
nsfwPolicy: UserLocalStorageKeys.NSFW_POLICY,
p2pEnabled: UserLocalStorageKeys.P2P_ENABLED,
autoPlayNextVideo: UserLocalStorageKeys.AUTO_PLAY_VIDEO,
autoPlayNextVideoPlaylist: UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST,
theme: UserLocalStorageKeys.THEME,
videoLanguages: UserLocalStorageKeys.VIDEO_LANGUAGES
}
const obj = Object.keys(localStorageKeys)
.filter(key => key in profile)
.map(key => ([ localStorageKeys[key], profile[key] ]))
for (const [ key, value ] of obj) {
try {
if (value === undefined) {
this.localStorageService.removeItem(key)
continue
}
const localStorageValue = typeof value === 'string'
? value
: JSON.stringify(value)
this.localStorageService.setItem(key, localStorageValue)
} catch (err) {
console.error(`Cannot set ${key}->${value} in localStorage. Likely due to a value impossible to stringify.`, err)
}
}
}
flushUserInfo () {
this.localStorageService.removeItem(UserLocalStorageKeys.NSFW_POLICY)
this.localStorageService.removeItem(UserLocalStorageKeys.P2P_ENABLED)
this.localStorageService.removeItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO)
this.localStorageService.removeItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST)
this.localStorageService.removeItem(UserLocalStorageKeys.THEME)
this.localStorageService.removeItem(UserLocalStorageKeys.VIDEO_LANGUAGES)
}
listenUserInfoChange () {
return this.localStorageService.watch([
UserLocalStorageKeys.NSFW_POLICY,
UserLocalStorageKeys.P2P_ENABLED,
UserLocalStorageKeys.AUTO_PLAY_VIDEO,
UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST,
UserLocalStorageKeys.THEME,
UserLocalStorageKeys.VIDEO_LANGUAGES
]).pipe(
throttleTime(200),
filter(() => this.authService.isLoggedIn() !== true)
)
}
// ---------------------------------------------------------------------------
getTokens () {
return UserTokens.getUserTokens(this.localStorageService)
}
setTokens (tokens: UserTokens) {
UserTokens.saveToLocalStorage(this.localStorageService, tokens)
}
flushTokens () {
UserTokens.flushLocalStorage(this.localStorageService)
}
}

View File

@ -26,7 +26,11 @@ export class User implements UserServerModel {
autoPlayVideo: boolean autoPlayVideo: boolean
autoPlayNextVideo: boolean autoPlayNextVideo: boolean
autoPlayNextVideoPlaylist: boolean autoPlayNextVideoPlaylist: boolean
webTorrentEnabled: boolean
p2pEnabled: boolean
// FIXME: deprecated in 4.1
webTorrentEnabled: never
videosHistoryEnabled: boolean videosHistoryEnabled: boolean
videoLanguages: string[] videoLanguages: string[]
@ -84,7 +88,7 @@ export class User implements UserServerModel {
this.videoCommentsCount = hash.videoCommentsCount this.videoCommentsCount = hash.videoCommentsCount
this.nsfwPolicy = hash.nsfwPolicy this.nsfwPolicy = hash.nsfwPolicy
this.webTorrentEnabled = hash.webTorrentEnabled this.p2pEnabled = hash.p2pEnabled
this.autoPlayVideo = hash.autoPlayVideo this.autoPlayVideo = hash.autoPlayVideo
this.autoPlayNextVideo = hash.autoPlayNextVideo this.autoPlayNextVideo = hash.autoPlayNextVideo
this.autoPlayNextVideoPlaylist = hash.autoPlayNextVideoPlaylist this.autoPlayNextVideoPlaylist = hash.autoPlayNextVideoPlaylist

View File

@ -1,11 +1,10 @@
import { SortMeta } from 'primeng/api' import { SortMeta } from 'primeng/api'
import { from, Observable, of } from 'rxjs' import { from, Observable, of } from 'rxjs'
import { catchError, concatMap, filter, first, map, shareReplay, tap, throttleTime, toArray } from 'rxjs/operators' import { catchError, concatMap, first, map, shareReplay, tap, toArray } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http' import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { AuthService } from '@app/core/auth' import { AuthService } from '@app/core/auth'
import { getBytes } from '@root-helpers/bytes' import { getBytes } from '@root-helpers/bytes'
import { UserLocalStorageKeys } from '@root-helpers/users'
import { import {
ActorImage, ActorImage,
ResultList, ResultList,
@ -17,10 +16,9 @@ import {
UserUpdateMe, UserUpdateMe,
UserVideoQuota UserVideoQuota
} from '@shared/models' } from '@shared/models'
import { ServerService } from '../'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
import { RestExtractor, RestPagination, RestService } from '../rest' import { RestExtractor, RestPagination, RestService } from '../rest'
import { LocalStorageService, SessionStorageService } from '../wrappers/storage.service' import { UserLocalStorageService } from './'
import { User } from './user.model' import { User } from './user.model'
@Injectable() @Injectable()
@ -33,12 +31,10 @@ export class UserService {
constructor ( constructor (
private authHttp: HttpClient, private authHttp: HttpClient,
private server: ServerService,
private authService: AuthService, private authService: AuthService,
private restExtractor: RestExtractor, private restExtractor: RestExtractor,
private restService: RestService, private restService: RestService,
private localStorageService: LocalStorageService, private userLocalStorageService: UserLocalStorageService
private sessionStorageService: SessionStorageService
) { } ) { }
hasSignupInThisSession () { hasSignupInThisSession () {
@ -73,6 +69,23 @@ export class UserService {
) )
} }
// ---------------------------------------------------------------------------
updateMyAnonymousProfile (profile: UserUpdateMe) {
this.userLocalStorageService.setUserInfo(profile)
}
listenAnonymousUpdate () {
return this.userLocalStorageService.listenUserInfoChange()
.pipe(map(() => this.getAnonymousUser()))
}
getAnonymousUser () {
return new User(this.userLocalStorageService.getUserInfo())
}
// ---------------------------------------------------------------------------
updateMyProfile (profile: UserUpdateMe) { updateMyProfile (profile: UserUpdateMe) {
const url = UserService.BASE_USERS_URL + 'me' const url = UserService.BASE_USERS_URL + 'me'
@ -83,53 +96,6 @@ export class UserService {
) )
} }
updateMyAnonymousProfile (profile: UserUpdateMe) {
const localStorageKeys: { [ id in keyof UserUpdateMe ]: string } = {
nsfwPolicy: UserLocalStorageKeys.NSFW_POLICY,
webTorrentEnabled: UserLocalStorageKeys.WEBTORRENT_ENABLED,
autoPlayNextVideo: UserLocalStorageKeys.AUTO_PLAY_VIDEO,
autoPlayNextVideoPlaylist: UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST,
theme: UserLocalStorageKeys.THEME,
videoLanguages: UserLocalStorageKeys.VIDEO_LANGUAGES
}
const obj = Object.keys(localStorageKeys)
.filter(key => key in profile)
.map(key => ([ localStorageKeys[key], profile[key] ]))
for (const [ key, value ] of obj) {
try {
if (value === undefined) {
this.localStorageService.removeItem(key)
continue
}
const localStorageValue = typeof value === 'string'
? value
: JSON.stringify(value)
this.localStorageService.setItem(key, localStorageValue)
} catch (err) {
console.error(`Cannot set ${key}->${value} in localStorage. Likely due to a value impossible to stringify.`, err)
}
}
}
listenAnonymousUpdate () {
return this.localStorageService.watch([
UserLocalStorageKeys.NSFW_POLICY,
UserLocalStorageKeys.WEBTORRENT_ENABLED,
UserLocalStorageKeys.AUTO_PLAY_VIDEO,
UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST,
UserLocalStorageKeys.THEME,
UserLocalStorageKeys.VIDEO_LANGUAGES
]).pipe(
throttleTime(200),
filter(() => this.authService.isLoggedIn() !== true),
map(() => this.getAnonymousUser())
)
}
deleteMe () { deleteMe () {
const url = UserService.BASE_USERS_URL + 'me' const url = UserService.BASE_USERS_URL + 'me'
@ -287,36 +253,6 @@ export class UserService {
.pipe(catchError(err => this.restExtractor.handleError(err))) .pipe(catchError(err => this.restExtractor.handleError(err)))
} }
getAnonymousUser () {
let videoLanguages: string[]
try {
const languagesString = this.localStorageService.getItem(UserLocalStorageKeys.VIDEO_LANGUAGES)
videoLanguages = languagesString && languagesString !== 'undefined'
? JSON.parse(languagesString)
: null
} catch (err) {
videoLanguages = null
console.error('Cannot parse desired video languages from localStorage.', err)
}
const defaultNSFWPolicy = this.server.getHTMLConfig().instance.defaultNSFWPolicy
return new User({
// local storage keys
nsfwPolicy: this.localStorageService.getItem(UserLocalStorageKeys.NSFW_POLICY) || defaultNSFWPolicy,
webTorrentEnabled: this.localStorageService.getItem(UserLocalStorageKeys.WEBTORRENT_ENABLED) !== 'false',
theme: this.localStorageService.getItem(UserLocalStorageKeys.THEME) || 'instance-default',
videoLanguages,
autoPlayNextVideoPlaylist: this.localStorageService.getItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST) !== 'false',
autoPlayVideo: this.localStorageService.getItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO) === 'true',
// session storage keys
autoPlayNextVideo: this.sessionStorageService.getItem(UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
})
}
getUsers (parameters: { getUsers (parameters: {
pagination: RestPagination pagination: RestPagination
sort: SortMeta sort: SortMeta

View File

@ -60,7 +60,7 @@
<my-global-icon iconName="p2p" aria-hidden="true"></my-global-icon> <my-global-icon iconName="p2p" aria-hidden="true"></my-global-icon>
<ng-container i18n>Help share videos</ng-container> <ng-container i18n>Help share videos</ng-container>
<my-input-switch class="ml-auto" [checked]="user.webTorrentEnabled"></my-input-switch> <my-input-switch class="ml-auto" [checked]="user.p2pEnabled"></my-input-switch>
</a> </a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>

View File

@ -196,9 +196,9 @@ export class MenuComponent implements OnInit {
toggleUseP2P () { toggleUseP2P () {
if (!this.user) return if (!this.user) return
this.user.webTorrentEnabled = !this.user.webTorrentEnabled this.user.p2pEnabled = !this.user.p2pEnabled
this.userService.updateMyProfile({ webTorrentEnabled: this.user.webTorrentEnabled }) this.userService.updateMyProfile({ p2pEnabled: this.user.p2pEnabled })
.subscribe(() => this.authService.refreshUserInformation()) .subscribe(() => this.authService.refreshUserInformation())
} }

View File

@ -4,7 +4,6 @@ import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { ComponentPaginationLight, RestExtractor, RestPagination, RestService } from '@app/core' import { ComponentPaginationLight, RestExtractor, RestPagination, RestService } from '@app/core'
import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { import {
ResultList, ResultList,
Video as VideoServerModel, Video as VideoServerModel,
@ -25,11 +24,7 @@ export class SearchService {
private restService: RestService, private restService: RestService,
private videoService: VideoService, private videoService: VideoService,
private playlistService: VideoPlaylistService private playlistService: VideoPlaylistService
) { ) { }
// Add ability to override search endpoint if the user updated this local storage key
const searchUrl = peertubeLocalStorage.getItem('search-url')
if (searchUrl) SearchService.BASE_SEARCH_URL = searchUrl
}
searchVideos (parameters: { searchVideos (parameters: {
search?: string search?: string

View File

@ -38,7 +38,7 @@
<div class="form-group"> <div class="form-group">
<my-peertube-checkbox <my-peertube-checkbox
inputName="webTorrentEnabled" formControlName="webTorrentEnabled" [recommended]="true" inputName="p2pEnabled" formControlName="p2pEnabled" [recommended]="true"
i18n-labelText labelText="Help share videos being played" i18n-labelText labelText="Help share videos being played"
> >
<ng-container ngProjectAs="description"> <ng-container ngProjectAs="description">

View File

@ -34,7 +34,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
ngOnInit () { ngOnInit () {
this.buildForm({ this.buildForm({
nsfwPolicy: null, nsfwPolicy: null,
webTorrentEnabled: null, p2pEnabled: null,
autoPlayVideo: null, autoPlayVideo: null,
autoPlayNextVideo: null, autoPlayNextVideo: null,
videoLanguages: null videoLanguages: null
@ -48,7 +48,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
this.form.patchValue({ this.form.patchValue({
nsfwPolicy: this.user.nsfwPolicy || this.defaultNSFWPolicy, nsfwPolicy: this.user.nsfwPolicy || this.defaultNSFWPolicy,
webTorrentEnabled: this.user.webTorrentEnabled, p2pEnabled: this.user.p2pEnabled,
autoPlayVideo: this.user.autoPlayVideo === true, autoPlayVideo: this.user.autoPlayVideo === true,
autoPlayNextVideo: this.user.autoPlayNextVideo, autoPlayNextVideo: this.user.autoPlayNextVideo,
videoLanguages: this.user.videoLanguages videoLanguages: this.user.videoLanguages
@ -65,7 +65,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
updateDetails (onlyKeys?: string[]) { updateDetails (onlyKeys?: string[]) {
const nsfwPolicy = this.form.value['nsfwPolicy'] const nsfwPolicy = this.form.value['nsfwPolicy']
const webTorrentEnabled = this.form.value['webTorrentEnabled'] const p2pEnabled = this.form.value['p2pEnabled']
const autoPlayVideo = this.form.value['autoPlayVideo'] const autoPlayVideo = this.form.value['autoPlayVideo']
const autoPlayNextVideo = this.form.value['autoPlayNextVideo'] const autoPlayNextVideo = this.form.value['autoPlayNextVideo']
@ -80,7 +80,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
let details: UserUpdateMe = { let details: UserUpdateMe = {
nsfwPolicy, nsfwPolicy,
webTorrentEnabled, p2pEnabled,
autoPlayVideo, autoPlayVideo,
autoPlayNextVideo, autoPlayNextVideo,
videoLanguages videoLanguages

View File

@ -10,14 +10,6 @@ function getStoredVolume () {
return undefined return undefined
} }
function getStoredP2PEnabled (): boolean {
const value = getLocalStorage('webtorrent_enabled')
if (value !== null && value !== undefined) return value === 'true'
// By default webtorrent is enabled
return true
}
function getStoredMute () { function getStoredMute () {
const value = getLocalStorage('mute') const value = getLocalStorage('mute')
if (value !== null && value !== undefined) return value === 'true' if (value !== null && value !== undefined) return value === 'true'
@ -123,7 +115,6 @@ function cleanupVideoWatch () {
export { export {
getStoredVolume, getStoredVolume,
getStoredP2PEnabled,
getStoredMute, getStoredMute,
getStoredTheater, getStoredTheater,
saveVolumeInStore, saveVolumeInStore,

View File

@ -31,7 +31,7 @@ import { copyToClipboard } from '../../root-helpers/utils'
import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager' import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder' import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder'
import { segmentValidatorFactory } from './p2p-media-loader/segment-validator' import { segmentValidatorFactory } from './p2p-media-loader/segment-validator'
import { getAverageBandwidthInStore, getStoredP2PEnabled, saveAverageBandwidth } from './peertube-player-local-storage' import { getAverageBandwidthInStore, saveAverageBandwidth } from './peertube-player-local-storage'
import { import {
NextPreviousVideoButtonOptions, NextPreviousVideoButtonOptions,
P2PMediaLoaderPluginOptions, P2PMediaLoaderPluginOptions,
@ -86,6 +86,7 @@ export interface CommonOptions extends CustomizationOptions {
onPlayerElementChange: (element: HTMLVideoElement) => void onPlayerElementChange: (element: HTMLVideoElement) => void
autoplay: boolean autoplay: boolean
p2pEnabled: boolean
nextVideo?: () => void nextVideo?: () => void
hasNextVideo?: () => boolean hasNextVideo?: () => boolean
@ -374,7 +375,7 @@ export class PeertubePlayerManager {
requiredSegmentsPriority: 1, requiredSegmentsPriority: 1,
simultaneousHttpDownloads: 1, simultaneousHttpDownloads: 1,
segmentUrlBuilder: segmentUrlBuilderFactory(redundancyUrlManager, 1), segmentUrlBuilder: segmentUrlBuilderFactory(redundancyUrlManager, 1),
useP2P: getStoredP2PEnabled(), useP2P: commonOptions.p2pEnabled,
consumeOnly consumeOnly
}, },
segments: { segments: {
@ -437,6 +438,7 @@ export class PeertubePlayerManager {
const webtorrent = { const webtorrent = {
autoplay, autoplay,
playerRefusedP2P: commonOptions.p2pEnabled === false,
videoDuration: commonOptions.videoDuration, videoDuration: commonOptions.videoDuration,
playerElement: commonOptions.playerElement, playerElement: commonOptions.playerElement,
videoFiles: webtorrentOptions.videoFiles.length !== 0 videoFiles: webtorrentOptions.videoFiles.length !== 0

View File

@ -137,6 +137,8 @@ type WebtorrentPluginOptions = {
videoFiles: VideoFile[] videoFiles: VideoFile[]
startTime: number | string startTime: number | string
playerRefusedP2P: boolean
} }
type P2PMediaLoaderPluginOptions = { type P2PMediaLoaderPluginOptions = {

View File

@ -1,4 +1,4 @@
import { VideoFile } from '@shared/models' import { HTMLServerConfig, Video, VideoFile } from '@shared/models'
function toTitleCase (str: string) { function toTitleCase (str: string) {
return str.charAt(0).toUpperCase() + str.slice(1) return str.charAt(0).toUpperCase() + str.slice(1)
@ -8,6 +8,13 @@ function isWebRTCDisabled () {
return !!((window as any).RTCPeerConnection || (window as any).mozRTCPeerConnection || (window as any).webkitRTCPeerConnection) === false return !!((window as any).RTCPeerConnection || (window as any).mozRTCPeerConnection || (window as any).webkitRTCPeerConnection) === false
} }
function isP2PEnabled (video: Video, config: HTMLServerConfig, userP2PEnabled: boolean) {
if (video.isLocal && config.tracker.enabled === false) return false
if (isWebRTCDisabled()) return false
return userP2PEnabled
}
function isIOS () { function isIOS () {
if (/iPad|iPhone|iPod/.test(navigator.platform)) { if (/iPad|iPhone|iPod/.test(navigator.platform)) {
return true return true
@ -97,6 +104,7 @@ export {
getRtcConfig, getRtcConfig,
toTitleCase, toTitleCase,
isWebRTCDisabled, isWebRTCDisabled,
isP2PEnabled,
buildVideoOrPlaylistEmbed, buildVideoOrPlaylistEmbed,
videoFileMaxByResolution, videoFileMaxByResolution,

View File

@ -2,13 +2,7 @@ import videojs from 'video.js'
import * as WebTorrent from 'webtorrent' import * as WebTorrent from 'webtorrent'
import { timeToInt } from '@shared/core-utils' import { timeToInt } from '@shared/core-utils'
import { VideoFile } from '@shared/models' import { VideoFile } from '@shared/models'
import { import { getAverageBandwidthInStore, getStoredMute, getStoredVolume, saveAverageBandwidth } from '../peertube-player-local-storage'
getAverageBandwidthInStore,
getStoredMute,
getStoredP2PEnabled,
getStoredVolume,
saveAverageBandwidth
} from '../peertube-player-local-storage'
import { PeerTubeResolution, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings' import { PeerTubeResolution, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings'
import { getRtcConfig, isIOS, videoFileMaxByResolution, videoFileMinByResolution } from '../utils' import { getRtcConfig, isIOS, videoFileMaxByResolution, videoFileMinByResolution } from '../utils'
import { PeertubeChunkStore } from './peertube-chunk-store' import { PeertubeChunkStore } from './peertube-chunk-store'
@ -74,9 +68,10 @@ class WebTorrentPlugin extends Plugin {
this.startTime = timeToInt(options.startTime) this.startTime = timeToInt(options.startTime)
// Disable auto play on iOS // Custom autoplay handled by webtorrent because we lazy play the video
this.autoplay = options.autoplay this.autoplay = options.autoplay
this.playerRefusedP2P = !getStoredP2PEnabled()
this.playerRefusedP2P = options.playerRefusedP2P
this.videoFiles = options.videoFiles this.videoFiles = options.videoFiles
this.videoDuration = options.videoDuration this.videoDuration = options.videoDuration

View File

@ -1,6 +1,7 @@
export * from './users' export * from './users'
export * from './bytes' export * from './bytes'
export * from './images' export * from './images'
export * from './local-storage-utils'
export * from './peertube-web-storage' export * from './peertube-web-storage'
export * from './utils' export * from './utils'
export * from './plugins-manager' export * from './plugins-manager'

View File

@ -0,0 +1,10 @@
function getBoolOrDefault (value: string, defaultValue: boolean) {
if (value === 'true') return true
if (value === 'false') return false
return defaultValue
}
export {
getBoolOrDefault
}

View File

@ -1,3 +1,2 @@
export * from './user-local-storage-keys' export * from './user-local-storage-keys'
export * from './user-local-storage-manager'
export * from './user-tokens' export * from './user-tokens'

View File

@ -1,15 +1,25 @@
export const UserLocalStorageKeys = { export const UserLocalStorageKeys = {
ID: 'id', ID: 'id',
USERNAME: 'username',
ROLE: 'role', ROLE: 'role',
EMAIL: 'email', EMAIL: 'email',
VIDEOS_HISTORY_ENABLED: 'videos-history-enabled', VIDEOS_HISTORY_ENABLED: 'videos-history-enabled',
USERNAME: 'username',
NSFW_POLICY: 'nsfw_policy', NSFW_POLICY: 'nsfw_policy',
WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled', P2P_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled',
AUTO_PLAY_VIDEO: 'auto_play_video', AUTO_PLAY_VIDEO: 'auto_play_video',
SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO: 'auto_play_next_video', AUTO_PLAY_NEXT_VIDEO: 'auto_play_next_video',
AUTO_PLAY_VIDEO_PLAYLIST: 'auto_play_video_playlist', AUTO_PLAY_VIDEO_PLAYLIST: 'auto_play_video_playlist',
THEME: 'theme', THEME: 'theme',
LAST_ACTIVE_THEME: 'last_active_theme', LAST_ACTIVE_THEME: 'last_active_theme',
VIDEO_LANGUAGES: 'video_languages' VIDEO_LANGUAGES: 'video_languages'
} }
export const UserTokenLocalStorageKeys = {
ACCESS_TOKEN: 'access_token',
REFRESH_TOKEN: 'refresh_token',
TOKEN_TYPE: 'token_type'
}

View File

@ -1,55 +0,0 @@
import { NSFWPolicyType, UserRole } from '@shared/models'
import { peertubeLocalStorage } from '../peertube-web-storage'
import { UserLocalStorageKeys } from './user-local-storage-keys'
function getUserInfoFromLocalStorage () {
const usernameLocalStorage = peertubeLocalStorage.getItem(UserLocalStorageKeys.USERNAME)
if (!usernameLocalStorage) return undefined
return {
id: parseInt(peertubeLocalStorage.getItem(UserLocalStorageKeys.ID), 10),
username: peertubeLocalStorage.getItem(UserLocalStorageKeys.USERNAME),
email: peertubeLocalStorage.getItem(UserLocalStorageKeys.EMAIL),
role: parseInt(peertubeLocalStorage.getItem(UserLocalStorageKeys.ROLE), 10) as UserRole,
nsfwPolicy: peertubeLocalStorage.getItem(UserLocalStorageKeys.NSFW_POLICY) as NSFWPolicyType,
webTorrentEnabled: peertubeLocalStorage.getItem(UserLocalStorageKeys.WEBTORRENT_ENABLED) === 'true',
autoPlayVideo: peertubeLocalStorage.getItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO) === 'true',
videosHistoryEnabled: peertubeLocalStorage.getItem(UserLocalStorageKeys.VIDEOS_HISTORY_ENABLED) === 'true'
}
}
function flushUserInfoFromLocalStorage () {
peertubeLocalStorage.removeItem(UserLocalStorageKeys.ID)
peertubeLocalStorage.removeItem(UserLocalStorageKeys.USERNAME)
peertubeLocalStorage.removeItem(UserLocalStorageKeys.EMAIL)
peertubeLocalStorage.removeItem(UserLocalStorageKeys.ROLE)
peertubeLocalStorage.removeItem(UserLocalStorageKeys.NSFW_POLICY)
peertubeLocalStorage.removeItem(UserLocalStorageKeys.WEBTORRENT_ENABLED)
peertubeLocalStorage.removeItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO)
peertubeLocalStorage.removeItem(UserLocalStorageKeys.VIDEOS_HISTORY_ENABLED)
}
function saveUserInfoIntoLocalStorage (info: {
id: number
username: string
email: string
role: UserRole
nsfwPolicy: NSFWPolicyType
webTorrentEnabled: boolean
autoPlayVideo: boolean
}) {
peertubeLocalStorage.setItem(UserLocalStorageKeys.ID, info.id.toString())
peertubeLocalStorage.setItem(UserLocalStorageKeys.USERNAME, info.username)
peertubeLocalStorage.setItem(UserLocalStorageKeys.EMAIL, info.email)
peertubeLocalStorage.setItem(UserLocalStorageKeys.ROLE, info.role.toString())
peertubeLocalStorage.setItem(UserLocalStorageKeys.NSFW_POLICY, info.nsfwPolicy.toString())
peertubeLocalStorage.setItem(UserLocalStorageKeys.WEBTORRENT_ENABLED, JSON.stringify(info.webTorrentEnabled))
peertubeLocalStorage.setItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO, JSON.stringify(info.autoPlayVideo))
}
export {
getUserInfoFromLocalStorage,
saveUserInfoIntoLocalStorage,
flushUserInfoFromLocalStorage
}

View File

@ -1,46 +1,11 @@
import { peertubeLocalStorage } from '../peertube-web-storage' import { UserTokenLocalStorageKeys } from './user-local-storage-keys'
export type TokenOptions = {
accessToken: string
refreshToken: string
tokenType: string
}
// Private class only used by User
export class Tokens {
private static KEYS = {
ACCESS_TOKEN: 'access_token',
REFRESH_TOKEN: 'refresh_token',
TOKEN_TYPE: 'token_type'
}
export class UserTokens {
accessToken: string accessToken: string
refreshToken: string refreshToken: string
tokenType: string tokenType: string
static load () { constructor (hash?: Partial<UserTokens>) {
const accessTokenLocalStorage = peertubeLocalStorage.getItem(this.KEYS.ACCESS_TOKEN)
const refreshTokenLocalStorage = peertubeLocalStorage.getItem(this.KEYS.REFRESH_TOKEN)
const tokenTypeLocalStorage = peertubeLocalStorage.getItem(this.KEYS.TOKEN_TYPE)
if (accessTokenLocalStorage && refreshTokenLocalStorage && tokenTypeLocalStorage) {
return new Tokens({
accessToken: accessTokenLocalStorage,
refreshToken: refreshTokenLocalStorage,
tokenType: tokenTypeLocalStorage
})
}
return null
}
static flush () {
peertubeLocalStorage.removeItem(this.KEYS.ACCESS_TOKEN)
peertubeLocalStorage.removeItem(this.KEYS.REFRESH_TOKEN)
peertubeLocalStorage.removeItem(this.KEYS.TOKEN_TYPE)
}
constructor (hash?: TokenOptions) {
if (hash) { if (hash) {
this.accessToken = hash.accessToken this.accessToken = hash.accessToken
this.refreshToken = hash.refreshToken this.refreshToken = hash.refreshToken
@ -53,9 +18,29 @@ export class Tokens {
} }
} }
save () { static getUserTokens (localStorage: Pick<Storage, 'getItem'>) {
peertubeLocalStorage.setItem(Tokens.KEYS.ACCESS_TOKEN, this.accessToken) const accessTokenLocalStorage = localStorage.getItem(UserTokenLocalStorageKeys.ACCESS_TOKEN)
peertubeLocalStorage.setItem(Tokens.KEYS.REFRESH_TOKEN, this.refreshToken) const refreshTokenLocalStorage = localStorage.getItem(UserTokenLocalStorageKeys.REFRESH_TOKEN)
peertubeLocalStorage.setItem(Tokens.KEYS.TOKEN_TYPE, this.tokenType) const tokenTypeLocalStorage = localStorage.getItem(UserTokenLocalStorageKeys.TOKEN_TYPE)
if (!accessTokenLocalStorage || !refreshTokenLocalStorage || !tokenTypeLocalStorage) return null
return new UserTokens({
accessToken: accessTokenLocalStorage,
refreshToken: refreshTokenLocalStorage,
tokenType: tokenTypeLocalStorage
})
}
static saveToLocalStorage (localStorage: Pick<Storage, 'setItem'>, tokens: UserTokens) {
localStorage.setItem(UserTokenLocalStorageKeys.ACCESS_TOKEN, tokens.accessToken)
localStorage.setItem(UserTokenLocalStorageKeys.REFRESH_TOKEN, tokens.refreshToken)
localStorage.setItem(UserTokenLocalStorageKeys.TOKEN_TYPE, tokens.tokenType)
}
static flushLocalStorage (localStorage: Pick<Storage, 'removeItem'>) {
localStorage.removeItem(UserTokenLocalStorageKeys.ACCESS_TOKEN)
localStorage.removeItem(UserTokenLocalStorageKeys.REFRESH_TOKEN)
localStorage.removeItem(UserTokenLocalStorageKeys.TOKEN_TYPE)
} }
} }

View File

@ -7,6 +7,7 @@ import {
OAuth2ErrorCode, OAuth2ErrorCode,
ResultList, ResultList,
UserRefreshToken, UserRefreshToken,
Video,
VideoCaption, VideoCaption,
VideoDetails, VideoDetails,
VideoPlaylist, VideoPlaylist,
@ -16,9 +17,11 @@ import {
import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager' import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager'
import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
import { TranslationsManager } from '../../assets/player/translations-manager' import { TranslationsManager } from '../../assets/player/translations-manager'
import { isP2PEnabled } from '../../assets/player/utils'
import { getBoolOrDefault } from '../../root-helpers/local-storage-utils'
import { peertubeLocalStorage } from '../../root-helpers/peertube-web-storage' import { peertubeLocalStorage } from '../../root-helpers/peertube-web-storage'
import { PluginsManager } from '../../root-helpers/plugins-manager' import { PluginsManager } from '../../root-helpers/plugins-manager'
import { Tokens } from '../../root-helpers/users' import { UserLocalStorageKeys, UserTokens } from '../../root-helpers/users'
import { objectToUrlEncoded } from '../../root-helpers/utils' import { objectToUrlEncoded } from '../../root-helpers/utils'
import { RegisterClientHelpers } from '../../types/register-client-option.model' import { RegisterClientHelpers } from '../../types/register-client-option.model'
import { PeerTubeEmbedApi } from './embed-api' import { PeerTubeEmbedApi } from './embed-api'
@ -48,7 +51,7 @@ export class PeerTubeEmbed {
mode: PlayerMode mode: PlayerMode
scope = 'peertube' scope = 'peertube'
userTokens: Tokens userTokens: UserTokens
headers = new Headers() headers = new Headers()
LOCAL_STORAGE_OAUTH_CLIENT_KEYS = { LOCAL_STORAGE_OAUTH_CLIENT_KEYS = {
CLIENT_ID: 'client_id', CLIENT_ID: 'client_id',
@ -118,7 +121,7 @@ export class PeerTubeEmbed {
return res.json() return res.json()
}).then((obj: UserRefreshToken & { code?: OAuth2ErrorCode }) => { }).then((obj: UserRefreshToken & { code?: OAuth2ErrorCode }) => {
if (!obj || obj.code === OAuth2ErrorCode.INVALID_GRANT) { if (!obj || obj.code === OAuth2ErrorCode.INVALID_GRANT) {
Tokens.flush() UserTokens.flushLocalStorage(peertubeLocalStorage)
this.removeTokensFromHeaders() this.removeTokensFromHeaders()
return resolve() return resolve()
@ -126,7 +129,7 @@ export class PeerTubeEmbed {
this.userTokens.accessToken = obj.access_token this.userTokens.accessToken = obj.access_token
this.userTokens.refreshToken = obj.refresh_token this.userTokens.refreshToken = obj.refresh_token
this.userTokens.save() UserTokens.saveToLocalStorage(peertubeLocalStorage, this.userTokens)
this.setHeadersFromTokens() this.setHeadersFromTokens()
@ -138,7 +141,7 @@ export class PeerTubeEmbed {
return refreshingTokenPromise return refreshingTokenPromise
.catch(() => { .catch(() => {
Tokens.flush() UserTokens.flushLocalStorage(peertubeLocalStorage)
this.removeTokensFromHeaders() this.removeTokensFromHeaders()
}).then(() => fetch(url, { }).then(() => fetch(url, {
@ -258,7 +261,7 @@ export class PeerTubeEmbed {
} }
async init () { async init () {
this.userTokens = Tokens.load() this.userTokens = UserTokens.getUserTokens(peertubeLocalStorage)
await this.initCore() await this.initCore()
} }
@ -515,6 +518,8 @@ export class PeerTubeEmbed {
muted: this.muted, muted: this.muted,
loop: this.loop, loop: this.loop,
p2pEnabled: this.isP2PEnabled(videoInfo),
captions: videoCaptions.length !== 0, captions: videoCaptions.length !== 0,
subtitle: this.subtitle, subtitle: this.subtitle,
@ -669,7 +674,7 @@ export class PeerTubeEmbed {
const title = this.title ? videoInfo.name : undefined const title = this.title ? videoInfo.name : undefined
const description = this.warningTitle && (!videoInfo.isLocal || this.config.tracker.enabled) const description = this.warningTitle && this.isP2PEnabled(videoInfo)
? '<span class="text">' + peertubeTranslate('Watching this video may reveal your IP address to others.') + '</span>' ? '<span class="text">' + peertubeTranslate('Watching this video may reveal your IP address to others.') + '</span>'
: undefined : undefined
@ -784,6 +789,15 @@ export class PeerTubeEmbed {
translate: (value: string) => Promise.resolve(peertubeTranslate(value, translations)) translate: (value: string) => Promise.resolve(peertubeTranslate(value, translations))
} }
} }
private isP2PEnabled (video: Video) {
const userP2PEnabled = getBoolOrDefault(
peertubeLocalStorage.getItem(UserLocalStorageKeys.P2P_ENABLED),
this.config.defaults.p2p.enabled
)
return isP2PEnabled(video, this.config, userP2PEnabled)
}
} }
PeerTubeEmbed.main() PeerTubeEmbed.main()

View File

@ -92,6 +92,11 @@ defaults:
# No licence by default # No licence by default
licence: null licence: null
p2p:
# Enable P2P by default
# Can be enabled/disabled by anonymous users and logged in users
enabled: true
# From the project root directory # From the project root directory
storage: storage:
tmp: 'storage/tmp/' # Use to download data (imports etc), store uploaded files before and during processing... tmp: 'storage/tmp/' # Use to download data (imports etc), store uploaded files before and during processing...
@ -216,7 +221,7 @@ security:
enabled: true enabled: true
tracker: tracker:
# If you disable the tracker, you disable the P2P aspect of PeerTube # If you disable the tracker, you disable the P2P on your PeerTube instance
enabled: true enabled: true
# Only handle requests on your videos # Only handle requests on your videos
# If you set this to false it means you have a public tracker # If you set this to false it means you have a public tracker

View File

@ -90,6 +90,11 @@ defaults:
# No licence by default # No licence by default
licence: null licence: null
p2p:
# Enable P2P by default
# Can be enabled/disabled by anonymous users and logged in users
enabled: true
# From the project root directory # From the project root directory
storage: storage:
tmp: '/var/www/peertube/storage/tmp/' # Use to download data (imports etc), store uploaded files before and during processing... tmp: '/var/www/peertube/storage/tmp/' # Use to download data (imports etc), store uploaded files before and during processing...

View File

@ -183,6 +183,7 @@ async function createUser (req: express.Request, res: express.Response) {
password: body.password, password: body.password,
email: body.email, email: body.email,
nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
p2pEnabled: CONFIG.DEFAULTS.P2P.ENABLED,
autoPlayVideo: true, autoPlayVideo: true,
role: body.role, role: body.role,
videoQuota: body.videoQuota, videoQuota: body.videoQuota,
@ -232,6 +233,7 @@ async function registerUser (req: express.Request, res: express.Response) {
password: body.password, password: body.password,
email: body.email, email: body.email,
nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
p2pEnabled: CONFIG.DEFAULTS.P2P.ENABLED,
autoPlayVideo: true, autoPlayVideo: true,
role: UserRole.USER, role: UserRole.USER,
videoQuota: CONFIG.USER.VIDEO_QUOTA, videoQuota: CONFIG.USER.VIDEO_QUOTA,

View File

@ -197,7 +197,7 @@ async function updateMe (req: express.Request, res: express.Response) {
const keysToUpdate: (keyof UserUpdateMe & keyof AttributesOnly<UserModel>)[] = [ const keysToUpdate: (keyof UserUpdateMe & keyof AttributesOnly<UserModel>)[] = [
'password', 'password',
'nsfwPolicy', 'nsfwPolicy',
'webTorrentEnabled', 'p2pEnabled',
'autoPlayVideo', 'autoPlayVideo',
'autoPlayNextVideo', 'autoPlayNextVideo',
'autoPlayNextVideoPlaylist', 'autoPlayNextVideoPlaylist',
@ -213,6 +213,12 @@ async function updateMe (req: express.Request, res: express.Response) {
if (body[key] !== undefined) user.set(key, body[key]) if (body[key] !== undefined) user.set(key, body[key])
} }
if (body.p2pEnabled !== undefined) {
user.set('p2pEnabled', body.p2pEnabled)
} else if (body.webTorrentEnabled !== undefined) { // FIXME: deprecated in 4.1
user.set('p2pEnabled', body.webTorrentEnabled)
}
if (body.email !== undefined) { if (body.email !== undefined) {
if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
user.pendingEmail = body.email user.pendingEmail = body.email

View File

@ -49,7 +49,7 @@ function isUserNSFWPolicyValid (value: any) {
return exists(value) && nsfwPolicies.includes(value) return exists(value) && nsfwPolicies.includes(value)
} }
function isUserWebTorrentEnabledValid (value: any) { function isUserP2PEnabledValid (value: any) {
return isBooleanValid(value) return isBooleanValid(value)
} }
@ -109,7 +109,7 @@ export {
isUserAdminFlagsValid, isUserAdminFlagsValid,
isUserEmailVerifiedValid, isUserEmailVerifiedValid,
isUserNSFWPolicyValid, isUserNSFWPolicyValid,
isUserWebTorrentEnabledValid, isUserP2PEnabledValid,
isUserAutoPlayVideoValid, isUserAutoPlayVideoValid,
isUserAutoPlayNextVideoValid, isUserAutoPlayNextVideoValid,
isUserAutoPlayNextVideoPlaylistValid, isUserAutoPlayNextVideoPlaylistValid,

View File

@ -78,6 +78,9 @@ const CONFIG = {
COMMENTS_ENABLED: config.get<boolean>('defaults.publish.comments_enabled'), COMMENTS_ENABLED: config.get<boolean>('defaults.publish.comments_enabled'),
PRIVACY: config.get<VideoPrivacy>('defaults.publish.privacy'), PRIVACY: config.get<VideoPrivacy>('defaults.publish.privacy'),
LICENCE: config.get<number>('defaults.publish.licence') LICENCE: config.get<number>('defaults.publish.licence')
},
P2P: {
ENABLED: config.get<boolean>('defaults.p2p.enabled')
} }
}, },

View File

@ -25,7 +25,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
const LAST_MIGRATION_VERSION = 670 const LAST_MIGRATION_VERSION = 675
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -144,6 +144,7 @@ async function createOAuthAdminIfNotExist () {
role, role,
verified: true, verified: true,
nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
p2pEnabled: CONFIG.DEFAULTS.P2P.ENABLED,
videoQuota: -1, videoQuota: -1,
videoQuotaDaily: -1 videoQuotaDaily: -1
} }

View File

@ -0,0 +1,21 @@
import * as Sequelize from 'sequelize'
async function up (utils: {
transaction: Sequelize.Transaction
queryInterface: Sequelize.QueryInterface
sequelize: Sequelize.Sequelize
db: any
}): Promise<void> {
await utils.queryInterface.renameColumn('user', 'webTorrentEnabled', 'p2pEnabled')
await utils.sequelize.query('ALTER TABLE "user" ALTER COLUMN "p2pEnabled" DROP DEFAULT')
}
function down (options) {
throw new Error('Not implemented.')
}
export {
up,
down
}

View File

@ -226,6 +226,7 @@ async function createUserFromExternal (pluginAuth: string, options: {
password: null, password: null,
email: options.email, email: options.email,
nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
p2pEnabled: CONFIG.DEFAULTS.P2P.ENABLED,
autoPlayVideo: true, autoPlayVideo: true,
role: options.role, role: options.role,
videoQuota: CONFIG.USER.VIDEO_QUOTA, videoQuota: CONFIG.USER.VIDEO_QUOTA,

View File

@ -61,6 +61,9 @@ class ServerConfigManager {
commentsEnabled: CONFIG.DEFAULTS.PUBLISH.COMMENTS_ENABLED, commentsEnabled: CONFIG.DEFAULTS.PUBLISH.COMMENTS_ENABLED,
privacy: CONFIG.DEFAULTS.PUBLISH.PRIVACY, privacy: CONFIG.DEFAULTS.PUBLISH.PRIVACY,
licence: CONFIG.DEFAULTS.PUBLISH.LICENCE licence: CONFIG.DEFAULTS.PUBLISH.LICENCE
},
p2p: {
enabled: CONFIG.DEFAULTS.P2P.ENABLED
} }
}, },

View File

@ -15,6 +15,7 @@ import {
isUserDisplayNameValid, isUserDisplayNameValid,
isUserNoModal, isUserNoModal,
isUserNSFWPolicyValid, isUserNSFWPolicyValid,
isUserP2PEnabledValid,
isUserPasswordValid, isUserPasswordValid,
isUserPasswordValidOrEmpty, isUserPasswordValidOrEmpty,
isUserRoleValid, isUserRoleValid,
@ -239,6 +240,9 @@ const usersUpdateMeValidator = [
body('autoPlayVideo') body('autoPlayVideo')
.optional() .optional()
.custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'), .custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
body('p2pEnabled')
.optional()
.custom(isUserP2PEnabledValid).withMessage('Should have a valid p2p enabled boolean'),
body('videoLanguages') body('videoLanguages')
.optional() .optional()
.custom(isUserVideoLanguages).withMessage('Should have a valid video languages attribute'), .custom(isUserVideoLanguages).withMessage('Should have a valid video languages attribute'),

View File

@ -55,7 +55,7 @@ import {
isUserVideoQuotaDailyValid, isUserVideoQuotaDailyValid,
isUserVideoQuotaValid, isUserVideoQuotaValid,
isUserVideosHistoryEnabledValid, isUserVideosHistoryEnabledValid,
isUserWebTorrentEnabledValid isUserP2PEnabledValid
} from '../../helpers/custom-validators/users' } from '../../helpers/custom-validators/users'
import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
import { DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants' import { DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants'
@ -267,10 +267,9 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
nsfwPolicy: NSFWPolicyType nsfwPolicy: NSFWPolicyType
@AllowNull(false) @AllowNull(false)
@Default(true) @Is('p2pEnabled', value => throwIfNotValid(value, isUserP2PEnabledValid, 'P2P enabled'))
@Is('UserWebTorrentEnabled', value => throwIfNotValid(value, isUserWebTorrentEnabledValid, 'WebTorrent enabled'))
@Column @Column
webTorrentEnabled: boolean p2pEnabled: boolean
@AllowNull(false) @AllowNull(false)
@Default(true) @Default(true)
@ -892,7 +891,11 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
emailVerified: this.emailVerified, emailVerified: this.emailVerified,
nsfwPolicy: this.nsfwPolicy, nsfwPolicy: this.nsfwPolicy,
webTorrentEnabled: this.webTorrentEnabled,
// FIXME: deprecated in 4.1
webTorrentEnabled: this.p2pEnabled,
p2pEnabled: this.p2pEnabled,
videosHistoryEnabled: this.videosHistoryEnabled, videosHistoryEnabled: this.videosHistoryEnabled,
autoPlayVideo: this.autoPlayVideo, autoPlayVideo: this.autoPlayVideo,
autoPlayNextVideo: this.autoPlayNextVideo, autoPlayNextVideo: this.autoPlayNextVideo,

View File

@ -21,18 +21,7 @@ describe('Test config defaults', function () {
before(async function () { before(async function () {
this.timeout(30000) this.timeout(30000)
const overrideConfig = { server = await createSingleServer(1)
defaults: {
publish: {
comments_enabled: false,
download_enabled: false,
privacy: VideoPrivacy.INTERNAL,
licence: 4
}
}
}
server = await createSingleServer(1, overrideConfig)
await setAccessTokensToServers([ server ]) await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ]) await setDefaultVideoChannel([ server ])
@ -40,6 +29,23 @@ describe('Test config defaults', function () {
}) })
describe('Default publish values', function () { describe('Default publish values', function () {
before(async function () {
const overrideConfig = {
defaults: {
publish: {
comments_enabled: false,
download_enabled: false,
privacy: VideoPrivacy.INTERNAL,
licence: 4
}
}
}
await server.kill()
await server.run(overrideConfig)
})
const attributes = { const attributes = {
name: 'video', name: 'video',
downloadEnabled: undefined, downloadEnabled: undefined,
@ -117,6 +123,45 @@ describe('Test config defaults', function () {
}) })
}) })
describe('Default P2P values', function () {
before(async function () {
const overrideConfig = {
defaults: {
p2p: {
enabled: false
}
}
}
await server.kill()
await server.run(overrideConfig)
})
it('Should not have P2P enabled', async function () {
const config = await server.config.getConfig()
expect(config.defaults.p2p.enabled).to.be.false
})
it('Should create a user with this default setting', async function () {
await server.users.create({ username: 'user_p2p_1' })
const userToken = await server.login.getAccessToken('user_p2p_1')
const { p2pEnabled } = await server.users.getMyInfo({ token: userToken })
expect(p2pEnabled).to.be.false
})
it('Should register a user with this default setting', async function () {
await server.users.register({ username: 'user_p2p_2' })
const userToken = await server.login.getAccessToken('user_p2p_2')
const { p2pEnabled } = await server.users.getMyInfo({ token: userToken })
expect(p2pEnabled).to.be.false
})
})
after(async function () { after(async function () {
await cleanupTests([ server ]) await cleanupTests([ server ])
}) })

View File

@ -292,7 +292,7 @@ describe('Test follows', function () {
}) })
it('Should upload a video on server 2 and 3 and propagate only the video of server 2', async function () { it('Should upload a video on server 2 and 3 and propagate only the video of server 2', async function () {
this.timeout(60000) this.timeout(120000)
await servers[1].videos.upload({ attributes: { name: 'server2' } }) await servers[1].videos.upload({ attributes: { name: 'server2' } })
await servers[2].videos.upload({ attributes: { name: 'server3' } }) await servers[2].videos.upload({ attributes: { name: 'server3' } })

View File

@ -559,6 +559,28 @@ describe('Test users', function () {
expect(user.autoPlayNextVideo).to.be.true expect(user.autoPlayNextVideo).to.be.true
}) })
it('Should be able to change the p2p attribute', async function () {
{
await server.users.updateMe({
token: userToken,
webTorrentEnabled: false
})
const user = await server.users.getMyInfo({ token: userToken })
expect(user.p2pEnabled).to.be.false
}
{
await server.users.updateMe({
token: userToken,
p2pEnabled: true
})
const user = await server.users.getMyInfo({ token: userToken })
expect(user.p2pEnabled).to.be.true
}
})
it('Should be able to change the email attribute', async function () { it('Should be able to change the email attribute', async function () {
await server.users.updateMe({ await server.users.updateMe({
token: userToken, token: userToken,

View File

@ -55,6 +55,10 @@ export interface ServerConfig {
privacy: VideoPrivacy privacy: VideoPrivacy
licence: number licence: number
} }
p2p: {
enabled: boolean
}
} }
webadmin: { webadmin: {

View File

@ -5,7 +5,10 @@ export interface UserUpdateMe {
description?: string description?: string
nsfwPolicy?: NSFWPolicyType nsfwPolicy?: NSFWPolicyType
// FIXME: deprecated in favour of p2pEnabled in 4.1
webTorrentEnabled?: boolean webTorrentEnabled?: boolean
p2pEnabled?: boolean
autoPlayVideo?: boolean autoPlayVideo?: boolean
autoPlayNextVideo?: boolean autoPlayNextVideo?: boolean
autoPlayNextVideoPlaylist?: boolean autoPlayNextVideoPlaylist?: boolean

View File

@ -20,7 +20,11 @@ export interface User {
autoPlayVideo: boolean autoPlayVideo: boolean
autoPlayNextVideo: boolean autoPlayNextVideo: boolean
autoPlayNextVideoPlaylist: boolean autoPlayNextVideoPlaylist: boolean
// @deprecated in favour of p2pEnabled
webTorrentEnabled: boolean webTorrentEnabled: boolean
p2pEnabled: boolean
videosHistoryEnabled: boolean videosHistoryEnabled: boolean
videoLanguages: string[] videoLanguages: string[]

View File

@ -6679,7 +6679,7 @@ components:
type: integer type: integer
description: The user daily video quota in bytes description: The user daily video quota in bytes
example: -1 example: -1
webtorrentEnabled: p2pEnabled:
type: boolean type: boolean
description: Enable P2P in the player description: Enable P2P in the player
UserWithStats: UserWithStats:
@ -6780,7 +6780,7 @@ components:
- 'true' - 'true'
- 'false' - 'false'
- both - both
webTorrentEnabled: p2pEnabled:
type: boolean type: boolean
description: whether to enable P2P in the player or not description: whether to enable P2P in the player or not
autoPlayVideo: autoPlayVideo: