]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Add ability for admins to set default p2p policy
authorChocobozzz <me@florianbigard.com>
Wed, 15 Dec 2021 14:58:10 +0000 (15:58 +0100)
committerChocobozzz <me@florianbigard.com>
Thu, 16 Dec 2021 09:08:55 +0000 (10:08 +0100)
59 files changed:
client/e2e/src/po/anonymous-settings.po.ts [new file with mode: 0644]
client/e2e/src/po/my-account.po.ts [moved from client/e2e/src/po/my-account.ts with 89% similarity]
client/e2e/src/po/video-upload.po.ts
client/e2e/src/po/video-watch.po.ts
client/e2e/src/suites-all/videos.e2e-spec.ts
client/e2e/src/suites-local/custom-server-defaults.e2e-spec.ts
client/e2e/src/suites-local/user-settings.e2e-spec.ts [new file with mode: 0644]
client/e2e/src/suites-local/videos-list.e2e-spec.ts
client/e2e/src/utils/elements.ts
client/e2e/src/utils/hooks.ts
client/src/app/+videos/+video-watch/shared/information/privacy-concerns.component.ts
client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts
client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.ts
client/src/app/+videos/+video-watch/video-watch.component.ts
client/src/app/app.component.ts
client/src/app/core/auth/auth-user.model.ts
client/src/app/core/auth/auth.service.ts
client/src/app/core/core.module.ts
client/src/app/core/users/index.ts
client/src/app/core/users/user-local-storage.service.ts [new file with mode: 0644]
client/src/app/core/users/user.model.ts
client/src/app/core/users/user.service.ts
client/src/app/menu/menu.component.html
client/src/app/menu/menu.component.ts
client/src/app/shared/shared-search/search.service.ts
client/src/app/shared/shared-user-settings/user-video-settings.component.html
client/src/app/shared/shared-user-settings/user-video-settings.component.ts
client/src/assets/player/peertube-player-local-storage.ts
client/src/assets/player/peertube-player-manager.ts
client/src/assets/player/peertube-videojs-typings.ts
client/src/assets/player/utils.ts
client/src/assets/player/webtorrent/webtorrent-plugin.ts
client/src/root-helpers/index.ts
client/src/root-helpers/local-storage-utils.ts [new file with mode: 0644]
client/src/root-helpers/users/index.ts
client/src/root-helpers/users/user-local-storage-keys.ts
client/src/root-helpers/users/user-local-storage-manager.ts [deleted file]
client/src/root-helpers/users/user-tokens.ts
client/src/standalone/videos/embed.ts
config/default.yaml
config/production.yaml.example
server/controllers/api/users/index.ts
server/controllers/api/users/me.ts
server/helpers/custom-validators/users.ts
server/initializers/config.ts
server/initializers/constants.ts
server/initializers/installer.ts
server/initializers/migrations/0675-p2p-enabled.ts [new file with mode: 0644]
server/lib/auth/oauth-model.ts
server/lib/server-config-manager.ts
server/middlewares/validators/users.ts
server/models/user/user.ts
server/tests/api/server/config-defaults.ts
server/tests/api/server/follows.ts
server/tests/api/users/users.ts
shared/models/server/server-config.model.ts
shared/models/users/user-update-me.model.ts
shared/models/users/user.model.ts
support/doc/api/openapi.yaml

diff --git a/client/e2e/src/po/anonymous-settings.po.ts b/client/e2e/src/po/anonymous-settings.po.ts
new file mode 100644 (file)
index 0000000..180d371
--- /dev/null
@@ -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()
+  }
+}
similarity index 89%
rename from client/e2e/src/po/my-account.ts
rename to client/e2e/src/po/my-account.po.ts
index b51614fd994f71cc9b3bbbb6a0d4111f92bd40e6..13a764e8782464c087a1975924deb907851e2f00 100644 (file)
@@ -1,4 +1,4 @@
-import { go } from '../utils'
+import { getCheckbox, go } from '../utils'
 
 export class MyAccountPage {
 
@@ -27,6 +27,21 @@ export class MyAccountPage {
     await nsfw.scrollIntoView(false) // Avoid issues with fixed header on firefox
     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]')
     await submit.scrollIntoView(false)
     await submit.click()
index dd437c3902f681f6bd0b1235093fea632c52e388..2206b56c3faebdfcd8d96f3abc17838e76ab12c8 100644 (file)
@@ -1,5 +1,5 @@
 import { join } from 'path'
-import { clickOnCheckbox } from '../utils'
+import { getCheckbox, selectCustomSelect } from '../utils'
 
 export class VideoUploadPage {
   async navigateTo () {
@@ -32,7 +32,7 @@ export class VideoUploadPage {
   }
 
   setAsNSFW () {
-    return clickOnCheckbox('nsfw')
+    return getCheckbox('nsfw').click()
   }
 
   async validSecondUploadStep (videoName: string) {
@@ -47,6 +47,10 @@ export class VideoUploadPage {
     })
   }
 
+  setAsPublic () {
+    return selectCustomSelect('privacy', 'Public')
+  }
+
   private getSecondStepSubmitButton () {
     return $('.submit-container my-button')
   }
index 1406c971a1694ebab238fd37a18c712ed02f24aa..cecda3a8b941a1092d558294ccf10bda4952acc6 100644 (file)
@@ -39,12 +39,23 @@ export class VideoWatchPage {
     return $('my-video-comment-add').isExisting()
   }
 
+  isPrivacyWarningDisplayed () {
+    return $('my-privacy-concerns').isDisplayed()
+  }
+
   async goOnAssociatedEmbed () {
     let url = await browser.getUrl()
     url = url.replace('/w/', '/videos/embed/')
     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 () {
index 3b8305a25fa4ef8295cd36a9eff4608f9f3a887d..b3a87c8e2f5d451a6af4f9c35757a65ef3a4fc5c 100644 (file)
@@ -1,5 +1,5 @@
 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 { VideoListPage } from '../po/video-list.po'
 import { VideoUpdatePage } from '../po/video-update.po'
index c2c8edcc91ffe5df2c85af885e8242b1fbef58a2..e060d382fa520ce6c5e39458cc681285dceaace9 100644 (file)
@@ -1,7 +1,7 @@
 import { LoginPage } from '../po/login.po'
 import { VideoUploadPage } from '../po/video-upload.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', () => {
   let videoUploadPage: VideoUploadPage
@@ -10,9 +10,7 @@ describe('Custom server defaults', () => {
 
   before(async () => {
     await waitServerUp()
-  })
 
-  beforeEach(async () => {
     loginPage = new LoginPage()
     videoUploadPage = new VideoUploadPage()
     videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
@@ -20,18 +18,69 @@ describe('Custom server defaults', () => {
     await browser.maximizeWindow()
   })
 
-  it('Should upload a video with custom default values', async function () {
-    await loginPage.loginAsRootUser()
-    await videoUploadPage.navigateTo()
-    await videoUploadPage.uploadVideo()
-    await videoUploadPage.validSecondUploadStep('video')
+  describe('Publish default values', function () {
+    before(async function () {
+      await loginPage.loginAsRootUser()
+    })
+
+    it('Should upload a video with custom default values', async function () {
+      await videoUploadPage.navigateTo()
+      await videoUploadPage.uploadVideo()
+      await videoUploadPage.validSecondUploadStep('video')
 
-    await videoWatchPage.waitWatchVideoName('video')
+      await videoWatchPage.waitWatchVideoName('video')
 
-    expect(await videoWatchPage.getPrivacy()).toBe('Internal')
-    expect(await videoWatchPage.getLicence()).toBe('Attribution - Non Commercial')
-    expect(await videoWatchPage.isDownloadEnabled()).toBeFalsy()
-    expect(await videoWatchPage.areCommentsEnabled()).toBeFalsy()
+      expect(await videoWatchPage.getPrivacy()).toBe('Internal')
+      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)
+    })
+  })
 })
diff --git a/client/e2e/src/suites-local/user-settings.e2e-spec.ts b/client/e2e/src/suites-local/user-settings.e2e-spec.ts
new file mode 100644 (file)
index 0000000..b87501c
--- /dev/null
@@ -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)
+    })
+  })
+})
index bca6018b94a397dfdd2063a0e8a25620aca7ca09..ce57261b9efcb1d11ee730e1297263e0563fa249 100644 (file)
@@ -1,6 +1,6 @@
 import { AdminConfigPage } from '../po/admin-config.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 { VideoSearchPage } from '../po/video-search.po'
 import { VideoUploadPage } from '../po/video-upload.po'
index cadc46cce47e8ff23e05ce6e873134dfdd1dbd62..315718879978fe9b9cec76390b15dff8b5e64390 100644 (file)
@@ -1,7 +1,22 @@
-function clickOnCheckbox (name: string) {
-  return $(`my-peertube-checkbox[inputname=${name}] label`).click()
+function getCheckbox (name: string) {
+  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 {
-  clickOnCheckbox
+  getCheckbox,
+  selectCustomSelect
 }
index e42c6a5d8b04cc149ca4b8efd656ffce60ce8cfe..2f3d10fe39dd8c6f174c6fef76c179e02850cc06 100644 (file)
@@ -55,6 +55,9 @@ function buildConfig (suiteFile: string = undefined) {
           comments_enabled: false,
           privacy: 4,
           licence: 4
+        },
+        p2p: {
+          enabled: false
         }
       }
     }
index bbc81d46d8f4a3b2890c067c62426c05a423c0b5..24030df3e492bcae305e7de9b7aa22a98333266d 100644 (file)
@@ -1,9 +1,8 @@
 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 { HTMLServerConfig, Video } from '@shared/models'
-import { getStoredP2PEnabled } from '../../../../../assets/player/peertube-player-local-storage'
-import { isWebRTCDisabled } from '../../../../../assets/player/utils'
+import { isP2PEnabled } from '../../../../../assets/player/utils'
 
 @Component({
   selector: 'my-privacy-concerns',
@@ -15,33 +14,32 @@ export class PrivacyConcernsComponent implements OnInit {
 
   @Input() video: Video
 
-  display = true
+  display = false
 
   private serverConfig: HTMLServerConfig
 
   constructor (
-    private serverService: ServerService
+    private serverService: ServerService,
+    private userService: UserService
   ) { }
 
   ngOnInit () {
     this.serverConfig = this.serverService.getHTMLConfig()
 
-    if (isWebRTCDisabled() || this.isTrackerDisabled() || this.isP2PDisabled() || this.alreadyAccepted()) {
-      this.display = false
-    }
+    this.userService.getAnonymousOrLoggedUser()
+      .subscribe(user => this.updateDisplay(user))
   }
 
   acceptedPrivacyConcern () {
     peertubeLocalStorage.setItem(PrivacyConcernsComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'true')
-    this.display = false
-  }
 
-  private isTrackerDisabled () {
-    return this.video.isLocal && this.serverConfig.tracker.enabled === false
+    this.display = false
   }
 
-  private isP2PDisabled () {
-    return getStoredP2PEnabled() === false
+  private updateDisplay (user: User) {
+    if (isP2PEnabled(this.video, this.serverConfig, user.p2pEnabled) && !this.alreadyAccepted()) {
+      this.display = true
+    }
   }
 
   private alreadyAccepted () {
index b2863fed6edc58df3a459731dbcd1909571658c3..fbf9a368784d066e27558a35080313914c3aef45 100644 (file)
@@ -1,16 +1,9 @@
 import { Component, EventEmitter, Input, Output } from '@angular/core'
 import { Router } from '@angular/router'
-import {
-  AuthService,
-  ComponentPagination,
-  HooksService,
-  LocalStorageService,
-  Notifier,
-  SessionStorageService,
-  UserService
-} from '@app/core'
+import { AuthService, ComponentPagination, HooksService, Notifier, SessionStorageService, UserService } from '@app/core'
 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'
 
 @Component({
@@ -19,8 +12,7 @@ import { VideoPlaylistPrivacy } from '@shared/models'
   styleUrls: [ './video-watch-playlist.component.scss' ]
 })
 export class VideoWatchPlaylistComponent {
-  static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'auto_play_video_playlist'
-  static SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'loop_playlist'
+  static SESSION_STORAGE_LOOP_PLAYLIST = 'loop_playlist'
 
   @Input() playlist: VideoPlaylist
 
@@ -47,19 +39,15 @@ export class VideoWatchPlaylistComponent {
     private auth: AuthService,
     private notifier: Notifier,
     private videoPlaylist: VideoPlaylistService,
-    private localStorageService: LocalStorageService,
     private sessionStorage: SessionStorageService,
     private router: Router
   ) {
-    // defaults to true
-    this.autoPlayNextVideoPlaylist = this.auth.isLoggedIn()
-      ? this.auth.getUser().autoPlayNextVideoPlaylist
-      : this.localStorageService.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false'
+    this.userService.getAnonymousOrLoggedUser()
+      .subscribe(user => this.autoPlayNextVideoPlaylist = user.autoPlayNextVideoPlaylist)
 
     this.setAutoPlayNextVideoPlaylistSwitchText()
 
-    // defaults to false
-    this.loopPlaylist = this.sessionStorage.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true'
+    this.loopPlaylist = getBoolOrDefault(this.sessionStorage.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_LOOP_PLAYLIST), false)
     this.setLoopPlaylistSwitchText()
   }
 
@@ -201,16 +189,9 @@ export class VideoWatchPlaylistComponent {
     this.autoPlayNextVideoPlaylist = !this.autoPlayNextVideoPlaylist
     this.setAutoPlayNextVideoPlaylistSwitchText()
 
-    peertubeLocalStorage.setItem(
-      VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST,
-      this.autoPlayNextVideoPlaylist.toString()
-    )
+    const details = { autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist }
 
     if (this.auth.isLoggedIn()) {
-      const details = {
-        autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist
-      }
-
       this.userService.updateMyProfile(details)
         .subscribe({
           next: () => {
@@ -219,6 +200,8 @@ export class VideoWatchPlaylistComponent {
 
           error: err => this.notifier.error(err.message)
         })
+    } else {
+      this.userService.updateMyAnonymousProfile(details)
     }
   }
 
@@ -227,7 +210,7 @@ export class VideoWatchPlaylistComponent {
     this.setLoopPlaylistSwitchText()
 
     peertubeSessionStorage.setItem(
-      VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST,
+      VideoWatchPlaylistComponent.SESSION_STORAGE_LOOP_PLAYLIST,
       this.loopPlaylist.toString()
     )
   }
index dfc296d154ff606b83454f0d048a34cb37419395..97f742499c6bab7a2d1989d8f7ae881c673afc51 100644 (file)
@@ -1,10 +1,9 @@
 import { Observable } from 'rxjs'
 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 { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature'
 import { VideoPlaylist } from '@app/shared/shared-video-playlist'
-import { UserLocalStorageKeys } from '@root-helpers/users'
 import { RecommendationInfo } from './recommendation-info.model'
 import { RecommendedVideosStore } from './recommended-videos.store'
 
@@ -39,24 +38,14 @@ export class RecommendedVideosComponent implements OnInit, OnChanges {
     private userService: UserService,
     private authService: AuthService,
     private notifier: Notifier,
-    private store: RecommendedVideosStore,
-    private sessionStorageService: SessionStorageService
+    private store: RecommendedVideosStore
   ) {
     this.videos$ = this.store.recommendations$
     this.hasVideos$ = this.store.hasRecommendations$
     this.videos$.subscribe(videos => this.gotRecommendations.emit(videos))
 
-    if (this.authService.isLoggedIn()) {
-      this.autoPlayNextVideo = this.authService.getUser().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.userService.getAnonymousOrLoggedUser()
+      .subscribe(user => this.autoPlayNextVideo = user.autoPlayNextVideo)
 
     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 () {
-    this.sessionStorageService.setItem(UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO, this.autoPlayNextVideo.toString())
+    const details = { autoPlayNextVideo: this.autoPlayNextVideo }
 
     if (this.authService.isLoggedIn()) {
-      const details = {
-        autoPlayNextVideo: this.autoPlayNextVideo
-      }
-
       this.userService.updateMyProfile(details)
         .subscribe({
           next: () => {
@@ -92,6 +77,8 @@ export class RecommendedVideosComponent implements OnInit, OnChanges {
 
           error: err => this.notifier.error(err.message)
         })
+    } else {
+      this.userService.updateMyAnonymousProfile(details)
     }
   }
 }
index fd61bcbf044f83317593fa12b15020c3096a860c..d542f243cebd3b1822269dadc46e61ce38d6f48b 100644 (file)
@@ -1,5 +1,6 @@
 import { Hotkey, HotkeysService } from 'angular2-hotkeys'
 import { forkJoin, Subscription } from 'rxjs'
+import { isP2PEnabled } from 'src/assets/player/utils'
 import { PlatformLocation } from '@angular/common'
 import { Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
@@ -14,6 +15,7 @@ import {
   RestExtractor,
   ScreenService,
   ServerService,
+  User,
   UserService
 } from '@app/core'
 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'
     )
 
-    forkJoin([ videoObs, this.videoCaptionService.listCaptions(videoId) ])
-      .subscribe({
-        next: ([ video, captionsResult ]) => {
-          const queryParams = this.route.snapshot.queryParams
+    forkJoin([
+      videoObs,
+      this.videoCaptionService.listCaptions(videoId),
+      this.userService.getAnonymousOrLoggedUser()
+    ]).subscribe({
+      next: ([ video, captionsResult, loggedInOrAnonymousUser ]) => {
+        const queryParams = this.route.snapshot.queryParams
 
-          const urlOptions = {
-            resume: queryParams.resume,
+        const urlOptions = {
+          resume: queryParams.resume,
 
-            startTime: queryParams.start,
-            stopTime: queryParams.stop,
+          startTime: queryParams.start,
+          stopTime: queryParams.stop,
 
-            muted: queryParams.muted,
-            loop: queryParams.loop,
-            subtitle: queryParams.subtitle,
+          muted: queryParams.muted,
+          loop: queryParams.loop,
+          subtitle: queryParams.subtitle,
 
-            playerMode: queryParams.mode,
-            peertubeLink: false
-          }
+          playerMode: queryParams.mode,
+          peertubeLink: false
+        }
 
-          this.onVideoFetched(video, captionsResult.data, urlOptions)
-              .catch(err => this.handleGlobalError(err))
-        },
+        this.onVideoFetched({ video, videoCaptions: captionsResult.data, loggedInOrAnonymousUser, urlOptions })
+            .catch(err => this.handleGlobalError(err))
+      },
 
-        error: err => this.handleRequestError(err)
-      })
+      error: err => this.handleRequestError(err)
+    })
   }
 
   private loadPlaylist (playlistId: string) {
@@ -323,11 +328,14 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     this.notifier.error(errorMessage)
   }
 
-  private async onVideoFetched (
-    video: VideoDetails,
-    videoCaptions: VideoCaption[],
+  private async onVideoFetched (options: {
+    video: VideoDetails
+    videoCaptions: VideoCaption[]
     urlOptions: URLOptions
-  ) {
+    loggedInOrAnonymousUser: User
+  }) {
+    const { video, videoCaptions, urlOptions, loggedInOrAnonymousUser } = options
+
     this.subscribeToLiveEventsIfNeeded(this.video, video)
 
     this.video = video
@@ -346,7 +354,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
       if (res === false) return this.location.back()
     }
 
-    this.buildPlayer(urlOptions)
+    this.buildPlayer(urlOptions, loggedInOrAnonymousUser)
       .catch(err => console.error('Cannot build the player', err))
 
     this.setOpenGraphTags()
@@ -359,7 +367,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     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
     this.flushPlayer()
 
@@ -380,6 +388,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
       video: this.video,
       videoCaptions: this.videoCaptions,
       urlOptions,
+      loggedInOrAnonymousUser,
       user: this.user
     }
     const { playerMode, playerOptions } = await this.hooks.wrapFun(
@@ -517,9 +526,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     video: VideoDetails
     videoCaptions: VideoCaption[]
     urlOptions: CustomizationOptions & { playerMode: PlayerMode }
+    loggedInOrAnonymousUser: User
     user?: AuthUser
   }) {
-    const { video, videoCaptions, urlOptions, user } = params
+    const { video, videoCaptions, urlOptions, loggedInOrAnonymousUser, user } = params
 
     const getStartTime = () => {
       const byUrl = urlOptions.startTime !== undefined
@@ -547,6 +557,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     const options: PeertubePlayerManagerOptions = {
       common: {
         autoplay: this.isAutoplay(),
+        p2pEnabled: isP2PEnabled(video, this.serverConfig, loggedInOrAnonymousUser.p2pEnabled),
+
         nextVideo: () => this.playNextVideoInAngularZone(),
 
         playerElement: this.playerElement,
index 95af89b19d464297eedd49bf70e0139d7755c559..a60138af9fc22f8d0edb4458376abe54de53f983 100644 (file)
@@ -14,7 +14,8 @@ import {
   ScrollService,
   ServerService,
   ThemeService,
-  User
+  User,
+  UserLocalStorageService
 } from '@app/core'
 import { HooksService } from '@app/core/plugins/hooks.service'
 import { PluginService } from '@app/core/plugins/plugin.service'
@@ -70,6 +71,7 @@ export class AppComponent implements OnInit, AfterViewInit {
     private ngbConfig: NgbConfig,
     private loadingBar: LoadingBarService,
     private scrollService: ScrollService,
+    private userLocalStorage: UserLocalStorageService,
     public menu: MenuService
   ) {
     this.ngbConfig.animation = false
@@ -86,6 +88,8 @@ export class AppComponent implements OnInit, AfterViewInit {
   ngOnInit () {
     document.getElementById('incompatible-browser').className += ' browser-ok'
 
+    this.loadUser()
+
     this.serverConfig = this.serverService.getHTMLConfig()
 
     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`)
     ])
   }
+
+  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)
+  }
 }
index f10b37e5ad71cff8905f089a5972504d353367fc..cd9665e378d96ab67a67ad825b8f98869475c0a2 100644 (file)
@@ -1,13 +1,7 @@
 import { Observable, of } from 'rxjs'
 import { map } from 'rxjs/operators'
 import { User } from '@app/core/users/user.model'
-import {
-  flushUserInfoFromLocalStorage,
-  getUserInfoFromLocalStorage,
-  saveUserInfoIntoLocalStorage,
-  TokenOptions,
-  Tokens
-} from '@root-helpers/users'
+import { UserTokens } from '@root-helpers/users'
 import { hasUserRight } from '@shared/core-utils/users'
 import {
   MyUser as ServerMyUserModel,
@@ -19,31 +13,15 @@ import {
 } from '@shared/models'
 
 export class AuthUser extends User implements ServerMyUserModel {
-  tokens: Tokens
+  tokens: UserTokens
   specialPlaylists: MyUserSpecialPlaylist[]
 
   canSeeVideosLink = true
 
-  static load () {
-    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) {
+  constructor (userHash: Partial<ServerMyUserModel>, hashTokens: Partial<UserTokens>) {
     super(userHash)
 
-    this.tokens = new Tokens(hashTokens)
+    this.tokens = new UserTokens(hashTokens)
     this.specialPlaylists = userHash.specialPlaylists
   }
 
@@ -77,20 +55,6 @@ export class AuthUser extends User implements ServerMyUserModel {
     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> {
     if (!this.isUploadDisabled()) {
       this.canSeeVideosLink = true
index 79239a17a2136c107307fba93d4dfa3f1babd813..2ac88c18529b8ad071e004ccdf2a778d3407dc20 100644 (file)
@@ -5,7 +5,7 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'
 import { Injectable } from '@angular/core'
 import { Router } from '@angular/router'
 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 { environment } from '../../../environments/environment'
 import { RestExtractor } from '../rest/rest-extractor.service'
@@ -34,6 +34,7 @@ export class AuthService {
 
   loginChangedSource: Observable<AuthStatus>
   userInformationLoaded = new ReplaySubject<boolean>(1)
+  tokensRefreshed = new ReplaySubject<void>(1)
   hotkeys: Hotkey[]
 
   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.loginChangedSource = this.loginChanged.asObservable()
 
-    // Return null if there is nothing to load
-    this.user = AuthUser.load()
-
     // Set HotKeys
     this.hotkeys = [
       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 () {
     // Fetch the client_id/client_secret
     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
 
-    AuthUser.flush()
-
     this.setStatus(AuthStatus.LoggedOut)
 
     this.hotkeysService.remove(this.hotkeys)
@@ -239,7 +239,6 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
         .subscribe({
           next: res => {
             this.user.patch(res)
-            this.user.save()
 
             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.save()
 
     this.setStatus(AuthStatus.LoggedIn)
     this.userInformationLoaded.next(true)
@@ -272,7 +270,7 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
 
   private handleRefreshToken (obj: UserRefreshToken) {
     this.user.refreshTokens(obj.access_token, obj.refresh_token)
-    this.user.save()
+    this.tokensRefreshed.next()
   }
 
   private setStatus (status: AuthStatus) {
index 04be0671c401ca61e7a80bb9e256cabf18ef636b..d80f95ed63afcd4fb5d54f91e213dfe643c16706 100644 (file)
@@ -30,7 +30,7 @@ import { ServerConfigResolver } from './routing/server-config-resolver.service'
 import { ScopedTokensService } from './scoped-tokens'
 import { ServerService } from './server'
 import { ThemeService } from './theme'
-import { UserService } from './users'
+import { UserLocalStorageService, UserService } from './users'
 import { LocalStorageService, ScreenService, SessionStorageService } from './wrappers'
 
 @NgModule({
@@ -79,6 +79,7 @@ import { LocalStorageService, ScreenService, SessionStorageService } from './wra
     RestService,
 
     UserService,
+    UserLocalStorageService,
 
     ScreenService,
     LocalStorageService,
index 7b5a67bc78513926957a988459da0da1a01df486..e235a875b7226af6fb31c489e10f1f5716c6508e 100644 (file)
@@ -1,2 +1,3 @@
+export * from './user-local-storage.service'
 export * from './user.model'
 export * from './user.service'
diff --git a/client/src/app/core/users/user-local-storage.service.ts b/client/src/app/core/users/user-local-storage.service.ts
new file mode 100644 (file)
index 0000000..85da46e
--- /dev/null
@@ -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)
+  }
+}
index c0e5d31698622c1b6f3f597946659094de30e7a4..f211051cec49ae29a7e16ffdae16e2dab9ee8169 100644 (file)
@@ -26,7 +26,11 @@ export class User implements UserServerModel {
   autoPlayVideo: boolean
   autoPlayNextVideo: boolean
   autoPlayNextVideoPlaylist: boolean
-  webTorrentEnabled: boolean
+
+  p2pEnabled: boolean
+  // FIXME: deprecated in 4.1
+  webTorrentEnabled: never
+
   videosHistoryEnabled: boolean
   videoLanguages: string[]
 
@@ -84,7 +88,7 @@ export class User implements UserServerModel {
     this.videoCommentsCount = hash.videoCommentsCount
 
     this.nsfwPolicy = hash.nsfwPolicy
-    this.webTorrentEnabled = hash.webTorrentEnabled
+    this.p2pEnabled = hash.p2pEnabled
     this.autoPlayVideo = hash.autoPlayVideo
     this.autoPlayNextVideo = hash.autoPlayNextVideo
     this.autoPlayNextVideoPlaylist = hash.autoPlayNextVideoPlaylist
index 632361e9bef3bdf929d50c5f398ae64bf55f1546..a6a0474aba0e2ebd854cdd355a3128be6d9f67aa 100644 (file)
@@ -1,11 +1,10 @@
 import { SortMeta } from 'primeng/api'
 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 { Injectable } from '@angular/core'
 import { AuthService } from '@app/core/auth'
 import { getBytes } from '@root-helpers/bytes'
-import { UserLocalStorageKeys } from '@root-helpers/users'
 import {
   ActorImage,
   ResultList,
@@ -17,10 +16,9 @@ import {
   UserUpdateMe,
   UserVideoQuota
 } from '@shared/models'
-import { ServerService } from '../'
 import { environment } from '../../../environments/environment'
 import { RestExtractor, RestPagination, RestService } from '../rest'
-import { LocalStorageService, SessionStorageService } from '../wrappers/storage.service'
+import { UserLocalStorageService } from './'
 import { User } from './user.model'
 
 @Injectable()
@@ -33,12 +31,10 @@ export class UserService {
 
   constructor (
     private authHttp: HttpClient,
-    private server: ServerService,
     private authService: AuthService,
     private restExtractor: RestExtractor,
     private restService: RestService,
-    private localStorageService: LocalStorageService,
-    private sessionStorageService: SessionStorageService
+    private userLocalStorageService: UserLocalStorageService
   ) { }
 
   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) {
     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 () {
     const url = UserService.BASE_USERS_URL + 'me'
 
@@ -287,36 +253,6 @@ export class UserService {
                .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: {
     pagination: RestPagination
     sort: SortMeta
index 9ea991042971021e8e1be60963f4bc7bd8269415..48b3fdc854a48c3797701018dafc48b10f51b1ad 100644 (file)
@@ -60,7 +60,7 @@
                 <my-global-icon iconName="p2p" aria-hidden="true"></my-global-icon>
                 <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>
 
               <div class="dropdown-divider"></div>
index d5ddc29cbe2508343d8eb585ae313cb268ab6378..983f0a938388814e6585a7d31e075b2fb26d299a 100644 (file)
@@ -196,9 +196,9 @@ export class MenuComponent implements OnInit {
 
   toggleUseP2P () {
     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())
   }
 
index 61acfb466cdcce52dfb5977c83a278d6a20278ab..ad2de0f37f54dcb5741121a2d46dccddbe4e2d8b 100644 (file)
@@ -4,7 +4,6 @@ import { HttpClient, HttpParams } from '@angular/common/http'
 import { Injectable } from '@angular/core'
 import { ComponentPaginationLight, RestExtractor, RestPagination, RestService } from '@app/core'
 import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
-import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
 import {
   ResultList,
   Video as VideoServerModel,
@@ -25,11 +24,7 @@ export class SearchService {
     private restService: RestService,
     private videoService: VideoService,
     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: {
     search?: string
index bc9dd0f7ffd82db272518d48d08d2b6c54f53b11..4843f65b993b7f39e43c8f374f0ce3de5b3c2834 100644 (file)
@@ -38,7 +38,7 @@
 
   <div class="form-group">
     <my-peertube-checkbox
-      inputName="webTorrentEnabled" formControlName="webTorrentEnabled" [recommended]="true"
+      inputName="p2pEnabled" formControlName="p2pEnabled" [recommended]="true"
       i18n-labelText labelText="Help share videos being played"
     >
       <ng-container ngProjectAs="description">
index 0cd889a8a3a27d885c6310070ec01ab2a5aadf26..7d6b69469c7c44e86d933586ae8059847a62df45 100644 (file)
@@ -34,7 +34,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
   ngOnInit () {
     this.buildForm({
       nsfwPolicy: null,
-      webTorrentEnabled: null,
+      p2pEnabled: null,
       autoPlayVideo: null,
       autoPlayNextVideo: null,
       videoLanguages: null
@@ -48,7 +48,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
 
           this.form.patchValue({
             nsfwPolicy: this.user.nsfwPolicy || this.defaultNSFWPolicy,
-            webTorrentEnabled: this.user.webTorrentEnabled,
+            p2pEnabled: this.user.p2pEnabled,
             autoPlayVideo: this.user.autoPlayVideo === true,
             autoPlayNextVideo: this.user.autoPlayNextVideo,
             videoLanguages: this.user.videoLanguages
@@ -65,7 +65,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
 
   updateDetails (onlyKeys?: string[]) {
     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 autoPlayNextVideo = this.form.value['autoPlayNextVideo']
 
@@ -80,7 +80,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
 
     let details: UserUpdateMe = {
       nsfwPolicy,
-      webTorrentEnabled,
+      p2pEnabled,
       autoPlayVideo,
       autoPlayNextVideo,
       videoLanguages
index d4cbda3a9164d5a029f3ce603705b22e5189444a..d9dacfba533ef1efdea82df24de2df7eece4327d 100644 (file)
@@ -10,14 +10,6 @@ function getStoredVolume () {
   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 () {
   const value = getLocalStorage('mute')
   if (value !== null && value !== undefined) return value === 'true'
@@ -123,7 +115,6 @@ function cleanupVideoWatch () {
 
 export {
   getStoredVolume,
-  getStoredP2PEnabled,
   getStoredMute,
   getStoredTheater,
   saveVolumeInStore,
index 3e6398f500746e24f46f7f5501bd1f08c2122f9d..c27024beba78e03c54eca09f7b7746925fc28c19 100644 (file)
@@ -31,7 +31,7 @@ import { copyToClipboard } from '../../root-helpers/utils'
 import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
 import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder'
 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 {
   NextPreviousVideoButtonOptions,
   P2PMediaLoaderPluginOptions,
@@ -86,6 +86,7 @@ export interface CommonOptions extends CustomizationOptions {
   onPlayerElementChange: (element: HTMLVideoElement) => void
 
   autoplay: boolean
+  p2pEnabled: boolean
 
   nextVideo?: () => void
   hasNextVideo?: () => boolean
@@ -374,7 +375,7 @@ export class PeertubePlayerManager {
         requiredSegmentsPriority: 1,
         simultaneousHttpDownloads: 1,
         segmentUrlBuilder: segmentUrlBuilderFactory(redundancyUrlManager, 1),
-        useP2P: getStoredP2PEnabled(),
+        useP2P: commonOptions.p2pEnabled,
         consumeOnly
       },
       segments: {
@@ -437,6 +438,7 @@ export class PeertubePlayerManager {
 
     const webtorrent = {
       autoplay,
+      playerRefusedP2P: commonOptions.p2pEnabled === false,
       videoDuration: commonOptions.videoDuration,
       playerElement: commonOptions.playerElement,
       videoFiles: webtorrentOptions.videoFiles.length !== 0
index ea39ac44d4f74ec37392f68dd9f6315384349f9d..824ea058b4cb53b3fdf9a20554db1618890facfe 100644 (file)
@@ -137,6 +137,8 @@ type WebtorrentPluginOptions = {
   videoFiles: VideoFile[]
 
   startTime: number | string
+
+  playerRefusedP2P: boolean
 }
 
 type P2PMediaLoaderPluginOptions = {
index 18380d9501033ee03baa243916f26e4e3890287d..7e25e3067d3ef9ba37357ff2daa59b0cd51d9187 100644 (file)
@@ -1,4 +1,4 @@
-import { VideoFile } from '@shared/models'
+import { HTMLServerConfig, Video, VideoFile } from '@shared/models'
 
 function toTitleCase (str: string) {
   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
 }
 
+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 () {
   if (/iPad|iPhone|iPod/.test(navigator.platform)) {
     return true
@@ -97,6 +104,7 @@ export {
   getRtcConfig,
   toTitleCase,
   isWebRTCDisabled,
+  isP2PEnabled,
 
   buildVideoOrPlaylistEmbed,
   videoFileMaxByResolution,
index 1a1cd7f1a62762c1faec271123361b511332f3cf..2b939020658212f3eb1022001a8a26d186918de3 100644 (file)
@@ -2,13 +2,7 @@ import videojs from 'video.js'
 import * as WebTorrent from 'webtorrent'
 import { timeToInt } from '@shared/core-utils'
 import { VideoFile } from '@shared/models'
-import {
-  getAverageBandwidthInStore,
-  getStoredMute,
-  getStoredP2PEnabled,
-  getStoredVolume,
-  saveAverageBandwidth
-} from '../peertube-player-local-storage'
+import { getAverageBandwidthInStore, getStoredMute, getStoredVolume, saveAverageBandwidth } from '../peertube-player-local-storage'
 import { PeerTubeResolution, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings'
 import { getRtcConfig, isIOS, videoFileMaxByResolution, videoFileMinByResolution } from '../utils'
 import { PeertubeChunkStore } from './peertube-chunk-store'
@@ -74,9 +68,10 @@ class WebTorrentPlugin extends Plugin {
 
     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.playerRefusedP2P = !getStoredP2PEnabled()
+
+    this.playerRefusedP2P = options.playerRefusedP2P
 
     this.videoFiles = options.videoFiles
     this.videoDuration = options.videoDuration
index 63d55d7ef069ef44b36fa06defd29dfaad83bfbb..aa3b442dde346189748f1ca01b55c4badd80124a 100644 (file)
@@ -1,6 +1,7 @@
 export * from './users'
 export * from './bytes'
 export * from './images'
+export * from './local-storage-utils'
 export * from './peertube-web-storage'
 export * from './utils'
 export * from './plugins-manager'
diff --git a/client/src/root-helpers/local-storage-utils.ts b/client/src/root-helpers/local-storage-utils.ts
new file mode 100644 (file)
index 0000000..c2b3f90
--- /dev/null
@@ -0,0 +1,10 @@
+function getBoolOrDefault (value: string, defaultValue: boolean) {
+  if (value === 'true') return true
+  if (value === 'false') return false
+
+  return defaultValue
+}
+
+export {
+  getBoolOrDefault
+}
index 8fbaca9e3fdc65a26b301d59df90b8115dcdb31c..2b11d0b7eddb2f82cd6ccf973f3aa54e43252910 100644 (file)
@@ -1,3 +1,2 @@
 export * from './user-local-storage-keys'
-export * from './user-local-storage-manager'
 export * from './user-tokens'
index 5f915899c6e3ef5befaf51ebb9d00ebbd71901e6..c3934ae3cfcf4ba1842a6ad4acbeb58de8226b6d 100644 (file)
@@ -1,15 +1,25 @@
 export const UserLocalStorageKeys = {
   ID: 'id',
+  USERNAME: 'username',
   ROLE: 'role',
   EMAIL: 'email',
+
   VIDEOS_HISTORY_ENABLED: 'videos-history-enabled',
-  USERNAME: 'username',
   NSFW_POLICY: 'nsfw_policy',
-  WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled',
+  P2P_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled',
+
   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',
+
   THEME: 'theme',
   LAST_ACTIVE_THEME: 'last_active_theme',
+
   VIDEO_LANGUAGES: 'video_languages'
 }
+
+export const UserTokenLocalStorageKeys = {
+  ACCESS_TOKEN: 'access_token',
+  REFRESH_TOKEN: 'refresh_token',
+  TOKEN_TYPE: 'token_type'
+}
diff --git a/client/src/root-helpers/users/user-local-storage-manager.ts b/client/src/root-helpers/users/user-local-storage-manager.ts
deleted file mode 100644 (file)
index c75cea1..0000000
+++ /dev/null
@@ -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
-}
index d42e1c8f321822bb1383b760ad217b6fd2031065..a6d614cb7b3419501cfbf0edeb8f8fdd47e22169 100644 (file)
@@ -1,46 +1,11 @@
-import { peertubeLocalStorage } from '../peertube-web-storage'
-
-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'
-  }
+import { UserTokenLocalStorageKeys } from './user-local-storage-keys'
 
+export class UserTokens {
   accessToken: string
   refreshToken: string
   tokenType: string
 
-  static load () {
-    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) {
+  constructor (hash?: Partial<UserTokens>) {
     if (hash) {
       this.accessToken = hash.accessToken
       this.refreshToken = hash.refreshToken
@@ -53,9 +18,29 @@ export class Tokens {
     }
   }
 
-  save () {
-    peertubeLocalStorage.setItem(Tokens.KEYS.ACCESS_TOKEN, this.accessToken)
-    peertubeLocalStorage.setItem(Tokens.KEYS.REFRESH_TOKEN, this.refreshToken)
-    peertubeLocalStorage.setItem(Tokens.KEYS.TOKEN_TYPE, this.tokenType)
+  static getUserTokens (localStorage: Pick<Storage, 'getItem'>) {
+    const accessTokenLocalStorage = localStorage.getItem(UserTokenLocalStorageKeys.ACCESS_TOKEN)
+    const refreshTokenLocalStorage = localStorage.getItem(UserTokenLocalStorageKeys.REFRESH_TOKEN)
+    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)
   }
 }
index 874be580dbd9a72704005dc4b67b329e737fd00b..94f1096b774e7d3549d85dbd4417c3788c93b89d 100644 (file)
@@ -7,6 +7,7 @@ import {
   OAuth2ErrorCode,
   ResultList,
   UserRefreshToken,
+  Video,
   VideoCaption,
   VideoDetails,
   VideoPlaylist,
@@ -16,9 +17,11 @@ import {
 import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager'
 import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
 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 { 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 { RegisterClientHelpers } from '../../types/register-client-option.model'
 import { PeerTubeEmbedApi } from './embed-api'
@@ -48,7 +51,7 @@ export class PeerTubeEmbed {
   mode: PlayerMode
   scope = 'peertube'
 
-  userTokens: Tokens
+  userTokens: UserTokens
   headers = new Headers()
   LOCAL_STORAGE_OAUTH_CLIENT_KEYS = {
     CLIENT_ID: 'client_id',
@@ -118,7 +121,7 @@ export class PeerTubeEmbed {
             return res.json()
           }).then((obj: UserRefreshToken & { code?: OAuth2ErrorCode }) => {
             if (!obj || obj.code === OAuth2ErrorCode.INVALID_GRANT) {
-              Tokens.flush()
+              UserTokens.flushLocalStorage(peertubeLocalStorage)
               this.removeTokensFromHeaders()
 
               return resolve()
@@ -126,7 +129,7 @@ export class PeerTubeEmbed {
 
             this.userTokens.accessToken = obj.access_token
             this.userTokens.refreshToken = obj.refresh_token
-            this.userTokens.save()
+            UserTokens.saveToLocalStorage(peertubeLocalStorage, this.userTokens)
 
             this.setHeadersFromTokens()
 
@@ -138,7 +141,7 @@ export class PeerTubeEmbed {
 
         return refreshingTokenPromise
           .catch(() => {
-            Tokens.flush()
+            UserTokens.flushLocalStorage(peertubeLocalStorage)
 
             this.removeTokensFromHeaders()
           }).then(() => fetch(url, {
@@ -258,7 +261,7 @@ export class PeerTubeEmbed {
   }
 
   async init () {
-    this.userTokens = Tokens.load()
+    this.userTokens = UserTokens.getUserTokens(peertubeLocalStorage)
     await this.initCore()
   }
 
@@ -515,6 +518,8 @@ export class PeerTubeEmbed {
         muted: this.muted,
         loop: this.loop,
 
+        p2pEnabled: this.isP2PEnabled(videoInfo),
+
         captions: videoCaptions.length !== 0,
         subtitle: this.subtitle,
 
@@ -669,7 +674,7 @@ export class PeerTubeEmbed {
 
     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>'
       : undefined
 
@@ -784,6 +789,15 @@ export class PeerTubeEmbed {
       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()
index fbe0dbbfbfd739ae68981a8490d3d8a2a2341fe6..421c195690438e2f0ea3ed438d7f8f92633e00f7 100644 (file)
@@ -92,6 +92,11 @@ defaults:
     # No licence by default
     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
 storage:
   tmp: 'storage/tmp/' # Use to download data (imports etc), store uploaded files before and during processing...
@@ -216,7 +221,7 @@ security:
     enabled: true
 
 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
   # Only handle requests on your videos
   # If you set this to false it means you have a public tracker
index 6363a51794cfccb7ad333969f25f4989e2ed0219..13219fd5d8ce6f1bf8a7d6234162c1c1c54f99f6 100644 (file)
@@ -90,6 +90,11 @@ defaults:
     # No licence by default
     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
 storage:
   tmp: '/var/www/peertube/storage/tmp/' # Use to download data (imports etc), store uploaded files before and during processing...
index 11d3525e4af85e05cf85e027e2ace1a6fd14ea17..f3b4508d932ba66ef4fa6ea51bc0fbe441d9e71f 100644 (file)
@@ -183,6 +183,7 @@ async function createUser (req: express.Request, res: express.Response) {
     password: body.password,
     email: body.email,
     nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
+    p2pEnabled: CONFIG.DEFAULTS.P2P.ENABLED,
     autoPlayVideo: true,
     role: body.role,
     videoQuota: body.videoQuota,
@@ -232,6 +233,7 @@ async function registerUser (req: express.Request, res: express.Response) {
     password: body.password,
     email: body.email,
     nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
+    p2pEnabled: CONFIG.DEFAULTS.P2P.ENABLED,
     autoPlayVideo: true,
     role: UserRole.USER,
     videoQuota: CONFIG.USER.VIDEO_QUOTA,
index 6bacdbbb63693702ee38423e4409fea48e90f8c7..1125771d44354315fdc50df131e0146f43250781 100644 (file)
@@ -197,7 +197,7 @@ async function updateMe (req: express.Request, res: express.Response) {
   const keysToUpdate: (keyof UserUpdateMe & keyof AttributesOnly<UserModel>)[] = [
     'password',
     'nsfwPolicy',
-    'webTorrentEnabled',
+    'p2pEnabled',
     'autoPlayVideo',
     'autoPlayNextVideo',
     '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.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 (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
       user.pendingEmail = body.email
index f52c60b60339c40e4d96aa2883aa055dd609da78..badf171d24af87647daf3c102408ec7633ef6e8a 100644 (file)
@@ -49,7 +49,7 @@ function isUserNSFWPolicyValid (value: any) {
   return exists(value) && nsfwPolicies.includes(value)
 }
 
-function isUserWebTorrentEnabledValid (value: any) {
+function isUserP2PEnabledValid (value: any) {
   return isBooleanValid(value)
 }
 
@@ -109,7 +109,7 @@ export {
   isUserAdminFlagsValid,
   isUserEmailVerifiedValid,
   isUserNSFWPolicyValid,
-  isUserWebTorrentEnabledValid,
+  isUserP2PEnabledValid,
   isUserAutoPlayVideoValid,
   isUserAutoPlayNextVideoValid,
   isUserAutoPlayNextVideoPlaylistValid,
index e3e8c426e001b4e343cec4f45f851966726d813a..a6ea6d888230d2d29903b79e00ba904453ff23c4 100644 (file)
@@ -78,6 +78,9 @@ const CONFIG = {
       COMMENTS_ENABLED: config.get<boolean>('defaults.publish.comments_enabled'),
       PRIVACY: config.get<VideoPrivacy>('defaults.publish.privacy'),
       LICENCE: config.get<number>('defaults.publish.licence')
+    },
+    P2P: {
+      ENABLED: config.get<boolean>('defaults.p2p.enabled')
     }
   },
 
index 026c715c2d4e2daf887a645ce178e42ae0c00061..258ccdb51e9fe711d507b4eeb07734041a97687e 100644 (file)
@@ -25,7 +25,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
 
 // ---------------------------------------------------------------------------
 
-const LAST_MIGRATION_VERSION = 670
+const LAST_MIGRATION_VERSION = 675
 
 // ---------------------------------------------------------------------------
 
index 75daeb5d89e55792c5a2e236caef15e64737db0d..19adaf17764222d679dd7f3b5140a99f7b77e4ce 100644 (file)
@@ -144,6 +144,7 @@ async function createOAuthAdminIfNotExist () {
     role,
     verified: true,
     nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
+    p2pEnabled: CONFIG.DEFAULTS.P2P.ENABLED,
     videoQuota: -1,
     videoQuotaDaily: -1
   }
diff --git a/server/initializers/migrations/0675-p2p-enabled.ts b/server/initializers/migrations/0675-p2p-enabled.ts
new file mode 100644 (file)
index 0000000..b4f5338
--- /dev/null
@@ -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
+}
index f2ef0a78af67265dc2e2ce187d2d57e9494eccd7..754bee36ddb0a117db8e3a6fe67032c257c688a3 100644 (file)
@@ -226,6 +226,7 @@ async function createUserFromExternal (pluginAuth: string, options: {
     password: null,
     email: options.email,
     nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
+    p2pEnabled: CONFIG.DEFAULTS.P2P.ENABLED,
     autoPlayVideo: true,
     role: options.role,
     videoQuota: CONFIG.USER.VIDEO_QUOTA,
index 8aea4cd6d204275cd6490969b22b304cec99c0de..d759f85e17c3ee1187eb56dc1e41bb3620224fb9 100644 (file)
@@ -61,6 +61,9 @@ class ServerConfigManager {
           commentsEnabled: CONFIG.DEFAULTS.PUBLISH.COMMENTS_ENABLED,
           privacy: CONFIG.DEFAULTS.PUBLISH.PRIVACY,
           licence: CONFIG.DEFAULTS.PUBLISH.LICENCE
+        },
+        p2p: {
+          enabled: CONFIG.DEFAULTS.P2P.ENABLED
         }
       },
 
index 7a6b2ce57f86122bc42b25e460a7c0bac44e3ef3..bc6007c6d45b6b4233489989398d877ecca7a364 100644 (file)
@@ -15,6 +15,7 @@ import {
   isUserDisplayNameValid,
   isUserNoModal,
   isUserNSFWPolicyValid,
+  isUserP2PEnabledValid,
   isUserPasswordValid,
   isUserPasswordValidOrEmpty,
   isUserRoleValid,
@@ -239,6 +240,9 @@ const usersUpdateMeValidator = [
   body('autoPlayVideo')
     .optional()
     .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')
     .optional()
     .custom(isUserVideoLanguages).withMessage('Should have a valid video languages attribute'),
index b56f37e558eea81a3bfa19e0950266d246d8d149..88c3ff52835241ce29829d57d67a4e7a4bd18f93 100644 (file)
@@ -55,7 +55,7 @@ import {
   isUserVideoQuotaDailyValid,
   isUserVideoQuotaValid,
   isUserVideosHistoryEnabledValid,
-  isUserWebTorrentEnabledValid
+  isUserP2PEnabledValid
 } from '../../helpers/custom-validators/users'
 import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
 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
 
   @AllowNull(false)
-  @Default(true)
-  @Is('UserWebTorrentEnabled', value => throwIfNotValid(value, isUserWebTorrentEnabledValid, 'WebTorrent enabled'))
+  @Is('p2pEnabled', value => throwIfNotValid(value, isUserP2PEnabledValid, 'P2P enabled'))
   @Column
-  webTorrentEnabled: boolean
+  p2pEnabled: boolean
 
   @AllowNull(false)
   @Default(true)
@@ -892,7 +891,11 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
       emailVerified: this.emailVerified,
 
       nsfwPolicy: this.nsfwPolicy,
-      webTorrentEnabled: this.webTorrentEnabled,
+
+      // FIXME: deprecated in 4.1
+      webTorrentEnabled: this.p2pEnabled,
+      p2pEnabled: this.p2pEnabled,
+
       videosHistoryEnabled: this.videosHistoryEnabled,
       autoPlayVideo: this.autoPlayVideo,
       autoPlayNextVideo: this.autoPlayNextVideo,
index 3dff7bfb7aa9fc787dcff8fa77f68a79366dc884..340d4b44bb7c79584a231a427c14cc2d6e369d86 100644 (file)
@@ -21,18 +21,7 @@ describe('Test config defaults', function () {
   before(async function () {
     this.timeout(30000)
 
-    const overrideConfig = {
-      defaults: {
-        publish: {
-          comments_enabled: false,
-          download_enabled: false,
-          privacy: VideoPrivacy.INTERNAL,
-          licence: 4
-        }
-      }
-    }
-
-    server = await createSingleServer(1, overrideConfig)
+    server = await createSingleServer(1)
     await setAccessTokensToServers([ server ])
     await setDefaultVideoChannel([ server ])
 
@@ -40,6 +29,23 @@ describe('Test config defaults', 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 = {
       name: 'video',
       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 () {
     await cleanupTests([ server ])
   })
index 748f4cd35689ffb8dc7c81ed204ead0aa3c411fc..c132d99eab382cc3d06ddea1fe4ec7ff5ed48263 100644 (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 () {
-      this.timeout(60000)
+      this.timeout(120000)
 
       await servers[1].videos.upload({ attributes: { name: 'server2' } })
       await servers[2].videos.upload({ attributes: { name: 'server3' } })
index 6c41e7d560146e5e5a589ce11c24e3addfc0f310..f00cbab5a71053760268ecbaf7a4b07a679c23fb 100644 (file)
@@ -559,6 +559,28 @@ describe('Test users', function () {
       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 () {
       await server.users.updateMe({
         token: userToken,
index 9c3dcd6d3c123db468cae09e8fedc70ae16e4f8f..71540e603416b902a42e8a2e8b53b4e8c9b5b223 100644 (file)
@@ -55,6 +55,10 @@ export interface ServerConfig {
       privacy: VideoPrivacy
       licence: number
     }
+
+    p2p: {
+      enabled: boolean
+    }
   }
 
   webadmin: {
index 6d7df38fbbf679df2b4005233326731839527372..e664e44b5e3db2b1a0f790f90958c82b75112134 100644 (file)
@@ -5,7 +5,10 @@ export interface UserUpdateMe {
   description?: string
   nsfwPolicy?: NSFWPolicyType
 
+  // FIXME: deprecated in favour of p2pEnabled in 4.1
   webTorrentEnabled?: boolean
+  p2pEnabled?: boolean
+
   autoPlayVideo?: boolean
   autoPlayNextVideo?: boolean
   autoPlayNextVideoPlaylist?: boolean
index 78870c55646f7e0890fee07ee118d1f57f584521..63c5c8a92f961e36da3e9b4e40e064e27cb713e4 100644 (file)
@@ -20,7 +20,11 @@ export interface User {
   autoPlayVideo: boolean
   autoPlayNextVideo: boolean
   autoPlayNextVideoPlaylist: boolean
+
+  // @deprecated in favour of p2pEnabled
   webTorrentEnabled: boolean
+  p2pEnabled: boolean
+
   videosHistoryEnabled: boolean
   videoLanguages: string[]
 
index 1d5581072adab2e4c0fec5ba638286f757f5cc65..7b6e8a1e4f22fd0f63ce39dcb7208735821bb2a6 100644 (file)
@@ -6679,7 +6679,7 @@ components:
           type: integer
           description: The user daily video quota in bytes
           example: -1
-        webtorrentEnabled:
+        p2pEnabled:
           type: boolean
           description: Enable P2P in the player
     UserWithStats:
@@ -6780,7 +6780,7 @@ components:
             - 'true'
             - 'false'
             - both
-        webTorrentEnabled:
+        p2pEnabled:
           type: boolean
           description: whether to enable P2P in the player or not
         autoPlayVideo: