aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-12-15 15:58:10 +0100
committerChocobozzz <me@florianbigard.com>2021-12-16 10:08:55 +0100
commita9bfa85d2cdf13670aaced740da5b493fbeddfce (patch)
tree3781c9218d4cc7786b6589365c0efbed2151703d
parentc77fdc605b3ccc1ab6890f889d8200fbe9372949 (diff)
downloadPeerTube-a9bfa85d2cdf13670aaced740da5b493fbeddfce.tar.gz
PeerTube-a9bfa85d2cdf13670aaced740da5b493fbeddfce.tar.zst
PeerTube-a9bfa85d2cdf13670aaced740da5b493fbeddfce.zip
Add ability for admins to set default p2p policy
-rw-r--r--client/e2e/src/po/anonymous-settings.po.ts21
-rw-r--r--client/e2e/src/po/my-account.po.ts (renamed from client/e2e/src/po/my-account.ts)17
-rw-r--r--client/e2e/src/po/video-upload.po.ts8
-rw-r--r--client/e2e/src/po/video-watch.po.ts13
-rw-r--r--client/e2e/src/suites-all/videos.e2e-spec.ts2
-rw-r--r--client/e2e/src/suites-local/custom-server-defaults.e2e-spec.ts75
-rw-r--r--client/e2e/src/suites-local/user-settings.e2e-spec.ts82
-rw-r--r--client/e2e/src/suites-local/videos-list.e2e-spec.ts2
-rw-r--r--client/e2e/src/utils/elements.ts21
-rw-r--r--client/e2e/src/utils/hooks.ts3
-rw-r--r--client/src/app/+videos/+video-watch/shared/information/privacy-concerns.component.ts26
-rw-r--r--client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts39
-rw-r--r--client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.ts27
-rw-r--r--client/src/app/+videos/+video-watch/video-watch.component.ts64
-rw-r--r--client/src/app/app.component.ts17
-rw-r--r--client/src/app/core/auth/auth-user.model.ts44
-rw-r--r--client/src/app/core/auth/auth.service.ts16
-rw-r--r--client/src/app/core/core.module.ts3
-rw-r--r--client/src/app/core/users/index.ts1
-rw-r--r--client/src/app/core/users/user-local-storage.service.ts186
-rw-r--r--client/src/app/core/users/user.model.ts8
-rw-r--r--client/src/app/core/users/user.service.ts104
-rw-r--r--client/src/app/menu/menu.component.html2
-rw-r--r--client/src/app/menu/menu.component.ts4
-rw-r--r--client/src/app/shared/shared-search/search.service.ts7
-rw-r--r--client/src/app/shared/shared-user-settings/user-video-settings.component.html2
-rw-r--r--client/src/app/shared/shared-user-settings/user-video-settings.component.ts8
-rw-r--r--client/src/assets/player/peertube-player-local-storage.ts9
-rw-r--r--client/src/assets/player/peertube-player-manager.ts6
-rw-r--r--client/src/assets/player/peertube-videojs-typings.ts2
-rw-r--r--client/src/assets/player/utils.ts10
-rw-r--r--client/src/assets/player/webtorrent/webtorrent-plugin.ts13
-rw-r--r--client/src/root-helpers/index.ts1
-rw-r--r--client/src/root-helpers/local-storage-utils.ts10
-rw-r--r--client/src/root-helpers/users/index.ts1
-rw-r--r--client/src/root-helpers/users/user-local-storage-keys.ts16
-rw-r--r--client/src/root-helpers/users/user-local-storage-manager.ts55
-rw-r--r--client/src/root-helpers/users/user-tokens.ts69
-rw-r--r--client/src/standalone/videos/embed.ts28
-rw-r--r--config/default.yaml7
-rw-r--r--config/production.yaml.example5
-rw-r--r--server/controllers/api/users/index.ts2
-rw-r--r--server/controllers/api/users/me.ts8
-rw-r--r--server/helpers/custom-validators/users.ts4
-rw-r--r--server/initializers/config.ts3
-rw-r--r--server/initializers/constants.ts2
-rw-r--r--server/initializers/installer.ts1
-rw-r--r--server/initializers/migrations/0675-p2p-enabled.ts21
-rw-r--r--server/lib/auth/oauth-model.ts1
-rw-r--r--server/lib/server-config-manager.ts3
-rw-r--r--server/middlewares/validators/users.ts4
-rw-r--r--server/models/user/user.ts13
-rw-r--r--server/tests/api/server/config-defaults.ts69
-rw-r--r--server/tests/api/server/follows.ts2
-rw-r--r--server/tests/api/users/users.ts22
-rw-r--r--shared/models/server/server-config.model.ts4
-rw-r--r--shared/models/users/user-update-me.model.ts3
-rw-r--r--shared/models/users/user.model.ts4
-rw-r--r--support/doc/api/openapi.yaml4
59 files changed, 789 insertions, 415 deletions
diff --git a/client/e2e/src/po/anonymous-settings.po.ts b/client/e2e/src/po/anonymous-settings.po.ts
new file mode 100644
index 000000000..180d371fa
--- /dev/null
+++ b/client/e2e/src/po/anonymous-settings.po.ts
@@ -0,0 +1,21 @@
1import { getCheckbox } from '../utils'
2
3export class AnonymousSettingsPage {
4
5 async openSettings () {
6 const link = await $$('.menu-link').filter(async i => {
7 return await i.getText() === 'My settings'
8 }).then(links => links[0])
9
10 await link.click()
11
12 await $('my-user-video-settings').waitForDisplayed()
13 }
14
15 async clickOnP2PCheckbox () {
16 const p2p = getCheckbox('p2pEnabled')
17 await p2p.waitForClickable()
18
19 await p2p.click()
20 }
21}
diff --git a/client/e2e/src/po/my-account.ts b/client/e2e/src/po/my-account.po.ts
index b51614fd9..13a764e87 100644
--- a/client/e2e/src/po/my-account.ts
+++ b/client/e2e/src/po/my-account.po.ts
@@ -1,4 +1,4 @@
1import { go } from '../utils' 1import { getCheckbox, go } from '../utils'
2 2
3export class MyAccountPage { 3export class MyAccountPage {
4 4
@@ -27,6 +27,21 @@ export class MyAccountPage {
27 await nsfw.scrollIntoView(false) // Avoid issues with fixed header on firefox 27 await nsfw.scrollIntoView(false) // Avoid issues with fixed header on firefox
28 await nsfw.selectByAttribute('value', newValue) 28 await nsfw.selectByAttribute('value', newValue)
29 29
30 await this.submitVideoSettings()
31 }
32
33 async clickOnP2PCheckbox () {
34 const p2p = getCheckbox('p2pEnabled')
35
36 await p2p.waitForClickable()
37 await p2p.scrollIntoView(false) // Avoid issues with fixed header on firefox
38
39 await p2p.click()
40
41 await this.submitVideoSettings()
42 }
43
44 private async submitVideoSettings () {
30 const submit = $('my-user-video-settings input[type=submit]') 45 const submit = $('my-user-video-settings input[type=submit]')
31 await submit.scrollIntoView(false) 46 await submit.scrollIntoView(false)
32 await submit.click() 47 await submit.click()
diff --git a/client/e2e/src/po/video-upload.po.ts b/client/e2e/src/po/video-upload.po.ts
index dd437c390..2206b56c3 100644
--- a/client/e2e/src/po/video-upload.po.ts
+++ b/client/e2e/src/po/video-upload.po.ts
@@ -1,5 +1,5 @@
1import { join } from 'path' 1import { join } from 'path'
2import { clickOnCheckbox } from '../utils' 2import { getCheckbox, selectCustomSelect } from '../utils'
3 3
4export class VideoUploadPage { 4export class VideoUploadPage {
5 async navigateTo () { 5 async navigateTo () {
@@ -32,7 +32,7 @@ export class VideoUploadPage {
32 } 32 }
33 33
34 setAsNSFW () { 34 setAsNSFW () {
35 return clickOnCheckbox('nsfw') 35 return getCheckbox('nsfw').click()
36 } 36 }
37 37
38 async validSecondUploadStep (videoName: string) { 38 async validSecondUploadStep (videoName: string) {
@@ -47,6 +47,10 @@ export class VideoUploadPage {
47 }) 47 })
48 } 48 }
49 49
50 setAsPublic () {
51 return selectCustomSelect('privacy', 'Public')
52 }
53
50 private getSecondStepSubmitButton () { 54 private getSecondStepSubmitButton () {
51 return $('.submit-container my-button') 55 return $('.submit-container my-button')
52 } 56 }
diff --git a/client/e2e/src/po/video-watch.po.ts b/client/e2e/src/po/video-watch.po.ts
index 1406c971a..cecda3a8b 100644
--- a/client/e2e/src/po/video-watch.po.ts
+++ b/client/e2e/src/po/video-watch.po.ts
@@ -39,12 +39,23 @@ export class VideoWatchPage {
39 return $('my-video-comment-add').isExisting() 39 return $('my-video-comment-add').isExisting()
40 } 40 }
41 41
42 isPrivacyWarningDisplayed () {
43 return $('my-privacy-concerns').isDisplayed()
44 }
45
42 async goOnAssociatedEmbed () { 46 async goOnAssociatedEmbed () {
43 let url = await browser.getUrl() 47 let url = await browser.getUrl()
44 url = url.replace('/w/', '/videos/embed/') 48 url = url.replace('/w/', '/videos/embed/')
45 url = url.replace(':3333', ':9001') 49 url = url.replace(':3333', ':9001')
46 50
47 return go(url) 51 await go(url)
52 await $('.vjs-big-play-button').waitForDisplayed()
53 }
54
55 async isEmbedWarningDisplayed () {
56 const text = await $('.vjs-dock-description').getText()
57
58 return !!text.trim()
48 } 59 }
49 60
50 goOnP2PMediaLoaderEmbed () { 61 goOnP2PMediaLoaderEmbed () {
diff --git a/client/e2e/src/suites-all/videos.e2e-spec.ts b/client/e2e/src/suites-all/videos.e2e-spec.ts
index 3b8305a25..b3a87c8e2 100644
--- a/client/e2e/src/suites-all/videos.e2e-spec.ts
+++ b/client/e2e/src/suites-all/videos.e2e-spec.ts
@@ -1,5 +1,5 @@
1import { LoginPage } from '../po/login.po' 1import { LoginPage } from '../po/login.po'
2import { MyAccountPage } from '../po/my-account' 2import { MyAccountPage } from '../po/my-account.po'
3import { PlayerPage } from '../po/player.po' 3import { PlayerPage } from '../po/player.po'
4import { VideoListPage } from '../po/video-list.po' 4import { VideoListPage } from '../po/video-list.po'
5import { VideoUpdatePage } from '../po/video-update.po' 5import { VideoUpdatePage } from '../po/video-update.po'
diff --git a/client/e2e/src/suites-local/custom-server-defaults.e2e-spec.ts b/client/e2e/src/suites-local/custom-server-defaults.e2e-spec.ts
index c2c8edcc9..e060d382f 100644
--- a/client/e2e/src/suites-local/custom-server-defaults.e2e-spec.ts
+++ b/client/e2e/src/suites-local/custom-server-defaults.e2e-spec.ts
@@ -1,7 +1,7 @@
1import { LoginPage } from '../po/login.po' 1import { LoginPage } from '../po/login.po'
2import { VideoUploadPage } from '../po/video-upload.po' 2import { VideoUploadPage } from '../po/video-upload.po'
3import { VideoWatchPage } from '../po/video-watch.po' 3import { VideoWatchPage } from '../po/video-watch.po'
4import { isMobileDevice, isSafari, waitServerUp } from '../utils' 4import { go, isMobileDevice, isSafari, waitServerUp } from '../utils'
5 5
6describe('Custom server defaults', () => { 6describe('Custom server defaults', () => {
7 let videoUploadPage: VideoUploadPage 7 let videoUploadPage: VideoUploadPage
@@ -10,9 +10,7 @@ describe('Custom server defaults', () => {
10 10
11 before(async () => { 11 before(async () => {
12 await waitServerUp() 12 await waitServerUp()
13 })
14 13
15 beforeEach(async () => {
16 loginPage = new LoginPage() 14 loginPage = new LoginPage()
17 videoUploadPage = new VideoUploadPage() 15 videoUploadPage = new VideoUploadPage()
18 videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari()) 16 videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
@@ -20,18 +18,69 @@ describe('Custom server defaults', () => {
20 await browser.maximizeWindow() 18 await browser.maximizeWindow()
21 }) 19 })
22 20
23 it('Should upload a video with custom default values', async function () { 21 describe('Publish default values', function () {
24 await loginPage.loginAsRootUser() 22 before(async function () {
25 await videoUploadPage.navigateTo() 23 await loginPage.loginAsRootUser()
26 await videoUploadPage.uploadVideo() 24 })
27 await videoUploadPage.validSecondUploadStep('video') 25
26 it('Should upload a video with custom default values', async function () {
27 await videoUploadPage.navigateTo()
28 await videoUploadPage.uploadVideo()
29 await videoUploadPage.validSecondUploadStep('video')
28 30
29 await videoWatchPage.waitWatchVideoName('video') 31 await videoWatchPage.waitWatchVideoName('video')
30 32
31 expect(await videoWatchPage.getPrivacy()).toBe('Internal') 33 expect(await videoWatchPage.getPrivacy()).toBe('Internal')
32 expect(await videoWatchPage.getLicence()).toBe('Attribution - Non Commercial') 34 expect(await videoWatchPage.getLicence()).toBe('Attribution - Non Commercial')
33 expect(await videoWatchPage.isDownloadEnabled()).toBeFalsy() 35 expect(await videoWatchPage.isDownloadEnabled()).toBeFalsy()
34 expect(await videoWatchPage.areCommentsEnabled()).toBeFalsy() 36 expect(await videoWatchPage.areCommentsEnabled()).toBeFalsy()
37 })
38
39 after(async function () {
40 await loginPage.logout()
41 })
35 }) 42 })
36 43
44 describe('P2P', function () {
45 let videoUrl: string
46
47 async function goOnVideoWatchPage () {
48 await go(videoUrl)
49 await videoWatchPage.waitWatchVideoName('video')
50 }
51
52 async function checkP2P (enabled: boolean) {
53 await goOnVideoWatchPage()
54 expect(await videoWatchPage.isPrivacyWarningDisplayed()).toEqual(enabled)
55
56 await videoWatchPage.goOnAssociatedEmbed()
57 expect(await videoWatchPage.isEmbedWarningDisplayed()).toEqual(enabled)
58 }
59
60 before(async () => {
61 await loginPage.loginAsRootUser()
62 await videoUploadPage.navigateTo()
63 await videoUploadPage.uploadVideo()
64 await videoUploadPage.setAsPublic()
65 await videoUploadPage.validSecondUploadStep('video')
66
67 await videoWatchPage.waitWatchVideoName('video')
68
69 videoUrl = await browser.getUrl()
70 })
71
72 beforeEach(async function () {
73 await goOnVideoWatchPage()
74 })
75
76 it('Should have P2P disabled for a logged in user', async function () {
77 await checkP2P(false)
78 })
79
80 it('Should have P2P disabled for anonymous users', async function () {
81 await loginPage.logout()
82
83 await checkP2P(false)
84 })
85 })
37}) 86})
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
index 000000000..b87501cd1
--- /dev/null
+++ b/client/e2e/src/suites-local/user-settings.e2e-spec.ts
@@ -0,0 +1,82 @@
1import { AnonymousSettingsPage } from '../po/anonymous-settings.po'
2import { LoginPage } from '../po/login.po'
3import { MyAccountPage } from '../po/my-account.po'
4import { VideoUploadPage } from '../po/video-upload.po'
5import { VideoWatchPage } from '../po/video-watch.po'
6import { go, isMobileDevice, isSafari, waitServerUp } from '../utils'
7
8describe('User settings', () => {
9 let videoUploadPage: VideoUploadPage
10 let loginPage: LoginPage
11 let videoWatchPage: VideoWatchPage
12 let myAccountPage: MyAccountPage
13 let anonymousSettingsPage: AnonymousSettingsPage
14
15 before(async () => {
16 await waitServerUp()
17
18 loginPage = new LoginPage()
19 videoUploadPage = new VideoUploadPage()
20 videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
21 myAccountPage = new MyAccountPage()
22 anonymousSettingsPage = new AnonymousSettingsPage()
23
24 await browser.maximizeWindow()
25 })
26
27 describe('P2P', function () {
28 let videoUrl: string
29
30 async function goOnVideoWatchPage () {
31 await go(videoUrl)
32 await videoWatchPage.waitWatchVideoName('video')
33 }
34
35 async function checkP2P (enabled: boolean) {
36 await goOnVideoWatchPage()
37 expect(await videoWatchPage.isPrivacyWarningDisplayed()).toEqual(enabled)
38
39 await videoWatchPage.goOnAssociatedEmbed()
40 expect(await videoWatchPage.isEmbedWarningDisplayed()).toEqual(enabled)
41 }
42
43 before(async () => {
44 await loginPage.loginAsRootUser()
45 await videoUploadPage.navigateTo()
46 await videoUploadPage.uploadVideo()
47 await videoUploadPage.validSecondUploadStep('video')
48
49 await videoWatchPage.waitWatchVideoName('video')
50
51 videoUrl = await browser.getUrl()
52 })
53
54 beforeEach(async function () {
55 await goOnVideoWatchPage()
56 })
57
58 it('Should have P2P enabled for a logged in user', async function () {
59 await checkP2P(true)
60 })
61
62 it('Should disable P2P for a logged in user', async function () {
63 await myAccountPage.navigateToMySettings()
64 await myAccountPage.clickOnP2PCheckbox()
65
66 await checkP2P(false)
67 })
68
69 it('Should have P2P enabled for anonymous users', async function () {
70 await loginPage.logout()
71
72 await checkP2P(true)
73 })
74
75 it('Should disable P2P for an anonymous user', async function () {
76 await anonymousSettingsPage.openSettings()
77 await anonymousSettingsPage.clickOnP2PCheckbox()
78
79 await checkP2P(false)
80 })
81 })
82})
diff --git a/client/e2e/src/suites-local/videos-list.e2e-spec.ts b/client/e2e/src/suites-local/videos-list.e2e-spec.ts
index bca6018b9..ce57261b9 100644
--- a/client/e2e/src/suites-local/videos-list.e2e-spec.ts
+++ b/client/e2e/src/suites-local/videos-list.e2e-spec.ts
@@ -1,6 +1,6 @@
1import { AdminConfigPage } from '../po/admin-config.po' 1import { AdminConfigPage } from '../po/admin-config.po'
2import { LoginPage } from '../po/login.po' 2import { LoginPage } from '../po/login.po'
3import { MyAccountPage } from '../po/my-account' 3import { MyAccountPage } from '../po/my-account.po'
4import { VideoListPage } from '../po/video-list.po' 4import { VideoListPage } from '../po/video-list.po'
5import { VideoSearchPage } from '../po/video-search.po' 5import { VideoSearchPage } from '../po/video-search.po'
6import { VideoUploadPage } from '../po/video-upload.po' 6import { VideoUploadPage } from '../po/video-upload.po'
diff --git a/client/e2e/src/utils/elements.ts b/client/e2e/src/utils/elements.ts
index cadc46cce..315718879 100644
--- a/client/e2e/src/utils/elements.ts
+++ b/client/e2e/src/utils/elements.ts
@@ -1,7 +1,22 @@
1function clickOnCheckbox (name: string) { 1function getCheckbox (name: string) {
2 return $(`my-peertube-checkbox[inputname=${name}] label`).click() 2 return $(`my-peertube-checkbox[inputname=${name}] label`)
3}
4
5async function selectCustomSelect (id: string, valueLabel: string) {
6 await $(`[formcontrolname=${id}] .ng-arrow-wrapper`).click()
7
8 const option = await $$(`[formcontrolname=${id}] .ng-option`).filter(async o => {
9 const text = await o.getText()
10
11 return text.trimStart().startsWith(valueLabel)
12 }).then(options => options[0])
13
14 await option.waitForDisplayed()
15
16 return option.click()
3} 17}
4 18
5export { 19export {
6 clickOnCheckbox 20 getCheckbox,
21 selectCustomSelect
7} 22}
diff --git a/client/e2e/src/utils/hooks.ts b/client/e2e/src/utils/hooks.ts
index e42c6a5d8..2f3d10fe3 100644
--- a/client/e2e/src/utils/hooks.ts
+++ b/client/e2e/src/utils/hooks.ts
@@ -55,6 +55,9 @@ function buildConfig (suiteFile: string = undefined) {
55 comments_enabled: false, 55 comments_enabled: false,
56 privacy: 4, 56 privacy: 4,
57 licence: 4 57 licence: 4
58 },
59 p2p: {
60 enabled: false
58 } 61 }
59 } 62 }
60 } 63 }
diff --git a/client/src/app/+videos/+video-watch/shared/information/privacy-concerns.component.ts b/client/src/app/+videos/+video-watch/shared/information/privacy-concerns.component.ts
index bbc81d46d..24030df3e 100644
--- a/client/src/app/+videos/+video-watch/shared/information/privacy-concerns.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/information/privacy-concerns.component.ts
@@ -1,9 +1,8 @@
1import { Component, Input, OnInit } from '@angular/core' 1import { Component, Input, OnInit } from '@angular/core'
2import { ServerService } from '@app/core' 2import { ServerService, User, UserService } from '@app/core'
3import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' 3import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
4import { HTMLServerConfig, Video } from '@shared/models' 4import { HTMLServerConfig, Video } from '@shared/models'
5import { getStoredP2PEnabled } from '../../../../../assets/player/peertube-player-local-storage' 5import { isP2PEnabled } from '../../../../../assets/player/utils'
6import { isWebRTCDisabled } from '../../../../../assets/player/utils'
7 6
8@Component({ 7@Component({
9 selector: 'my-privacy-concerns', 8 selector: 'my-privacy-concerns',
@@ -15,33 +14,32 @@ export class PrivacyConcernsComponent implements OnInit {
15 14
16 @Input() video: Video 15 @Input() video: Video
17 16
18 display = true 17 display = false
19 18
20 private serverConfig: HTMLServerConfig 19 private serverConfig: HTMLServerConfig
21 20
22 constructor ( 21 constructor (
23 private serverService: ServerService 22 private serverService: ServerService,
23 private userService: UserService
24 ) { } 24 ) { }
25 25
26 ngOnInit () { 26 ngOnInit () {
27 this.serverConfig = this.serverService.getHTMLConfig() 27 this.serverConfig = this.serverService.getHTMLConfig()
28 28
29 if (isWebRTCDisabled() || this.isTrackerDisabled() || this.isP2PDisabled() || this.alreadyAccepted()) { 29 this.userService.getAnonymousOrLoggedUser()
30 this.display = false 30 .subscribe(user => this.updateDisplay(user))
31 }
32 } 31 }
33 32
34 acceptedPrivacyConcern () { 33 acceptedPrivacyConcern () {
35 peertubeLocalStorage.setItem(PrivacyConcernsComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'true') 34 peertubeLocalStorage.setItem(PrivacyConcernsComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'true')
36 this.display = false
37 }
38 35
39 private isTrackerDisabled () { 36 this.display = false
40 return this.video.isLocal && this.serverConfig.tracker.enabled === false
41 } 37 }
42 38
43 private isP2PDisabled () { 39 private updateDisplay (user: User) {
44 return getStoredP2PEnabled() === false 40 if (isP2PEnabled(this.video, this.serverConfig, user.p2pEnabled) && !this.alreadyAccepted()) {
41 this.display = true
42 }
45 } 43 }
46 44
47 private alreadyAccepted () { 45 private alreadyAccepted () {
diff --git a/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts b/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts
index b2863fed6..fbf9a3687 100644
--- a/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts
@@ -1,16 +1,9 @@
1import { Component, EventEmitter, Input, Output } from '@angular/core' 1import { Component, EventEmitter, Input, Output } from '@angular/core'
2import { Router } from '@angular/router' 2import { Router } from '@angular/router'
3import { 3import { AuthService, ComponentPagination, HooksService, Notifier, SessionStorageService, UserService } from '@app/core'
4 AuthService,
5 ComponentPagination,
6 HooksService,
7 LocalStorageService,
8 Notifier,
9 SessionStorageService,
10 UserService
11} from '@app/core'
12import { VideoPlaylist, VideoPlaylistElement, VideoPlaylistService } from '@app/shared/shared-video-playlist' 4import { VideoPlaylist, VideoPlaylistElement, VideoPlaylistService } from '@app/shared/shared-video-playlist'
13import { peertubeLocalStorage, peertubeSessionStorage } from '@root-helpers/peertube-web-storage' 5import { peertubeSessionStorage } from '@root-helpers/peertube-web-storage'
6import { getBoolOrDefault } from '@root-helpers/local-storage-utils'
14import { VideoPlaylistPrivacy } from '@shared/models' 7import { VideoPlaylistPrivacy } from '@shared/models'
15 8
16@Component({ 9@Component({
@@ -19,8 +12,7 @@ import { VideoPlaylistPrivacy } from '@shared/models'
19 styleUrls: [ './video-watch-playlist.component.scss' ] 12 styleUrls: [ './video-watch-playlist.component.scss' ]
20}) 13})
21export class VideoWatchPlaylistComponent { 14export class VideoWatchPlaylistComponent {
22 static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'auto_play_video_playlist' 15 static SESSION_STORAGE_LOOP_PLAYLIST = 'loop_playlist'
23 static SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'loop_playlist'
24 16
25 @Input() playlist: VideoPlaylist 17 @Input() playlist: VideoPlaylist
26 18
@@ -47,19 +39,15 @@ export class VideoWatchPlaylistComponent {
47 private auth: AuthService, 39 private auth: AuthService,
48 private notifier: Notifier, 40 private notifier: Notifier,
49 private videoPlaylist: VideoPlaylistService, 41 private videoPlaylist: VideoPlaylistService,
50 private localStorageService: LocalStorageService,
51 private sessionStorage: SessionStorageService, 42 private sessionStorage: SessionStorageService,
52 private router: Router 43 private router: Router
53 ) { 44 ) {
54 // defaults to true 45 this.userService.getAnonymousOrLoggedUser()
55 this.autoPlayNextVideoPlaylist = this.auth.isLoggedIn() 46 .subscribe(user => this.autoPlayNextVideoPlaylist = user.autoPlayNextVideoPlaylist)
56 ? this.auth.getUser().autoPlayNextVideoPlaylist
57 : this.localStorageService.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false'
58 47
59 this.setAutoPlayNextVideoPlaylistSwitchText() 48 this.setAutoPlayNextVideoPlaylistSwitchText()
60 49
61 // defaults to false 50 this.loopPlaylist = getBoolOrDefault(this.sessionStorage.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_LOOP_PLAYLIST), false)
62 this.loopPlaylist = this.sessionStorage.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true'
63 this.setLoopPlaylistSwitchText() 51 this.setLoopPlaylistSwitchText()
64 } 52 }
65 53
@@ -201,16 +189,9 @@ export class VideoWatchPlaylistComponent {
201 this.autoPlayNextVideoPlaylist = !this.autoPlayNextVideoPlaylist 189 this.autoPlayNextVideoPlaylist = !this.autoPlayNextVideoPlaylist
202 this.setAutoPlayNextVideoPlaylistSwitchText() 190 this.setAutoPlayNextVideoPlaylistSwitchText()
203 191
204 peertubeLocalStorage.setItem( 192 const details = { autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist }
205 VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST,
206 this.autoPlayNextVideoPlaylist.toString()
207 )
208 193
209 if (this.auth.isLoggedIn()) { 194 if (this.auth.isLoggedIn()) {
210 const details = {
211 autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist
212 }
213
214 this.userService.updateMyProfile(details) 195 this.userService.updateMyProfile(details)
215 .subscribe({ 196 .subscribe({
216 next: () => { 197 next: () => {
@@ -219,6 +200,8 @@ export class VideoWatchPlaylistComponent {
219 200
220 error: err => this.notifier.error(err.message) 201 error: err => this.notifier.error(err.message)
221 }) 202 })
203 } else {
204 this.userService.updateMyAnonymousProfile(details)
222 } 205 }
223 } 206 }
224 207
@@ -227,7 +210,7 @@ export class VideoWatchPlaylistComponent {
227 this.setLoopPlaylistSwitchText() 210 this.setLoopPlaylistSwitchText()
228 211
229 peertubeSessionStorage.setItem( 212 peertubeSessionStorage.setItem(
230 VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST, 213 VideoWatchPlaylistComponent.SESSION_STORAGE_LOOP_PLAYLIST,
231 this.loopPlaylist.toString() 214 this.loopPlaylist.toString()
232 ) 215 )
233 } 216 }
diff --git a/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.ts b/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.ts
index dfc296d15..97f742499 100644
--- a/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.ts
@@ -1,10 +1,9 @@
1import { Observable } from 'rxjs' 1import { Observable } from 'rxjs'
2import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core' 2import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'
3import { AuthService, Notifier, SessionStorageService, User, UserService } from '@app/core' 3import { AuthService, Notifier, User, UserService } from '@app/core'
4import { Video } from '@app/shared/shared-main' 4import { Video } from '@app/shared/shared-main'
5import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature' 5import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature'
6import { VideoPlaylist } from '@app/shared/shared-video-playlist' 6import { VideoPlaylist } from '@app/shared/shared-video-playlist'
7import { UserLocalStorageKeys } from '@root-helpers/users'
8import { RecommendationInfo } from './recommendation-info.model' 7import { RecommendationInfo } from './recommendation-info.model'
9import { RecommendedVideosStore } from './recommended-videos.store' 8import { RecommendedVideosStore } from './recommended-videos.store'
10 9
@@ -39,24 +38,14 @@ export class RecommendedVideosComponent implements OnInit, OnChanges {
39 private userService: UserService, 38 private userService: UserService,
40 private authService: AuthService, 39 private authService: AuthService,
41 private notifier: Notifier, 40 private notifier: Notifier,
42 private store: RecommendedVideosStore, 41 private store: RecommendedVideosStore
43 private sessionStorageService: SessionStorageService
44 ) { 42 ) {
45 this.videos$ = this.store.recommendations$ 43 this.videos$ = this.store.recommendations$
46 this.hasVideos$ = this.store.hasRecommendations$ 44 this.hasVideos$ = this.store.hasRecommendations$
47 this.videos$.subscribe(videos => this.gotRecommendations.emit(videos)) 45 this.videos$.subscribe(videos => this.gotRecommendations.emit(videos))
48 46
49 if (this.authService.isLoggedIn()) { 47 this.userService.getAnonymousOrLoggedUser()
50 this.autoPlayNextVideo = this.authService.getUser().autoPlayNextVideo 48 .subscribe(user => this.autoPlayNextVideo = user.autoPlayNextVideo)
51 } else {
52 this.autoPlayNextVideo = this.sessionStorageService.getItem(UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
53
54 this.sessionStorageService.watch([ UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO ]).subscribe(
55 () => {
56 this.autoPlayNextVideo = this.sessionStorageService.getItem(UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
57 }
58 )
59 }
60 49
61 this.autoPlayNextVideoTooltip = $localize`When active, the next video is automatically played after the current one.` 50 this.autoPlayNextVideoTooltip = $localize`When active, the next video is automatically played after the current one.`
62 } 51 }
@@ -77,13 +66,9 @@ export class RecommendedVideosComponent implements OnInit, OnChanges {
77 } 66 }
78 67
79 switchAutoPlayNextVideo () { 68 switchAutoPlayNextVideo () {
80 this.sessionStorageService.setItem(UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO, this.autoPlayNextVideo.toString()) 69 const details = { autoPlayNextVideo: this.autoPlayNextVideo }
81 70
82 if (this.authService.isLoggedIn()) { 71 if (this.authService.isLoggedIn()) {
83 const details = {
84 autoPlayNextVideo: this.autoPlayNextVideo
85 }
86
87 this.userService.updateMyProfile(details) 72 this.userService.updateMyProfile(details)
88 .subscribe({ 73 .subscribe({
89 next: () => { 74 next: () => {
@@ -92,6 +77,8 @@ export class RecommendedVideosComponent implements OnInit, OnChanges {
92 77
93 error: err => this.notifier.error(err.message) 78 error: err => this.notifier.error(err.message)
94 }) 79 })
80 } else {
81 this.userService.updateMyAnonymousProfile(details)
95 } 82 }
96 } 83 }
97} 84}
diff --git a/client/src/app/+videos/+video-watch/video-watch.component.ts b/client/src/app/+videos/+video-watch/video-watch.component.ts
index fd61bcbf0..d542f243c 100644
--- a/client/src/app/+videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/+videos/+video-watch/video-watch.component.ts
@@ -1,5 +1,6 @@
1import { Hotkey, HotkeysService } from 'angular2-hotkeys' 1import { Hotkey, HotkeysService } from 'angular2-hotkeys'
2import { forkJoin, Subscription } from 'rxjs' 2import { forkJoin, Subscription } from 'rxjs'
3import { isP2PEnabled } from 'src/assets/player/utils'
3import { PlatformLocation } from '@angular/common' 4import { PlatformLocation } from '@angular/common'
4import { Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' 5import { Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
5import { ActivatedRoute, Router } from '@angular/router' 6import { ActivatedRoute, Router } from '@angular/router'
@@ -14,6 +15,7 @@ import {
14 RestExtractor, 15 RestExtractor,
15 ScreenService, 16 ScreenService,
16 ServerService, 17 ServerService,
18 User,
17 UserService 19 UserService
18} from '@app/core' 20} from '@app/core'
19import { HooksService } from '@app/core/plugins/hooks.service' 21import { HooksService } from '@app/core/plugins/hooks.service'
@@ -237,31 +239,34 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
237 'filter:api.video-watch.video.get.result' 239 'filter:api.video-watch.video.get.result'
238 ) 240 )
239 241
240 forkJoin([ videoObs, this.videoCaptionService.listCaptions(videoId) ]) 242 forkJoin([
241 .subscribe({ 243 videoObs,
242 next: ([ video, captionsResult ]) => { 244 this.videoCaptionService.listCaptions(videoId),
243 const queryParams = this.route.snapshot.queryParams 245 this.userService.getAnonymousOrLoggedUser()
246 ]).subscribe({
247 next: ([ video, captionsResult, loggedInOrAnonymousUser ]) => {
248 const queryParams = this.route.snapshot.queryParams
244 249
245 const urlOptions = { 250 const urlOptions = {
246 resume: queryParams.resume, 251 resume: queryParams.resume,
247 252
248 startTime: queryParams.start, 253 startTime: queryParams.start,
249 stopTime: queryParams.stop, 254 stopTime: queryParams.stop,
250 255
251 muted: queryParams.muted, 256 muted: queryParams.muted,
252 loop: queryParams.loop, 257 loop: queryParams.loop,
253 subtitle: queryParams.subtitle, 258 subtitle: queryParams.subtitle,
254 259
255 playerMode: queryParams.mode, 260 playerMode: queryParams.mode,
256 peertubeLink: false 261 peertubeLink: false
257 } 262 }
258 263
259 this.onVideoFetched(video, captionsResult.data, urlOptions) 264 this.onVideoFetched({ video, videoCaptions: captionsResult.data, loggedInOrAnonymousUser, urlOptions })
260 .catch(err => this.handleGlobalError(err)) 265 .catch(err => this.handleGlobalError(err))
261 }, 266 },
262 267
263 error: err => this.handleRequestError(err) 268 error: err => this.handleRequestError(err)
264 }) 269 })
265 } 270 }
266 271
267 private loadPlaylist (playlistId: string) { 272 private loadPlaylist (playlistId: string) {
@@ -323,11 +328,14 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
323 this.notifier.error(errorMessage) 328 this.notifier.error(errorMessage)
324 } 329 }
325 330
326 private async onVideoFetched ( 331 private async onVideoFetched (options: {
327 video: VideoDetails, 332 video: VideoDetails
328 videoCaptions: VideoCaption[], 333 videoCaptions: VideoCaption[]
329 urlOptions: URLOptions 334 urlOptions: URLOptions
330 ) { 335 loggedInOrAnonymousUser: User
336 }) {
337 const { video, videoCaptions, urlOptions, loggedInOrAnonymousUser } = options
338
331 this.subscribeToLiveEventsIfNeeded(this.video, video) 339 this.subscribeToLiveEventsIfNeeded(this.video, video)
332 340
333 this.video = video 341 this.video = video
@@ -346,7 +354,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
346 if (res === false) return this.location.back() 354 if (res === false) return this.location.back()
347 } 355 }
348 356
349 this.buildPlayer(urlOptions) 357 this.buildPlayer(urlOptions, loggedInOrAnonymousUser)
350 .catch(err => console.error('Cannot build the player', err)) 358 .catch(err => console.error('Cannot build the player', err))
351 359
352 this.setOpenGraphTags() 360 this.setOpenGraphTags()
@@ -359,7 +367,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
359 this.hooks.runAction('action:video-watch.video.loaded', 'video-watch', hookOptions) 367 this.hooks.runAction('action:video-watch.video.loaded', 'video-watch', hookOptions)
360 } 368 }
361 369
362 private async buildPlayer (urlOptions: URLOptions) { 370 private async buildPlayer (urlOptions: URLOptions, loggedInOrAnonymousUser: User) {
363 // Flush old player if needed 371 // Flush old player if needed
364 this.flushPlayer() 372 this.flushPlayer()
365 373
@@ -380,6 +388,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
380 video: this.video, 388 video: this.video,
381 videoCaptions: this.videoCaptions, 389 videoCaptions: this.videoCaptions,
382 urlOptions, 390 urlOptions,
391 loggedInOrAnonymousUser,
383 user: this.user 392 user: this.user
384 } 393 }
385 const { playerMode, playerOptions } = await this.hooks.wrapFun( 394 const { playerMode, playerOptions } = await this.hooks.wrapFun(
@@ -517,9 +526,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
517 video: VideoDetails 526 video: VideoDetails
518 videoCaptions: VideoCaption[] 527 videoCaptions: VideoCaption[]
519 urlOptions: CustomizationOptions & { playerMode: PlayerMode } 528 urlOptions: CustomizationOptions & { playerMode: PlayerMode }
529 loggedInOrAnonymousUser: User
520 user?: AuthUser 530 user?: AuthUser
521 }) { 531 }) {
522 const { video, videoCaptions, urlOptions, user } = params 532 const { video, videoCaptions, urlOptions, loggedInOrAnonymousUser, user } = params
523 533
524 const getStartTime = () => { 534 const getStartTime = () => {
525 const byUrl = urlOptions.startTime !== undefined 535 const byUrl = urlOptions.startTime !== undefined
@@ -547,6 +557,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
547 const options: PeertubePlayerManagerOptions = { 557 const options: PeertubePlayerManagerOptions = {
548 common: { 558 common: {
549 autoplay: this.isAutoplay(), 559 autoplay: this.isAutoplay(),
560 p2pEnabled: isP2PEnabled(video, this.serverConfig, loggedInOrAnonymousUser.p2pEnabled),
561
550 nextVideo: () => this.playNextVideoInAngularZone(), 562 nextVideo: () => this.playNextVideoInAngularZone(),
551 563
552 playerElement: this.playerElement, 564 playerElement: this.playerElement,
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index 95af89b19..a60138af9 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -14,7 +14,8 @@ import {
14 ScrollService, 14 ScrollService,
15 ServerService, 15 ServerService,
16 ThemeService, 16 ThemeService,
17 User 17 User,
18 UserLocalStorageService
18} from '@app/core' 19} from '@app/core'
19import { HooksService } from '@app/core/plugins/hooks.service' 20import { HooksService } from '@app/core/plugins/hooks.service'
20import { PluginService } from '@app/core/plugins/plugin.service' 21import { PluginService } from '@app/core/plugins/plugin.service'
@@ -70,6 +71,7 @@ export class AppComponent implements OnInit, AfterViewInit {
70 private ngbConfig: NgbConfig, 71 private ngbConfig: NgbConfig,
71 private loadingBar: LoadingBarService, 72 private loadingBar: LoadingBarService,
72 private scrollService: ScrollService, 73 private scrollService: ScrollService,
74 private userLocalStorage: UserLocalStorageService,
73 public menu: MenuService 75 public menu: MenuService
74 ) { 76 ) {
75 this.ngbConfig.animation = false 77 this.ngbConfig.animation = false
@@ -86,6 +88,8 @@ export class AppComponent implements OnInit, AfterViewInit {
86 ngOnInit () { 88 ngOnInit () {
87 document.getElementById('incompatible-browser').className += ' browser-ok' 89 document.getElementById('incompatible-browser').className += ' browser-ok'
88 90
91 this.loadUser()
92
89 this.serverConfig = this.serverService.getHTMLConfig() 93 this.serverConfig = this.serverService.getHTMLConfig()
90 94
91 this.hooks.runAction('action:application.init', 'common') 95 this.hooks.runAction('action:application.init', 'common')
@@ -300,4 +304,15 @@ export class AppComponent implements OnInit, AfterViewInit {
300 }, undefined, $localize`Go to the videos upload page`) 304 }, undefined, $localize`Go to the videos upload page`)
301 ]) 305 ])
302 } 306 }
307
308 private loadUser () {
309 const tokens = this.userLocalStorage.getTokens()
310 if (!tokens) return
311
312 const user = this.userLocalStorage.getLoggedInUser()
313 if (!user) return
314
315 // Initialize user
316 this.authService.buildAuthUser(user, tokens)
317 }
303} 318}
diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts
index f10b37e5a..cd9665e37 100644
--- a/client/src/app/core/auth/auth-user.model.ts
+++ b/client/src/app/core/auth/auth-user.model.ts
@@ -1,13 +1,7 @@
1import { Observable, of } from 'rxjs' 1import { Observable, of } from 'rxjs'
2import { map } from 'rxjs/operators' 2import { map } from 'rxjs/operators'
3import { User } from '@app/core/users/user.model' 3import { User } from '@app/core/users/user.model'
4import { 4import { UserTokens } from '@root-helpers/users'
5 flushUserInfoFromLocalStorage,
6 getUserInfoFromLocalStorage,
7 saveUserInfoIntoLocalStorage,
8 TokenOptions,
9 Tokens
10} from '@root-helpers/users'
11import { hasUserRight } from '@shared/core-utils/users' 5import { hasUserRight } from '@shared/core-utils/users'
12import { 6import {
13 MyUser as ServerMyUserModel, 7 MyUser as ServerMyUserModel,
@@ -19,31 +13,15 @@ import {
19} from '@shared/models' 13} from '@shared/models'
20 14
21export class AuthUser extends User implements ServerMyUserModel { 15export class AuthUser extends User implements ServerMyUserModel {
22 tokens: Tokens 16 tokens: UserTokens
23 specialPlaylists: MyUserSpecialPlaylist[] 17 specialPlaylists: MyUserSpecialPlaylist[]
24 18
25 canSeeVideosLink = true 19 canSeeVideosLink = true
26 20
27 static load () { 21 constructor (userHash: Partial<ServerMyUserModel>, hashTokens: Partial<UserTokens>) {
28 const tokens = Tokens.load()
29 if (!tokens) return null
30
31 const userInfo = getUserInfoFromLocalStorage()
32 if (!userInfo) return null
33
34 return new AuthUser(userInfo, tokens)
35 }
36
37 static flush () {
38 flushUserInfoFromLocalStorage()
39
40 Tokens.flush()
41 }
42
43 constructor (userHash: Partial<ServerMyUserModel>, hashTokens: TokenOptions) {
44 super(userHash) 22 super(userHash)
45 23
46 this.tokens = new Tokens(hashTokens) 24 this.tokens = new UserTokens(hashTokens)
47 this.specialPlaylists = userHash.specialPlaylists 25 this.specialPlaylists = userHash.specialPlaylists
48 } 26 }
49 27
@@ -77,20 +55,6 @@ export class AuthUser extends User implements ServerMyUserModel {
77 return user.role === UserRole.USER 55 return user.role === UserRole.USER
78 } 56 }
79 57
80 save () {
81 saveUserInfoIntoLocalStorage({
82 id: this.id,
83 username: this.username,
84 email: this.email,
85 role: this.role,
86 nsfwPolicy: this.nsfwPolicy,
87 webTorrentEnabled: this.webTorrentEnabled,
88 autoPlayVideo: this.autoPlayVideo
89 })
90
91 this.tokens.save()
92 }
93
94 computeCanSeeVideosLink (quotaObservable: Observable<UserVideoQuota>): Observable<boolean> { 58 computeCanSeeVideosLink (quotaObservable: Observable<UserVideoQuota>): Observable<boolean> {
95 if (!this.isUploadDisabled()) { 59 if (!this.isUploadDisabled()) {
96 this.canSeeVideosLink = true 60 this.canSeeVideosLink = true
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts
index 79239a17a..2ac88c185 100644
--- a/client/src/app/core/auth/auth.service.ts
+++ b/client/src/app/core/auth/auth.service.ts
@@ -5,7 +5,7 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'
5import { Injectable } from '@angular/core' 5import { Injectable } from '@angular/core'
6import { Router } from '@angular/router' 6import { Router } from '@angular/router'
7import { Notifier } from '@app/core/notification/notifier.service' 7import { Notifier } from '@app/core/notification/notifier.service'
8import { objectToUrlEncoded, peertubeLocalStorage } from '@root-helpers/index' 8import { objectToUrlEncoded, peertubeLocalStorage, UserTokens } from '@root-helpers/index'
9import { HttpStatusCode, MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models' 9import { HttpStatusCode, MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models'
10import { environment } from '../../../environments/environment' 10import { environment } from '../../../environments/environment'
11import { RestExtractor } from '../rest/rest-extractor.service' 11import { RestExtractor } from '../rest/rest-extractor.service'
@@ -34,6 +34,7 @@ export class AuthService {
34 34
35 loginChangedSource: Observable<AuthStatus> 35 loginChangedSource: Observable<AuthStatus>
36 userInformationLoaded = new ReplaySubject<boolean>(1) 36 userInformationLoaded = new ReplaySubject<boolean>(1)
37 tokensRefreshed = new ReplaySubject<void>(1)
37 hotkeys: Hotkey[] 38 hotkeys: Hotkey[]
38 39
39 private clientId: string = peertubeLocalStorage.getItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID) 40 private clientId: string = peertubeLocalStorage.getItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID)
@@ -52,9 +53,6 @@ export class AuthService {
52 this.loginChanged = new Subject<AuthStatus>() 53 this.loginChanged = new Subject<AuthStatus>()
53 this.loginChangedSource = this.loginChanged.asObservable() 54 this.loginChangedSource = this.loginChanged.asObservable()
54 55
55 // Return null if there is nothing to load
56 this.user = AuthUser.load()
57
58 // Set HotKeys 56 // Set HotKeys
59 this.hotkeys = [ 57 this.hotkeys = [
60 new Hotkey('m s', (event: KeyboardEvent): boolean => { 58 new Hotkey('m s', (event: KeyboardEvent): boolean => {
@@ -76,6 +74,10 @@ export class AuthService {
76 ] 74 ]
77 } 75 }
78 76
77 buildAuthUser (userInfo: Partial<User>, tokens: UserTokens) {
78 this.user = new AuthUser(userInfo, tokens)
79 }
80
79 loadClientCredentials () { 81 loadClientCredentials () {
80 // Fetch the client_id/client_secret 82 // Fetch the client_id/client_secret
81 this.http.get<OAuthClientLocal>(AuthService.BASE_CLIENT_URL) 83 this.http.get<OAuthClientLocal>(AuthService.BASE_CLIENT_URL)
@@ -180,8 +182,6 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
180 182
181 this.user = null 183 this.user = null
182 184
183 AuthUser.flush()
184
185 this.setStatus(AuthStatus.LoggedOut) 185 this.setStatus(AuthStatus.LoggedOut)
186 186
187 this.hotkeysService.remove(this.hotkeys) 187 this.hotkeysService.remove(this.hotkeys)
@@ -239,7 +239,6 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
239 .subscribe({ 239 .subscribe({
240 next: res => { 240 next: res => {
241 this.user.patch(res) 241 this.user.patch(res)
242 this.user.save()
243 242
244 this.userInformationLoaded.next(true) 243 this.userInformationLoaded.next(true)
245 } 244 }
@@ -262,7 +261,6 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
262 } 261 }
263 262
264 this.user = new AuthUser(obj, hashTokens) 263 this.user = new AuthUser(obj, hashTokens)
265 this.user.save()
266 264
267 this.setStatus(AuthStatus.LoggedIn) 265 this.setStatus(AuthStatus.LoggedIn)
268 this.userInformationLoaded.next(true) 266 this.userInformationLoaded.next(true)
@@ -272,7 +270,7 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
272 270
273 private handleRefreshToken (obj: UserRefreshToken) { 271 private handleRefreshToken (obj: UserRefreshToken) {
274 this.user.refreshTokens(obj.access_token, obj.refresh_token) 272 this.user.refreshTokens(obj.access_token, obj.refresh_token)
275 this.user.save() 273 this.tokensRefreshed.next()
276 } 274 }
277 275
278 private setStatus (status: AuthStatus) { 276 private setStatus (status: AuthStatus) {
diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts
index 04be0671c..d80f95ed6 100644
--- a/client/src/app/core/core.module.ts
+++ b/client/src/app/core/core.module.ts
@@ -30,7 +30,7 @@ import { ServerConfigResolver } from './routing/server-config-resolver.service'
30import { ScopedTokensService } from './scoped-tokens' 30import { ScopedTokensService } from './scoped-tokens'
31import { ServerService } from './server' 31import { ServerService } from './server'
32import { ThemeService } from './theme' 32import { ThemeService } from './theme'
33import { UserService } from './users' 33import { UserLocalStorageService, UserService } from './users'
34import { LocalStorageService, ScreenService, SessionStorageService } from './wrappers' 34import { LocalStorageService, ScreenService, SessionStorageService } from './wrappers'
35 35
36@NgModule({ 36@NgModule({
@@ -79,6 +79,7 @@ import { LocalStorageService, ScreenService, SessionStorageService } from './wra
79 RestService, 79 RestService,
80 80
81 UserService, 81 UserService,
82 UserLocalStorageService,
82 83
83 ScreenService, 84 ScreenService,
84 LocalStorageService, 85 LocalStorageService,
diff --git a/client/src/app/core/users/index.ts b/client/src/app/core/users/index.ts
index 7b5a67bc7..e235a875b 100644
--- a/client/src/app/core/users/index.ts
+++ b/client/src/app/core/users/index.ts
@@ -1,2 +1,3 @@
1export * from './user-local-storage.service'
1export * from './user.model' 2export * from './user.model'
2export * from './user.service' 3export * 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
index 000000000..85da46e0d
--- /dev/null
+++ b/client/src/app/core/users/user-local-storage.service.ts
@@ -0,0 +1,186 @@
1
2import { filter, throttleTime } from 'rxjs'
3import { Injectable } from '@angular/core'
4import { AuthService, AuthStatus } from '@app/core/auth'
5import { UserLocalStorageKeys, UserTokens } from '@root-helpers/users'
6import { getBoolOrDefault } from '@root-helpers/local-storage-utils'
7import { UserRole, UserUpdateMe } from '@shared/models'
8import { NSFWPolicyType } from '@shared/models/videos'
9import { ServerService } from '../server'
10import { LocalStorageService } from '../wrappers/storage.service'
11
12@Injectable()
13export class UserLocalStorageService {
14
15 constructor (
16 private authService: AuthService,
17 private server: ServerService,
18 private localStorageService: LocalStorageService
19 ) {
20 this.authService.userInformationLoaded.subscribe({
21 next: () => {
22 const user = this.authService.getUser()
23
24 this.setLoggedInUser(user)
25 this.setUserInfo(user)
26 this.setTokens(user.tokens)
27 }
28 })
29
30 this.authService.loginChangedSource
31 .pipe(filter(status => status === AuthStatus.LoggedOut))
32 .subscribe({
33 next: () => {
34 this.flushLoggedInUser()
35 this.flushUserInfo()
36 this.flushTokens()
37 }
38 })
39
40 this.authService.tokensRefreshed
41 .subscribe({
42 next: () => {
43 const user = this.authService.getUser()
44
45 this.setTokens(user.tokens)
46 }
47 })
48 }
49
50 // ---------------------------------------------------------------------------
51
52 getLoggedInUser () {
53 const usernameLocalStorage = this.localStorageService.getItem(UserLocalStorageKeys.USERNAME)
54
55 if (!usernameLocalStorage) return undefined
56
57 return {
58 id: parseInt(this.localStorageService.getItem(UserLocalStorageKeys.ID), 10),
59 username: this.localStorageService.getItem(UserLocalStorageKeys.USERNAME),
60 email: this.localStorageService.getItem(UserLocalStorageKeys.EMAIL),
61 role: parseInt(this.localStorageService.getItem(UserLocalStorageKeys.ROLE), 10) as UserRole,
62
63 ...this.getUserInfo()
64 }
65 }
66
67 setLoggedInUser (user: {
68 id: number
69 username: string
70 email: string
71 role: UserRole
72 }) {
73 this.localStorageService.setItem(UserLocalStorageKeys.ID, user.id.toString())
74 this.localStorageService.setItem(UserLocalStorageKeys.USERNAME, user.username)
75 this.localStorageService.setItem(UserLocalStorageKeys.EMAIL, user.email)
76 this.localStorageService.setItem(UserLocalStorageKeys.ROLE, user.role.toString())
77 }
78
79 flushLoggedInUser () {
80 this.localStorageService.removeItem(UserLocalStorageKeys.ID)
81 this.localStorageService.removeItem(UserLocalStorageKeys.USERNAME)
82 this.localStorageService.removeItem(UserLocalStorageKeys.EMAIL)
83 this.localStorageService.removeItem(UserLocalStorageKeys.ROLE)
84 }
85
86 // ---------------------------------------------------------------------------
87
88 getUserInfo () {
89 let videoLanguages: string[]
90
91 try {
92 const languagesString = this.localStorageService.getItem(UserLocalStorageKeys.VIDEO_LANGUAGES)
93 videoLanguages = languagesString && languagesString !== 'undefined'
94 ? JSON.parse(languagesString)
95 : null
96 } catch (err) {
97 videoLanguages = null
98 console.error('Cannot parse desired video languages from localStorage.', err)
99 }
100
101 const htmlConfig = this.server.getHTMLConfig()
102
103 const defaultNSFWPolicy = htmlConfig.instance.defaultNSFWPolicy
104 const defaultP2PEnabled = htmlConfig.defaults.p2p.enabled
105
106 return {
107 nsfwPolicy: this.localStorageService.getItem<NSFWPolicyType>(UserLocalStorageKeys.NSFW_POLICY) || defaultNSFWPolicy,
108 p2pEnabled: getBoolOrDefault(this.localStorageService.getItem(UserLocalStorageKeys.P2P_ENABLED), defaultP2PEnabled),
109 theme: this.localStorageService.getItem(UserLocalStorageKeys.THEME) || 'instance-default',
110 videoLanguages,
111
112 autoPlayVideo: getBoolOrDefault(this.localStorageService.getItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO), true),
113 autoPlayNextVideo: getBoolOrDefault(this.localStorageService.getItem(UserLocalStorageKeys.AUTO_PLAY_NEXT_VIDEO), false),
114 autoPlayNextVideoPlaylist: getBoolOrDefault(this.localStorageService.getItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST), true)
115 }
116 }
117
118 setUserInfo (profile: UserUpdateMe) {
119 const localStorageKeys: { [ id in keyof UserUpdateMe ]: string } = {
120 nsfwPolicy: UserLocalStorageKeys.NSFW_POLICY,
121 p2pEnabled: UserLocalStorageKeys.P2P_ENABLED,
122 autoPlayNextVideo: UserLocalStorageKeys.AUTO_PLAY_VIDEO,
123 autoPlayNextVideoPlaylist: UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST,
124 theme: UserLocalStorageKeys.THEME,
125 videoLanguages: UserLocalStorageKeys.VIDEO_LANGUAGES
126 }
127
128 const obj = Object.keys(localStorageKeys)
129 .filter(key => key in profile)
130 .map(key => ([ localStorageKeys[key], profile[key] ]))
131
132 for (const [ key, value ] of obj) {
133 try {
134 if (value === undefined) {
135 this.localStorageService.removeItem(key)
136 continue
137 }
138
139 const localStorageValue = typeof value === 'string'
140 ? value
141 : JSON.stringify(value)
142
143 this.localStorageService.setItem(key, localStorageValue)
144 } catch (err) {
145 console.error(`Cannot set ${key}->${value} in localStorage. Likely due to a value impossible to stringify.`, err)
146 }
147 }
148 }
149
150 flushUserInfo () {
151 this.localStorageService.removeItem(UserLocalStorageKeys.NSFW_POLICY)
152 this.localStorageService.removeItem(UserLocalStorageKeys.P2P_ENABLED)
153 this.localStorageService.removeItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO)
154 this.localStorageService.removeItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST)
155 this.localStorageService.removeItem(UserLocalStorageKeys.THEME)
156 this.localStorageService.removeItem(UserLocalStorageKeys.VIDEO_LANGUAGES)
157 }
158
159 listenUserInfoChange () {
160 return this.localStorageService.watch([
161 UserLocalStorageKeys.NSFW_POLICY,
162 UserLocalStorageKeys.P2P_ENABLED,
163 UserLocalStorageKeys.AUTO_PLAY_VIDEO,
164 UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST,
165 UserLocalStorageKeys.THEME,
166 UserLocalStorageKeys.VIDEO_LANGUAGES
167 ]).pipe(
168 throttleTime(200),
169 filter(() => this.authService.isLoggedIn() !== true)
170 )
171 }
172
173 // ---------------------------------------------------------------------------
174
175 getTokens () {
176 return UserTokens.getUserTokens(this.localStorageService)
177 }
178
179 setTokens (tokens: UserTokens) {
180 UserTokens.saveToLocalStorage(this.localStorageService, tokens)
181 }
182
183 flushTokens () {
184 UserTokens.flushLocalStorage(this.localStorageService)
185 }
186}
diff --git a/client/src/app/core/users/user.model.ts b/client/src/app/core/users/user.model.ts
index c0e5d3169..f211051ce 100644
--- a/client/src/app/core/users/user.model.ts
+++ b/client/src/app/core/users/user.model.ts
@@ -26,7 +26,11 @@ export class User implements UserServerModel {
26 autoPlayVideo: boolean 26 autoPlayVideo: boolean
27 autoPlayNextVideo: boolean 27 autoPlayNextVideo: boolean
28 autoPlayNextVideoPlaylist: boolean 28 autoPlayNextVideoPlaylist: boolean
29 webTorrentEnabled: boolean 29
30 p2pEnabled: boolean
31 // FIXME: deprecated in 4.1
32 webTorrentEnabled: never
33
30 videosHistoryEnabled: boolean 34 videosHistoryEnabled: boolean
31 videoLanguages: string[] 35 videoLanguages: string[]
32 36
@@ -84,7 +88,7 @@ export class User implements UserServerModel {
84 this.videoCommentsCount = hash.videoCommentsCount 88 this.videoCommentsCount = hash.videoCommentsCount
85 89
86 this.nsfwPolicy = hash.nsfwPolicy 90 this.nsfwPolicy = hash.nsfwPolicy
87 this.webTorrentEnabled = hash.webTorrentEnabled 91 this.p2pEnabled = hash.p2pEnabled
88 this.autoPlayVideo = hash.autoPlayVideo 92 this.autoPlayVideo = hash.autoPlayVideo
89 this.autoPlayNextVideo = hash.autoPlayNextVideo 93 this.autoPlayNextVideo = hash.autoPlayNextVideo
90 this.autoPlayNextVideoPlaylist = hash.autoPlayNextVideoPlaylist 94 this.autoPlayNextVideoPlaylist = hash.autoPlayNextVideoPlaylist
diff --git a/client/src/app/core/users/user.service.ts b/client/src/app/core/users/user.service.ts
index 632361e9b..a6a0474ab 100644
--- a/client/src/app/core/users/user.service.ts
+++ b/client/src/app/core/users/user.service.ts
@@ -1,11 +1,10 @@
1import { SortMeta } from 'primeng/api' 1import { SortMeta } from 'primeng/api'
2import { from, Observable, of } from 'rxjs' 2import { from, Observable, of } from 'rxjs'
3import { catchError, concatMap, filter, first, map, shareReplay, tap, throttleTime, toArray } from 'rxjs/operators' 3import { catchError, concatMap, first, map, shareReplay, tap, toArray } from 'rxjs/operators'
4import { HttpClient, HttpParams } from '@angular/common/http' 4import { HttpClient, HttpParams } from '@angular/common/http'
5import { Injectable } from '@angular/core' 5import { Injectable } from '@angular/core'
6import { AuthService } from '@app/core/auth' 6import { AuthService } from '@app/core/auth'
7import { getBytes } from '@root-helpers/bytes' 7import { getBytes } from '@root-helpers/bytes'
8import { UserLocalStorageKeys } from '@root-helpers/users'
9import { 8import {
10 ActorImage, 9 ActorImage,
11 ResultList, 10 ResultList,
@@ -17,10 +16,9 @@ import {
17 UserUpdateMe, 16 UserUpdateMe,
18 UserVideoQuota 17 UserVideoQuota
19} from '@shared/models' 18} from '@shared/models'
20import { ServerService } from '../'
21import { environment } from '../../../environments/environment' 19import { environment } from '../../../environments/environment'
22import { RestExtractor, RestPagination, RestService } from '../rest' 20import { RestExtractor, RestPagination, RestService } from '../rest'
23import { LocalStorageService, SessionStorageService } from '../wrappers/storage.service' 21import { UserLocalStorageService } from './'
24import { User } from './user.model' 22import { User } from './user.model'
25 23
26@Injectable() 24@Injectable()
@@ -33,12 +31,10 @@ export class UserService {
33 31
34 constructor ( 32 constructor (
35 private authHttp: HttpClient, 33 private authHttp: HttpClient,
36 private server: ServerService,
37 private authService: AuthService, 34 private authService: AuthService,
38 private restExtractor: RestExtractor, 35 private restExtractor: RestExtractor,
39 private restService: RestService, 36 private restService: RestService,
40 private localStorageService: LocalStorageService, 37 private userLocalStorageService: UserLocalStorageService
41 private sessionStorageService: SessionStorageService
42 ) { } 38 ) { }
43 39
44 hasSignupInThisSession () { 40 hasSignupInThisSession () {
@@ -73,6 +69,23 @@ export class UserService {
73 ) 69 )
74 } 70 }
75 71
72 // ---------------------------------------------------------------------------
73
74 updateMyAnonymousProfile (profile: UserUpdateMe) {
75 this.userLocalStorageService.setUserInfo(profile)
76 }
77
78 listenAnonymousUpdate () {
79 return this.userLocalStorageService.listenUserInfoChange()
80 .pipe(map(() => this.getAnonymousUser()))
81 }
82
83 getAnonymousUser () {
84 return new User(this.userLocalStorageService.getUserInfo())
85 }
86
87 // ---------------------------------------------------------------------------
88
76 updateMyProfile (profile: UserUpdateMe) { 89 updateMyProfile (profile: UserUpdateMe) {
77 const url = UserService.BASE_USERS_URL + 'me' 90 const url = UserService.BASE_USERS_URL + 'me'
78 91
@@ -83,53 +96,6 @@ export class UserService {
83 ) 96 )
84 } 97 }
85 98
86 updateMyAnonymousProfile (profile: UserUpdateMe) {
87 const localStorageKeys: { [ id in keyof UserUpdateMe ]: string } = {
88 nsfwPolicy: UserLocalStorageKeys.NSFW_POLICY,
89 webTorrentEnabled: UserLocalStorageKeys.WEBTORRENT_ENABLED,
90 autoPlayNextVideo: UserLocalStorageKeys.AUTO_PLAY_VIDEO,
91 autoPlayNextVideoPlaylist: UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST,
92 theme: UserLocalStorageKeys.THEME,
93 videoLanguages: UserLocalStorageKeys.VIDEO_LANGUAGES
94 }
95
96 const obj = Object.keys(localStorageKeys)
97 .filter(key => key in profile)
98 .map(key => ([ localStorageKeys[key], profile[key] ]))
99
100 for (const [ key, value ] of obj) {
101 try {
102 if (value === undefined) {
103 this.localStorageService.removeItem(key)
104 continue
105 }
106
107 const localStorageValue = typeof value === 'string'
108 ? value
109 : JSON.stringify(value)
110
111 this.localStorageService.setItem(key, localStorageValue)
112 } catch (err) {
113 console.error(`Cannot set ${key}->${value} in localStorage. Likely due to a value impossible to stringify.`, err)
114 }
115 }
116 }
117
118 listenAnonymousUpdate () {
119 return this.localStorageService.watch([
120 UserLocalStorageKeys.NSFW_POLICY,
121 UserLocalStorageKeys.WEBTORRENT_ENABLED,
122 UserLocalStorageKeys.AUTO_PLAY_VIDEO,
123 UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST,
124 UserLocalStorageKeys.THEME,
125 UserLocalStorageKeys.VIDEO_LANGUAGES
126 ]).pipe(
127 throttleTime(200),
128 filter(() => this.authService.isLoggedIn() !== true),
129 map(() => this.getAnonymousUser())
130 )
131 }
132
133 deleteMe () { 99 deleteMe () {
134 const url = UserService.BASE_USERS_URL + 'me' 100 const url = UserService.BASE_USERS_URL + 'me'
135 101
@@ -287,36 +253,6 @@ export class UserService {
287 .pipe(catchError(err => this.restExtractor.handleError(err))) 253 .pipe(catchError(err => this.restExtractor.handleError(err)))
288 } 254 }
289 255
290 getAnonymousUser () {
291 let videoLanguages: string[]
292
293 try {
294 const languagesString = this.localStorageService.getItem(UserLocalStorageKeys.VIDEO_LANGUAGES)
295 videoLanguages = languagesString && languagesString !== 'undefined'
296 ? JSON.parse(languagesString)
297 : null
298 } catch (err) {
299 videoLanguages = null
300 console.error('Cannot parse desired video languages from localStorage.', err)
301 }
302
303 const defaultNSFWPolicy = this.server.getHTMLConfig().instance.defaultNSFWPolicy
304
305 return new User({
306 // local storage keys
307 nsfwPolicy: this.localStorageService.getItem(UserLocalStorageKeys.NSFW_POLICY) || defaultNSFWPolicy,
308 webTorrentEnabled: this.localStorageService.getItem(UserLocalStorageKeys.WEBTORRENT_ENABLED) !== 'false',
309 theme: this.localStorageService.getItem(UserLocalStorageKeys.THEME) || 'instance-default',
310 videoLanguages,
311
312 autoPlayNextVideoPlaylist: this.localStorageService.getItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST) !== 'false',
313 autoPlayVideo: this.localStorageService.getItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO) === 'true',
314
315 // session storage keys
316 autoPlayNextVideo: this.sessionStorageService.getItem(UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
317 })
318 }
319
320 getUsers (parameters: { 256 getUsers (parameters: {
321 pagination: RestPagination 257 pagination: RestPagination
322 sort: SortMeta 258 sort: SortMeta
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html
index 9ea991042..48b3fdc85 100644
--- a/client/src/app/menu/menu.component.html
+++ b/client/src/app/menu/menu.component.html
@@ -60,7 +60,7 @@
60 <my-global-icon iconName="p2p" aria-hidden="true"></my-global-icon> 60 <my-global-icon iconName="p2p" aria-hidden="true"></my-global-icon>
61 <ng-container i18n>Help share videos</ng-container> 61 <ng-container i18n>Help share videos</ng-container>
62 62
63 <my-input-switch class="ml-auto" [checked]="user.webTorrentEnabled"></my-input-switch> 63 <my-input-switch class="ml-auto" [checked]="user.p2pEnabled"></my-input-switch>
64 </a> 64 </a>
65 65
66 <div class="dropdown-divider"></div> 66 <div class="dropdown-divider"></div>
diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts
index d5ddc29cb..983f0a938 100644
--- a/client/src/app/menu/menu.component.ts
+++ b/client/src/app/menu/menu.component.ts
@@ -196,9 +196,9 @@ export class MenuComponent implements OnInit {
196 196
197 toggleUseP2P () { 197 toggleUseP2P () {
198 if (!this.user) return 198 if (!this.user) return
199 this.user.webTorrentEnabled = !this.user.webTorrentEnabled 199 this.user.p2pEnabled = !this.user.p2pEnabled
200 200
201 this.userService.updateMyProfile({ webTorrentEnabled: this.user.webTorrentEnabled }) 201 this.userService.updateMyProfile({ p2pEnabled: this.user.p2pEnabled })
202 .subscribe(() => this.authService.refreshUserInformation()) 202 .subscribe(() => this.authService.refreshUserInformation())
203 } 203 }
204 204
diff --git a/client/src/app/shared/shared-search/search.service.ts b/client/src/app/shared/shared-search/search.service.ts
index 61acfb466..ad2de0f37 100644
--- a/client/src/app/shared/shared-search/search.service.ts
+++ b/client/src/app/shared/shared-search/search.service.ts
@@ -4,7 +4,6 @@ import { HttpClient, HttpParams } from '@angular/common/http'
4import { Injectable } from '@angular/core' 4import { Injectable } from '@angular/core'
5import { ComponentPaginationLight, RestExtractor, RestPagination, RestService } from '@app/core' 5import { ComponentPaginationLight, RestExtractor, RestPagination, RestService } from '@app/core'
6import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' 6import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
7import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
8import { 7import {
9 ResultList, 8 ResultList,
10 Video as VideoServerModel, 9 Video as VideoServerModel,
@@ -25,11 +24,7 @@ export class SearchService {
25 private restService: RestService, 24 private restService: RestService,
26 private videoService: VideoService, 25 private videoService: VideoService,
27 private playlistService: VideoPlaylistService 26 private playlistService: VideoPlaylistService
28 ) { 27 ) { }
29 // Add ability to override search endpoint if the user updated this local storage key
30 const searchUrl = peertubeLocalStorage.getItem('search-url')
31 if (searchUrl) SearchService.BASE_SEARCH_URL = searchUrl
32 }
33 28
34 searchVideos (parameters: { 29 searchVideos (parameters: {
35 search?: string 30 search?: string
diff --git a/client/src/app/shared/shared-user-settings/user-video-settings.component.html b/client/src/app/shared/shared-user-settings/user-video-settings.component.html
index bc9dd0f7f..4843f65b9 100644
--- a/client/src/app/shared/shared-user-settings/user-video-settings.component.html
+++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.html
@@ -38,7 +38,7 @@
38 38
39 <div class="form-group"> 39 <div class="form-group">
40 <my-peertube-checkbox 40 <my-peertube-checkbox
41 inputName="webTorrentEnabled" formControlName="webTorrentEnabled" [recommended]="true" 41 inputName="p2pEnabled" formControlName="p2pEnabled" [recommended]="true"
42 i18n-labelText labelText="Help share videos being played" 42 i18n-labelText labelText="Help share videos being played"
43 > 43 >
44 <ng-container ngProjectAs="description"> 44 <ng-container ngProjectAs="description">
diff --git a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts
index 0cd889a8a..7d6b69469 100644
--- a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts
+++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts
@@ -34,7 +34,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
34 ngOnInit () { 34 ngOnInit () {
35 this.buildForm({ 35 this.buildForm({
36 nsfwPolicy: null, 36 nsfwPolicy: null,
37 webTorrentEnabled: null, 37 p2pEnabled: null,
38 autoPlayVideo: null, 38 autoPlayVideo: null,
39 autoPlayNextVideo: null, 39 autoPlayNextVideo: null,
40 videoLanguages: null 40 videoLanguages: null
@@ -48,7 +48,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
48 48
49 this.form.patchValue({ 49 this.form.patchValue({
50 nsfwPolicy: this.user.nsfwPolicy || this.defaultNSFWPolicy, 50 nsfwPolicy: this.user.nsfwPolicy || this.defaultNSFWPolicy,
51 webTorrentEnabled: this.user.webTorrentEnabled, 51 p2pEnabled: this.user.p2pEnabled,
52 autoPlayVideo: this.user.autoPlayVideo === true, 52 autoPlayVideo: this.user.autoPlayVideo === true,
53 autoPlayNextVideo: this.user.autoPlayNextVideo, 53 autoPlayNextVideo: this.user.autoPlayNextVideo,
54 videoLanguages: this.user.videoLanguages 54 videoLanguages: this.user.videoLanguages
@@ -65,7 +65,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
65 65
66 updateDetails (onlyKeys?: string[]) { 66 updateDetails (onlyKeys?: string[]) {
67 const nsfwPolicy = this.form.value['nsfwPolicy'] 67 const nsfwPolicy = this.form.value['nsfwPolicy']
68 const webTorrentEnabled = this.form.value['webTorrentEnabled'] 68 const p2pEnabled = this.form.value['p2pEnabled']
69 const autoPlayVideo = this.form.value['autoPlayVideo'] 69 const autoPlayVideo = this.form.value['autoPlayVideo']
70 const autoPlayNextVideo = this.form.value['autoPlayNextVideo'] 70 const autoPlayNextVideo = this.form.value['autoPlayNextVideo']
71 71
@@ -80,7 +80,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
80 80
81 let details: UserUpdateMe = { 81 let details: UserUpdateMe = {
82 nsfwPolicy, 82 nsfwPolicy,
83 webTorrentEnabled, 83 p2pEnabled,
84 autoPlayVideo, 84 autoPlayVideo,
85 autoPlayNextVideo, 85 autoPlayNextVideo,
86 videoLanguages 86 videoLanguages
diff --git a/client/src/assets/player/peertube-player-local-storage.ts b/client/src/assets/player/peertube-player-local-storage.ts
index d4cbda3a9..d9dacfba5 100644
--- a/client/src/assets/player/peertube-player-local-storage.ts
+++ b/client/src/assets/player/peertube-player-local-storage.ts
@@ -10,14 +10,6 @@ function getStoredVolume () {
10 return undefined 10 return undefined
11} 11}
12 12
13function getStoredP2PEnabled (): boolean {
14 const value = getLocalStorage('webtorrent_enabled')
15 if (value !== null && value !== undefined) return value === 'true'
16
17 // By default webtorrent is enabled
18 return true
19}
20
21function getStoredMute () { 13function getStoredMute () {
22 const value = getLocalStorage('mute') 14 const value = getLocalStorage('mute')
23 if (value !== null && value !== undefined) return value === 'true' 15 if (value !== null && value !== undefined) return value === 'true'
@@ -123,7 +115,6 @@ function cleanupVideoWatch () {
123 115
124export { 116export {
125 getStoredVolume, 117 getStoredVolume,
126 getStoredP2PEnabled,
127 getStoredMute, 118 getStoredMute,
128 getStoredTheater, 119 getStoredTheater,
129 saveVolumeInStore, 120 saveVolumeInStore,
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts
index 3e6398f50..c27024beb 100644
--- a/client/src/assets/player/peertube-player-manager.ts
+++ b/client/src/assets/player/peertube-player-manager.ts
@@ -31,7 +31,7 @@ import { copyToClipboard } from '../../root-helpers/utils'
31import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager' 31import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
32import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder' 32import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder'
33import { segmentValidatorFactory } from './p2p-media-loader/segment-validator' 33import { segmentValidatorFactory } from './p2p-media-loader/segment-validator'
34import { getAverageBandwidthInStore, getStoredP2PEnabled, saveAverageBandwidth } from './peertube-player-local-storage' 34import { getAverageBandwidthInStore, saveAverageBandwidth } from './peertube-player-local-storage'
35import { 35import {
36 NextPreviousVideoButtonOptions, 36 NextPreviousVideoButtonOptions,
37 P2PMediaLoaderPluginOptions, 37 P2PMediaLoaderPluginOptions,
@@ -86,6 +86,7 @@ export interface CommonOptions extends CustomizationOptions {
86 onPlayerElementChange: (element: HTMLVideoElement) => void 86 onPlayerElementChange: (element: HTMLVideoElement) => void
87 87
88 autoplay: boolean 88 autoplay: boolean
89 p2pEnabled: boolean
89 90
90 nextVideo?: () => void 91 nextVideo?: () => void
91 hasNextVideo?: () => boolean 92 hasNextVideo?: () => boolean
@@ -374,7 +375,7 @@ export class PeertubePlayerManager {
374 requiredSegmentsPriority: 1, 375 requiredSegmentsPriority: 1,
375 simultaneousHttpDownloads: 1, 376 simultaneousHttpDownloads: 1,
376 segmentUrlBuilder: segmentUrlBuilderFactory(redundancyUrlManager, 1), 377 segmentUrlBuilder: segmentUrlBuilderFactory(redundancyUrlManager, 1),
377 useP2P: getStoredP2PEnabled(), 378 useP2P: commonOptions.p2pEnabled,
378 consumeOnly 379 consumeOnly
379 }, 380 },
380 segments: { 381 segments: {
@@ -437,6 +438,7 @@ export class PeertubePlayerManager {
437 438
438 const webtorrent = { 439 const webtorrent = {
439 autoplay, 440 autoplay,
441 playerRefusedP2P: commonOptions.p2pEnabled === false,
440 videoDuration: commonOptions.videoDuration, 442 videoDuration: commonOptions.videoDuration,
441 playerElement: commonOptions.playerElement, 443 playerElement: commonOptions.playerElement,
442 videoFiles: webtorrentOptions.videoFiles.length !== 0 444 videoFiles: webtorrentOptions.videoFiles.length !== 0
diff --git a/client/src/assets/player/peertube-videojs-typings.ts b/client/src/assets/player/peertube-videojs-typings.ts
index ea39ac44d..824ea058b 100644
--- a/client/src/assets/player/peertube-videojs-typings.ts
+++ b/client/src/assets/player/peertube-videojs-typings.ts
@@ -137,6 +137,8 @@ type WebtorrentPluginOptions = {
137 videoFiles: VideoFile[] 137 videoFiles: VideoFile[]
138 138
139 startTime: number | string 139 startTime: number | string
140
141 playerRefusedP2P: boolean
140} 142}
141 143
142type P2PMediaLoaderPluginOptions = { 144type P2PMediaLoaderPluginOptions = {
diff --git a/client/src/assets/player/utils.ts b/client/src/assets/player/utils.ts
index 18380d950..7e25e3067 100644
--- a/client/src/assets/player/utils.ts
+++ b/client/src/assets/player/utils.ts
@@ -1,4 +1,4 @@
1import { VideoFile } from '@shared/models' 1import { HTMLServerConfig, Video, VideoFile } from '@shared/models'
2 2
3function toTitleCase (str: string) { 3function toTitleCase (str: string) {
4 return str.charAt(0).toUpperCase() + str.slice(1) 4 return str.charAt(0).toUpperCase() + str.slice(1)
@@ -8,6 +8,13 @@ function isWebRTCDisabled () {
8 return !!((window as any).RTCPeerConnection || (window as any).mozRTCPeerConnection || (window as any).webkitRTCPeerConnection) === false 8 return !!((window as any).RTCPeerConnection || (window as any).mozRTCPeerConnection || (window as any).webkitRTCPeerConnection) === false
9} 9}
10 10
11function isP2PEnabled (video: Video, config: HTMLServerConfig, userP2PEnabled: boolean) {
12 if (video.isLocal && config.tracker.enabled === false) return false
13 if (isWebRTCDisabled()) return false
14
15 return userP2PEnabled
16}
17
11function isIOS () { 18function isIOS () {
12 if (/iPad|iPhone|iPod/.test(navigator.platform)) { 19 if (/iPad|iPhone|iPod/.test(navigator.platform)) {
13 return true 20 return true
@@ -97,6 +104,7 @@ export {
97 getRtcConfig, 104 getRtcConfig,
98 toTitleCase, 105 toTitleCase,
99 isWebRTCDisabled, 106 isWebRTCDisabled,
107 isP2PEnabled,
100 108
101 buildVideoOrPlaylistEmbed, 109 buildVideoOrPlaylistEmbed,
102 videoFileMaxByResolution, 110 videoFileMaxByResolution,
diff --git a/client/src/assets/player/webtorrent/webtorrent-plugin.ts b/client/src/assets/player/webtorrent/webtorrent-plugin.ts
index 1a1cd7f1a..2b9390206 100644
--- a/client/src/assets/player/webtorrent/webtorrent-plugin.ts
+++ b/client/src/assets/player/webtorrent/webtorrent-plugin.ts
@@ -2,13 +2,7 @@ import videojs from 'video.js'
2import * as WebTorrent from 'webtorrent' 2import * as WebTorrent from 'webtorrent'
3import { timeToInt } from '@shared/core-utils' 3import { timeToInt } from '@shared/core-utils'
4import { VideoFile } from '@shared/models' 4import { VideoFile } from '@shared/models'
5import { 5import { getAverageBandwidthInStore, getStoredMute, getStoredVolume, saveAverageBandwidth } from '../peertube-player-local-storage'
6 getAverageBandwidthInStore,
7 getStoredMute,
8 getStoredP2PEnabled,
9 getStoredVolume,
10 saveAverageBandwidth
11} from '../peertube-player-local-storage'
12import { PeerTubeResolution, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings' 6import { PeerTubeResolution, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings'
13import { getRtcConfig, isIOS, videoFileMaxByResolution, videoFileMinByResolution } from '../utils' 7import { getRtcConfig, isIOS, videoFileMaxByResolution, videoFileMinByResolution } from '../utils'
14import { PeertubeChunkStore } from './peertube-chunk-store' 8import { PeertubeChunkStore } from './peertube-chunk-store'
@@ -74,9 +68,10 @@ class WebTorrentPlugin extends Plugin {
74 68
75 this.startTime = timeToInt(options.startTime) 69 this.startTime = timeToInt(options.startTime)
76 70
77 // Disable auto play on iOS 71 // Custom autoplay handled by webtorrent because we lazy play the video
78 this.autoplay = options.autoplay 72 this.autoplay = options.autoplay
79 this.playerRefusedP2P = !getStoredP2PEnabled() 73
74 this.playerRefusedP2P = options.playerRefusedP2P
80 75
81 this.videoFiles = options.videoFiles 76 this.videoFiles = options.videoFiles
82 this.videoDuration = options.videoDuration 77 this.videoDuration = options.videoDuration
diff --git a/client/src/root-helpers/index.ts b/client/src/root-helpers/index.ts
index 63d55d7ef..aa3b442dd 100644
--- a/client/src/root-helpers/index.ts
+++ b/client/src/root-helpers/index.ts
@@ -1,6 +1,7 @@
1export * from './users' 1export * from './users'
2export * from './bytes' 2export * from './bytes'
3export * from './images' 3export * from './images'
4export * from './local-storage-utils'
4export * from './peertube-web-storage' 5export * from './peertube-web-storage'
5export * from './utils' 6export * from './utils'
6export * from './plugins-manager' 7export * 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
index 000000000..c2b3f9035
--- /dev/null
+++ b/client/src/root-helpers/local-storage-utils.ts
@@ -0,0 +1,10 @@
1function getBoolOrDefault (value: string, defaultValue: boolean) {
2 if (value === 'true') return true
3 if (value === 'false') return false
4
5 return defaultValue
6}
7
8export {
9 getBoolOrDefault
10}
diff --git a/client/src/root-helpers/users/index.ts b/client/src/root-helpers/users/index.ts
index 8fbaca9e3..2b11d0b7e 100644
--- a/client/src/root-helpers/users/index.ts
+++ b/client/src/root-helpers/users/index.ts
@@ -1,3 +1,2 @@
1export * from './user-local-storage-keys' 1export * from './user-local-storage-keys'
2export * from './user-local-storage-manager'
3export * from './user-tokens' 2export * from './user-tokens'
diff --git a/client/src/root-helpers/users/user-local-storage-keys.ts b/client/src/root-helpers/users/user-local-storage-keys.ts
index 5f915899c..c3934ae3c 100644
--- a/client/src/root-helpers/users/user-local-storage-keys.ts
+++ b/client/src/root-helpers/users/user-local-storage-keys.ts
@@ -1,15 +1,25 @@
1export const UserLocalStorageKeys = { 1export const UserLocalStorageKeys = {
2 ID: 'id', 2 ID: 'id',
3 USERNAME: 'username',
3 ROLE: 'role', 4 ROLE: 'role',
4 EMAIL: 'email', 5 EMAIL: 'email',
6
5 VIDEOS_HISTORY_ENABLED: 'videos-history-enabled', 7 VIDEOS_HISTORY_ENABLED: 'videos-history-enabled',
6 USERNAME: 'username',
7 NSFW_POLICY: 'nsfw_policy', 8 NSFW_POLICY: 'nsfw_policy',
8 WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled', 9 P2P_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled',
10
9 AUTO_PLAY_VIDEO: 'auto_play_video', 11 AUTO_PLAY_VIDEO: 'auto_play_video',
10 SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO: 'auto_play_next_video', 12 AUTO_PLAY_NEXT_VIDEO: 'auto_play_next_video',
11 AUTO_PLAY_VIDEO_PLAYLIST: 'auto_play_video_playlist', 13 AUTO_PLAY_VIDEO_PLAYLIST: 'auto_play_video_playlist',
14
12 THEME: 'theme', 15 THEME: 'theme',
13 LAST_ACTIVE_THEME: 'last_active_theme', 16 LAST_ACTIVE_THEME: 'last_active_theme',
17
14 VIDEO_LANGUAGES: 'video_languages' 18 VIDEO_LANGUAGES: 'video_languages'
15} 19}
20
21export const UserTokenLocalStorageKeys = {
22 ACCESS_TOKEN: 'access_token',
23 REFRESH_TOKEN: 'refresh_token',
24 TOKEN_TYPE: 'token_type'
25}
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
index c75cea127..000000000
--- a/client/src/root-helpers/users/user-local-storage-manager.ts
+++ /dev/null
@@ -1,55 +0,0 @@
1import { NSFWPolicyType, UserRole } from '@shared/models'
2import { peertubeLocalStorage } from '../peertube-web-storage'
3import { UserLocalStorageKeys } from './user-local-storage-keys'
4
5function getUserInfoFromLocalStorage () {
6 const usernameLocalStorage = peertubeLocalStorage.getItem(UserLocalStorageKeys.USERNAME)
7
8 if (!usernameLocalStorage) return undefined
9
10 return {
11 id: parseInt(peertubeLocalStorage.getItem(UserLocalStorageKeys.ID), 10),
12 username: peertubeLocalStorage.getItem(UserLocalStorageKeys.USERNAME),
13 email: peertubeLocalStorage.getItem(UserLocalStorageKeys.EMAIL),
14 role: parseInt(peertubeLocalStorage.getItem(UserLocalStorageKeys.ROLE), 10) as UserRole,
15 nsfwPolicy: peertubeLocalStorage.getItem(UserLocalStorageKeys.NSFW_POLICY) as NSFWPolicyType,
16 webTorrentEnabled: peertubeLocalStorage.getItem(UserLocalStorageKeys.WEBTORRENT_ENABLED) === 'true',
17 autoPlayVideo: peertubeLocalStorage.getItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO) === 'true',
18 videosHistoryEnabled: peertubeLocalStorage.getItem(UserLocalStorageKeys.VIDEOS_HISTORY_ENABLED) === 'true'
19 }
20}
21
22function flushUserInfoFromLocalStorage () {
23 peertubeLocalStorage.removeItem(UserLocalStorageKeys.ID)
24 peertubeLocalStorage.removeItem(UserLocalStorageKeys.USERNAME)
25 peertubeLocalStorage.removeItem(UserLocalStorageKeys.EMAIL)
26 peertubeLocalStorage.removeItem(UserLocalStorageKeys.ROLE)
27 peertubeLocalStorage.removeItem(UserLocalStorageKeys.NSFW_POLICY)
28 peertubeLocalStorage.removeItem(UserLocalStorageKeys.WEBTORRENT_ENABLED)
29 peertubeLocalStorage.removeItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO)
30 peertubeLocalStorage.removeItem(UserLocalStorageKeys.VIDEOS_HISTORY_ENABLED)
31}
32
33function saveUserInfoIntoLocalStorage (info: {
34 id: number
35 username: string
36 email: string
37 role: UserRole
38 nsfwPolicy: NSFWPolicyType
39 webTorrentEnabled: boolean
40 autoPlayVideo: boolean
41}) {
42 peertubeLocalStorage.setItem(UserLocalStorageKeys.ID, info.id.toString())
43 peertubeLocalStorage.setItem(UserLocalStorageKeys.USERNAME, info.username)
44 peertubeLocalStorage.setItem(UserLocalStorageKeys.EMAIL, info.email)
45 peertubeLocalStorage.setItem(UserLocalStorageKeys.ROLE, info.role.toString())
46 peertubeLocalStorage.setItem(UserLocalStorageKeys.NSFW_POLICY, info.nsfwPolicy.toString())
47 peertubeLocalStorage.setItem(UserLocalStorageKeys.WEBTORRENT_ENABLED, JSON.stringify(info.webTorrentEnabled))
48 peertubeLocalStorage.setItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO, JSON.stringify(info.autoPlayVideo))
49}
50
51export {
52 getUserInfoFromLocalStorage,
53 saveUserInfoIntoLocalStorage,
54 flushUserInfoFromLocalStorage
55}
diff --git a/client/src/root-helpers/users/user-tokens.ts b/client/src/root-helpers/users/user-tokens.ts
index d42e1c8f3..a6d614cb7 100644
--- a/client/src/root-helpers/users/user-tokens.ts
+++ b/client/src/root-helpers/users/user-tokens.ts
@@ -1,46 +1,11 @@
1import { peertubeLocalStorage } from '../peertube-web-storage' 1import { UserTokenLocalStorageKeys } from './user-local-storage-keys'
2
3export type TokenOptions = {
4 accessToken: string
5 refreshToken: string
6 tokenType: string
7}
8
9// Private class only used by User
10export class Tokens {
11 private static KEYS = {
12 ACCESS_TOKEN: 'access_token',
13 REFRESH_TOKEN: 'refresh_token',
14 TOKEN_TYPE: 'token_type'
15 }
16 2
3export class UserTokens {
17 accessToken: string 4 accessToken: string
18 refreshToken: string 5 refreshToken: string
19 tokenType: string 6 tokenType: string
20 7
21 static load () { 8 constructor (hash?: Partial<UserTokens>) {
22 const accessTokenLocalStorage = peertubeLocalStorage.getItem(this.KEYS.ACCESS_TOKEN)
23 const refreshTokenLocalStorage = peertubeLocalStorage.getItem(this.KEYS.REFRESH_TOKEN)
24 const tokenTypeLocalStorage = peertubeLocalStorage.getItem(this.KEYS.TOKEN_TYPE)
25
26 if (accessTokenLocalStorage && refreshTokenLocalStorage && tokenTypeLocalStorage) {
27 return new Tokens({
28 accessToken: accessTokenLocalStorage,
29 refreshToken: refreshTokenLocalStorage,
30 tokenType: tokenTypeLocalStorage
31 })
32 }
33
34 return null
35 }
36
37 static flush () {
38 peertubeLocalStorage.removeItem(this.KEYS.ACCESS_TOKEN)
39 peertubeLocalStorage.removeItem(this.KEYS.REFRESH_TOKEN)
40 peertubeLocalStorage.removeItem(this.KEYS.TOKEN_TYPE)
41 }
42
43 constructor (hash?: TokenOptions) {
44 if (hash) { 9 if (hash) {
45 this.accessToken = hash.accessToken 10 this.accessToken = hash.accessToken
46 this.refreshToken = hash.refreshToken 11 this.refreshToken = hash.refreshToken
@@ -53,9 +18,29 @@ export class Tokens {
53 } 18 }
54 } 19 }
55 20
56 save () { 21 static getUserTokens (localStorage: Pick<Storage, 'getItem'>) {
57 peertubeLocalStorage.setItem(Tokens.KEYS.ACCESS_TOKEN, this.accessToken) 22 const accessTokenLocalStorage = localStorage.getItem(UserTokenLocalStorageKeys.ACCESS_TOKEN)
58 peertubeLocalStorage.setItem(Tokens.KEYS.REFRESH_TOKEN, this.refreshToken) 23 const refreshTokenLocalStorage = localStorage.getItem(UserTokenLocalStorageKeys.REFRESH_TOKEN)
59 peertubeLocalStorage.setItem(Tokens.KEYS.TOKEN_TYPE, this.tokenType) 24 const tokenTypeLocalStorage = localStorage.getItem(UserTokenLocalStorageKeys.TOKEN_TYPE)
25
26 if (!accessTokenLocalStorage || !refreshTokenLocalStorage || !tokenTypeLocalStorage) return null
27
28 return new UserTokens({
29 accessToken: accessTokenLocalStorage,
30 refreshToken: refreshTokenLocalStorage,
31 tokenType: tokenTypeLocalStorage
32 })
33 }
34
35 static saveToLocalStorage (localStorage: Pick<Storage, 'setItem'>, tokens: UserTokens) {
36 localStorage.setItem(UserTokenLocalStorageKeys.ACCESS_TOKEN, tokens.accessToken)
37 localStorage.setItem(UserTokenLocalStorageKeys.REFRESH_TOKEN, tokens.refreshToken)
38 localStorage.setItem(UserTokenLocalStorageKeys.TOKEN_TYPE, tokens.tokenType)
39 }
40
41 static flushLocalStorage (localStorage: Pick<Storage, 'removeItem'>) {
42 localStorage.removeItem(UserTokenLocalStorageKeys.ACCESS_TOKEN)
43 localStorage.removeItem(UserTokenLocalStorageKeys.REFRESH_TOKEN)
44 localStorage.removeItem(UserTokenLocalStorageKeys.TOKEN_TYPE)
60 } 45 }
61} 46}
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index 874be580d..94f1096b7 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -7,6 +7,7 @@ import {
7 OAuth2ErrorCode, 7 OAuth2ErrorCode,
8 ResultList, 8 ResultList,
9 UserRefreshToken, 9 UserRefreshToken,
10 Video,
10 VideoCaption, 11 VideoCaption,
11 VideoDetails, 12 VideoDetails,
12 VideoPlaylist, 13 VideoPlaylist,
@@ -16,9 +17,11 @@ import {
16import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager' 17import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager'
17import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' 18import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
18import { TranslationsManager } from '../../assets/player/translations-manager' 19import { TranslationsManager } from '../../assets/player/translations-manager'
20import { isP2PEnabled } from '../../assets/player/utils'
21import { getBoolOrDefault } from '../../root-helpers/local-storage-utils'
19import { peertubeLocalStorage } from '../../root-helpers/peertube-web-storage' 22import { peertubeLocalStorage } from '../../root-helpers/peertube-web-storage'
20import { PluginsManager } from '../../root-helpers/plugins-manager' 23import { PluginsManager } from '../../root-helpers/plugins-manager'
21import { Tokens } from '../../root-helpers/users' 24import { UserLocalStorageKeys, UserTokens } from '../../root-helpers/users'
22import { objectToUrlEncoded } from '../../root-helpers/utils' 25import { objectToUrlEncoded } from '../../root-helpers/utils'
23import { RegisterClientHelpers } from '../../types/register-client-option.model' 26import { RegisterClientHelpers } from '../../types/register-client-option.model'
24import { PeerTubeEmbedApi } from './embed-api' 27import { PeerTubeEmbedApi } from './embed-api'
@@ -48,7 +51,7 @@ export class PeerTubeEmbed {
48 mode: PlayerMode 51 mode: PlayerMode
49 scope = 'peertube' 52 scope = 'peertube'
50 53
51 userTokens: Tokens 54 userTokens: UserTokens
52 headers = new Headers() 55 headers = new Headers()
53 LOCAL_STORAGE_OAUTH_CLIENT_KEYS = { 56 LOCAL_STORAGE_OAUTH_CLIENT_KEYS = {
54 CLIENT_ID: 'client_id', 57 CLIENT_ID: 'client_id',
@@ -118,7 +121,7 @@ export class PeerTubeEmbed {
118 return res.json() 121 return res.json()
119 }).then((obj: UserRefreshToken & { code?: OAuth2ErrorCode }) => { 122 }).then((obj: UserRefreshToken & { code?: OAuth2ErrorCode }) => {
120 if (!obj || obj.code === OAuth2ErrorCode.INVALID_GRANT) { 123 if (!obj || obj.code === OAuth2ErrorCode.INVALID_GRANT) {
121 Tokens.flush() 124 UserTokens.flushLocalStorage(peertubeLocalStorage)
122 this.removeTokensFromHeaders() 125 this.removeTokensFromHeaders()
123 126
124 return resolve() 127 return resolve()
@@ -126,7 +129,7 @@ export class PeerTubeEmbed {
126 129
127 this.userTokens.accessToken = obj.access_token 130 this.userTokens.accessToken = obj.access_token
128 this.userTokens.refreshToken = obj.refresh_token 131 this.userTokens.refreshToken = obj.refresh_token
129 this.userTokens.save() 132 UserTokens.saveToLocalStorage(peertubeLocalStorage, this.userTokens)
130 133
131 this.setHeadersFromTokens() 134 this.setHeadersFromTokens()
132 135
@@ -138,7 +141,7 @@ export class PeerTubeEmbed {
138 141
139 return refreshingTokenPromise 142 return refreshingTokenPromise
140 .catch(() => { 143 .catch(() => {
141 Tokens.flush() 144 UserTokens.flushLocalStorage(peertubeLocalStorage)
142 145
143 this.removeTokensFromHeaders() 146 this.removeTokensFromHeaders()
144 }).then(() => fetch(url, { 147 }).then(() => fetch(url, {
@@ -258,7 +261,7 @@ export class PeerTubeEmbed {
258 } 261 }
259 262
260 async init () { 263 async init () {
261 this.userTokens = Tokens.load() 264 this.userTokens = UserTokens.getUserTokens(peertubeLocalStorage)
262 await this.initCore() 265 await this.initCore()
263 } 266 }
264 267
@@ -515,6 +518,8 @@ export class PeerTubeEmbed {
515 muted: this.muted, 518 muted: this.muted,
516 loop: this.loop, 519 loop: this.loop,
517 520
521 p2pEnabled: this.isP2PEnabled(videoInfo),
522
518 captions: videoCaptions.length !== 0, 523 captions: videoCaptions.length !== 0,
519 subtitle: this.subtitle, 524 subtitle: this.subtitle,
520 525
@@ -669,7 +674,7 @@ export class PeerTubeEmbed {
669 674
670 const title = this.title ? videoInfo.name : undefined 675 const title = this.title ? videoInfo.name : undefined
671 676
672 const description = this.warningTitle && (!videoInfo.isLocal || this.config.tracker.enabled) 677 const description = this.warningTitle && this.isP2PEnabled(videoInfo)
673 ? '<span class="text">' + peertubeTranslate('Watching this video may reveal your IP address to others.') + '</span>' 678 ? '<span class="text">' + peertubeTranslate('Watching this video may reveal your IP address to others.') + '</span>'
674 : undefined 679 : undefined
675 680
@@ -784,6 +789,15 @@ export class PeerTubeEmbed {
784 translate: (value: string) => Promise.resolve(peertubeTranslate(value, translations)) 789 translate: (value: string) => Promise.resolve(peertubeTranslate(value, translations))
785 } 790 }
786 } 791 }
792
793 private isP2PEnabled (video: Video) {
794 const userP2PEnabled = getBoolOrDefault(
795 peertubeLocalStorage.getItem(UserLocalStorageKeys.P2P_ENABLED),
796 this.config.defaults.p2p.enabled
797 )
798
799 return isP2PEnabled(video, this.config, userP2PEnabled)
800 }
787} 801}
788 802
789PeerTubeEmbed.main() 803PeerTubeEmbed.main()
diff --git a/config/default.yaml b/config/default.yaml
index fbe0dbbfb..421c19569 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -92,6 +92,11 @@ defaults:
92 # No licence by default 92 # No licence by default
93 licence: null 93 licence: null
94 94
95 p2p:
96 # Enable P2P by default
97 # Can be enabled/disabled by anonymous users and logged in users
98 enabled: true
99
95# From the project root directory 100# From the project root directory
96storage: 101storage:
97 tmp: 'storage/tmp/' # Use to download data (imports etc), store uploaded files before and during processing... 102 tmp: 'storage/tmp/' # Use to download data (imports etc), store uploaded files before and during processing...
@@ -216,7 +221,7 @@ security:
216 enabled: true 221 enabled: true
217 222
218tracker: 223tracker:
219 # If you disable the tracker, you disable the P2P aspect of PeerTube 224 # If you disable the tracker, you disable the P2P on your PeerTube instance
220 enabled: true 225 enabled: true
221 # Only handle requests on your videos 226 # Only handle requests on your videos
222 # If you set this to false it means you have a public tracker 227 # If you set this to false it means you have a public tracker
diff --git a/config/production.yaml.example b/config/production.yaml.example
index 6363a5179..13219fd5d 100644
--- a/config/production.yaml.example
+++ b/config/production.yaml.example
@@ -90,6 +90,11 @@ defaults:
90 # No licence by default 90 # No licence by default
91 licence: null 91 licence: null
92 92
93 p2p:
94 # Enable P2P by default
95 # Can be enabled/disabled by anonymous users and logged in users
96 enabled: true
97
93# From the project root directory 98# From the project root directory
94storage: 99storage:
95 tmp: '/var/www/peertube/storage/tmp/' # Use to download data (imports etc), store uploaded files before and during processing... 100 tmp: '/var/www/peertube/storage/tmp/' # Use to download data (imports etc), store uploaded files before and during processing...
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index 11d3525e4..f3b4508d9 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -183,6 +183,7 @@ async function createUser (req: express.Request, res: express.Response) {
183 password: body.password, 183 password: body.password,
184 email: body.email, 184 email: body.email,
185 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 185 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
186 p2pEnabled: CONFIG.DEFAULTS.P2P.ENABLED,
186 autoPlayVideo: true, 187 autoPlayVideo: true,
187 role: body.role, 188 role: body.role,
188 videoQuota: body.videoQuota, 189 videoQuota: body.videoQuota,
@@ -232,6 +233,7 @@ async function registerUser (req: express.Request, res: express.Response) {
232 password: body.password, 233 password: body.password,
233 email: body.email, 234 email: body.email,
234 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 235 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
236 p2pEnabled: CONFIG.DEFAULTS.P2P.ENABLED,
235 autoPlayVideo: true, 237 autoPlayVideo: true,
236 role: UserRole.USER, 238 role: UserRole.USER,
237 videoQuota: CONFIG.USER.VIDEO_QUOTA, 239 videoQuota: CONFIG.USER.VIDEO_QUOTA,
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index 6bacdbbb6..1125771d4 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -197,7 +197,7 @@ async function updateMe (req: express.Request, res: express.Response) {
197 const keysToUpdate: (keyof UserUpdateMe & keyof AttributesOnly<UserModel>)[] = [ 197 const keysToUpdate: (keyof UserUpdateMe & keyof AttributesOnly<UserModel>)[] = [
198 'password', 198 'password',
199 'nsfwPolicy', 199 'nsfwPolicy',
200 'webTorrentEnabled', 200 'p2pEnabled',
201 'autoPlayVideo', 201 'autoPlayVideo',
202 'autoPlayNextVideo', 202 'autoPlayNextVideo',
203 'autoPlayNextVideoPlaylist', 203 'autoPlayNextVideoPlaylist',
@@ -213,6 +213,12 @@ async function updateMe (req: express.Request, res: express.Response) {
213 if (body[key] !== undefined) user.set(key, body[key]) 213 if (body[key] !== undefined) user.set(key, body[key])
214 } 214 }
215 215
216 if (body.p2pEnabled !== undefined) {
217 user.set('p2pEnabled', body.p2pEnabled)
218 } else if (body.webTorrentEnabled !== undefined) { // FIXME: deprecated in 4.1
219 user.set('p2pEnabled', body.webTorrentEnabled)
220 }
221
216 if (body.email !== undefined) { 222 if (body.email !== undefined) {
217 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { 223 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
218 user.pendingEmail = body.email 224 user.pendingEmail = body.email
diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts
index f52c60b60..badf171d2 100644
--- a/server/helpers/custom-validators/users.ts
+++ b/server/helpers/custom-validators/users.ts
@@ -49,7 +49,7 @@ function isUserNSFWPolicyValid (value: any) {
49 return exists(value) && nsfwPolicies.includes(value) 49 return exists(value) && nsfwPolicies.includes(value)
50} 50}
51 51
52function isUserWebTorrentEnabledValid (value: any) { 52function isUserP2PEnabledValid (value: any) {
53 return isBooleanValid(value) 53 return isBooleanValid(value)
54} 54}
55 55
@@ -109,7 +109,7 @@ export {
109 isUserAdminFlagsValid, 109 isUserAdminFlagsValid,
110 isUserEmailVerifiedValid, 110 isUserEmailVerifiedValid,
111 isUserNSFWPolicyValid, 111 isUserNSFWPolicyValid,
112 isUserWebTorrentEnabledValid, 112 isUserP2PEnabledValid,
113 isUserAutoPlayVideoValid, 113 isUserAutoPlayVideoValid,
114 isUserAutoPlayNextVideoValid, 114 isUserAutoPlayNextVideoValid,
115 isUserAutoPlayNextVideoPlaylistValid, 115 isUserAutoPlayNextVideoPlaylistValid,
diff --git a/server/initializers/config.ts b/server/initializers/config.ts
index e3e8c426e..a6ea6d888 100644
--- a/server/initializers/config.ts
+++ b/server/initializers/config.ts
@@ -78,6 +78,9 @@ const CONFIG = {
78 COMMENTS_ENABLED: config.get<boolean>('defaults.publish.comments_enabled'), 78 COMMENTS_ENABLED: config.get<boolean>('defaults.publish.comments_enabled'),
79 PRIVACY: config.get<VideoPrivacy>('defaults.publish.privacy'), 79 PRIVACY: config.get<VideoPrivacy>('defaults.publish.privacy'),
80 LICENCE: config.get<number>('defaults.publish.licence') 80 LICENCE: config.get<number>('defaults.publish.licence')
81 },
82 P2P: {
83 ENABLED: config.get<boolean>('defaults.p2p.enabled')
81 } 84 }
82 }, 85 },
83 86
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 026c715c2..258ccdb51 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -25,7 +25,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
25 25
26// --------------------------------------------------------------------------- 26// ---------------------------------------------------------------------------
27 27
28const LAST_MIGRATION_VERSION = 670 28const LAST_MIGRATION_VERSION = 675
29 29
30// --------------------------------------------------------------------------- 30// ---------------------------------------------------------------------------
31 31
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts
index 75daeb5d8..19adaf177 100644
--- a/server/initializers/installer.ts
+++ b/server/initializers/installer.ts
@@ -144,6 +144,7 @@ async function createOAuthAdminIfNotExist () {
144 role, 144 role,
145 verified: true, 145 verified: true,
146 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 146 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
147 p2pEnabled: CONFIG.DEFAULTS.P2P.ENABLED,
147 videoQuota: -1, 148 videoQuota: -1,
148 videoQuotaDaily: -1 149 videoQuotaDaily: -1
149 } 150 }
diff --git a/server/initializers/migrations/0675-p2p-enabled.ts b/server/initializers/migrations/0675-p2p-enabled.ts
new file mode 100644
index 000000000..b4f53381e
--- /dev/null
+++ b/server/initializers/migrations/0675-p2p-enabled.ts
@@ -0,0 +1,21 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize
7 db: any
8}): Promise<void> {
9 await utils.queryInterface.renameColumn('user', 'webTorrentEnabled', 'p2pEnabled')
10
11 await utils.sequelize.query('ALTER TABLE "user" ALTER COLUMN "p2pEnabled" DROP DEFAULT')
12}
13
14function down (options) {
15 throw new Error('Not implemented.')
16}
17
18export {
19 up,
20 down
21}
diff --git a/server/lib/auth/oauth-model.ts b/server/lib/auth/oauth-model.ts
index f2ef0a78a..754bee36d 100644
--- a/server/lib/auth/oauth-model.ts
+++ b/server/lib/auth/oauth-model.ts
@@ -226,6 +226,7 @@ async function createUserFromExternal (pluginAuth: string, options: {
226 password: null, 226 password: null,
227 email: options.email, 227 email: options.email,
228 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 228 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
229 p2pEnabled: CONFIG.DEFAULTS.P2P.ENABLED,
229 autoPlayVideo: true, 230 autoPlayVideo: true,
230 role: options.role, 231 role: options.role,
231 videoQuota: CONFIG.USER.VIDEO_QUOTA, 232 videoQuota: CONFIG.USER.VIDEO_QUOTA,
diff --git a/server/lib/server-config-manager.ts b/server/lib/server-config-manager.ts
index 8aea4cd6d..d759f85e1 100644
--- a/server/lib/server-config-manager.ts
+++ b/server/lib/server-config-manager.ts
@@ -61,6 +61,9 @@ class ServerConfigManager {
61 commentsEnabled: CONFIG.DEFAULTS.PUBLISH.COMMENTS_ENABLED, 61 commentsEnabled: CONFIG.DEFAULTS.PUBLISH.COMMENTS_ENABLED,
62 privacy: CONFIG.DEFAULTS.PUBLISH.PRIVACY, 62 privacy: CONFIG.DEFAULTS.PUBLISH.PRIVACY,
63 licence: CONFIG.DEFAULTS.PUBLISH.LICENCE 63 licence: CONFIG.DEFAULTS.PUBLISH.LICENCE
64 },
65 p2p: {
66 enabled: CONFIG.DEFAULTS.P2P.ENABLED
64 } 67 }
65 }, 68 },
66 69
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index 7a6b2ce57..bc6007c6d 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -15,6 +15,7 @@ import {
15 isUserDisplayNameValid, 15 isUserDisplayNameValid,
16 isUserNoModal, 16 isUserNoModal,
17 isUserNSFWPolicyValid, 17 isUserNSFWPolicyValid,
18 isUserP2PEnabledValid,
18 isUserPasswordValid, 19 isUserPasswordValid,
19 isUserPasswordValidOrEmpty, 20 isUserPasswordValidOrEmpty,
20 isUserRoleValid, 21 isUserRoleValid,
@@ -239,6 +240,9 @@ const usersUpdateMeValidator = [
239 body('autoPlayVideo') 240 body('autoPlayVideo')
240 .optional() 241 .optional()
241 .custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'), 242 .custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
243 body('p2pEnabled')
244 .optional()
245 .custom(isUserP2PEnabledValid).withMessage('Should have a valid p2p enabled boolean'),
242 body('videoLanguages') 246 body('videoLanguages')
243 .optional() 247 .optional()
244 .custom(isUserVideoLanguages).withMessage('Should have a valid video languages attribute'), 248 .custom(isUserVideoLanguages).withMessage('Should have a valid video languages attribute'),
diff --git a/server/models/user/user.ts b/server/models/user/user.ts
index b56f37e55..88c3ff528 100644
--- a/server/models/user/user.ts
+++ b/server/models/user/user.ts
@@ -55,7 +55,7 @@ import {
55 isUserVideoQuotaDailyValid, 55 isUserVideoQuotaDailyValid,
56 isUserVideoQuotaValid, 56 isUserVideoQuotaValid,
57 isUserVideosHistoryEnabledValid, 57 isUserVideosHistoryEnabledValid,
58 isUserWebTorrentEnabledValid 58 isUserP2PEnabledValid
59} from '../../helpers/custom-validators/users' 59} from '../../helpers/custom-validators/users'
60import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' 60import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
61import { DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants' 61import { DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants'
@@ -267,10 +267,9 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
267 nsfwPolicy: NSFWPolicyType 267 nsfwPolicy: NSFWPolicyType
268 268
269 @AllowNull(false) 269 @AllowNull(false)
270 @Default(true) 270 @Is('p2pEnabled', value => throwIfNotValid(value, isUserP2PEnabledValid, 'P2P enabled'))
271 @Is('UserWebTorrentEnabled', value => throwIfNotValid(value, isUserWebTorrentEnabledValid, 'WebTorrent enabled'))
272 @Column 271 @Column
273 webTorrentEnabled: boolean 272 p2pEnabled: boolean
274 273
275 @AllowNull(false) 274 @AllowNull(false)
276 @Default(true) 275 @Default(true)
@@ -892,7 +891,11 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
892 emailVerified: this.emailVerified, 891 emailVerified: this.emailVerified,
893 892
894 nsfwPolicy: this.nsfwPolicy, 893 nsfwPolicy: this.nsfwPolicy,
895 webTorrentEnabled: this.webTorrentEnabled, 894
895 // FIXME: deprecated in 4.1
896 webTorrentEnabled: this.p2pEnabled,
897 p2pEnabled: this.p2pEnabled,
898
896 videosHistoryEnabled: this.videosHistoryEnabled, 899 videosHistoryEnabled: this.videosHistoryEnabled,
897 autoPlayVideo: this.autoPlayVideo, 900 autoPlayVideo: this.autoPlayVideo,
898 autoPlayNextVideo: this.autoPlayNextVideo, 901 autoPlayNextVideo: this.autoPlayNextVideo,
diff --git a/server/tests/api/server/config-defaults.ts b/server/tests/api/server/config-defaults.ts
index 3dff7bfb7..340d4b44b 100644
--- a/server/tests/api/server/config-defaults.ts
+++ b/server/tests/api/server/config-defaults.ts
@@ -21,18 +21,7 @@ describe('Test config defaults', function () {
21 before(async function () { 21 before(async function () {
22 this.timeout(30000) 22 this.timeout(30000)
23 23
24 const overrideConfig = { 24 server = await createSingleServer(1)
25 defaults: {
26 publish: {
27 comments_enabled: false,
28 download_enabled: false,
29 privacy: VideoPrivacy.INTERNAL,
30 licence: 4
31 }
32 }
33 }
34
35 server = await createSingleServer(1, overrideConfig)
36 await setAccessTokensToServers([ server ]) 25 await setAccessTokensToServers([ server ])
37 await setDefaultVideoChannel([ server ]) 26 await setDefaultVideoChannel([ server ])
38 27
@@ -40,6 +29,23 @@ describe('Test config defaults', function () {
40 }) 29 })
41 30
42 describe('Default publish values', function () { 31 describe('Default publish values', function () {
32
33 before(async function () {
34 const overrideConfig = {
35 defaults: {
36 publish: {
37 comments_enabled: false,
38 download_enabled: false,
39 privacy: VideoPrivacy.INTERNAL,
40 licence: 4
41 }
42 }
43 }
44
45 await server.kill()
46 await server.run(overrideConfig)
47 })
48
43 const attributes = { 49 const attributes = {
44 name: 'video', 50 name: 'video',
45 downloadEnabled: undefined, 51 downloadEnabled: undefined,
@@ -117,6 +123,45 @@ describe('Test config defaults', function () {
117 }) 123 })
118 }) 124 })
119 125
126 describe('Default P2P values', function () {
127
128 before(async function () {
129 const overrideConfig = {
130 defaults: {
131 p2p: {
132 enabled: false
133 }
134 }
135 }
136
137 await server.kill()
138 await server.run(overrideConfig)
139 })
140
141 it('Should not have P2P enabled', async function () {
142 const config = await server.config.getConfig()
143
144 expect(config.defaults.p2p.enabled).to.be.false
145 })
146
147 it('Should create a user with this default setting', async function () {
148 await server.users.create({ username: 'user_p2p_1' })
149 const userToken = await server.login.getAccessToken('user_p2p_1')
150
151 const { p2pEnabled } = await server.users.getMyInfo({ token: userToken })
152 expect(p2pEnabled).to.be.false
153 })
154
155 it('Should register a user with this default setting', async function () {
156 await server.users.register({ username: 'user_p2p_2' })
157
158 const userToken = await server.login.getAccessToken('user_p2p_2')
159
160 const { p2pEnabled } = await server.users.getMyInfo({ token: userToken })
161 expect(p2pEnabled).to.be.false
162 })
163 })
164
120 after(async function () { 165 after(async function () {
121 await cleanupTests([ server ]) 166 await cleanupTests([ server ])
122 }) 167 })
diff --git a/server/tests/api/server/follows.ts b/server/tests/api/server/follows.ts
index 748f4cd35..c132d99ea 100644
--- a/server/tests/api/server/follows.ts
+++ b/server/tests/api/server/follows.ts
@@ -292,7 +292,7 @@ describe('Test follows', function () {
292 }) 292 })
293 293
294 it('Should upload a video on server 2 and 3 and propagate only the video of server 2', async function () { 294 it('Should upload a video on server 2 and 3 and propagate only the video of server 2', async function () {
295 this.timeout(60000) 295 this.timeout(120000)
296 296
297 await servers[1].videos.upload({ attributes: { name: 'server2' } }) 297 await servers[1].videos.upload({ attributes: { name: 'server2' } })
298 await servers[2].videos.upload({ attributes: { name: 'server3' } }) 298 await servers[2].videos.upload({ attributes: { name: 'server3' } })
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts
index 6c41e7d56..f00cbab5a 100644
--- a/server/tests/api/users/users.ts
+++ b/server/tests/api/users/users.ts
@@ -559,6 +559,28 @@ describe('Test users', function () {
559 expect(user.autoPlayNextVideo).to.be.true 559 expect(user.autoPlayNextVideo).to.be.true
560 }) 560 })
561 561
562 it('Should be able to change the p2p attribute', async function () {
563 {
564 await server.users.updateMe({
565 token: userToken,
566 webTorrentEnabled: false
567 })
568
569 const user = await server.users.getMyInfo({ token: userToken })
570 expect(user.p2pEnabled).to.be.false
571 }
572
573 {
574 await server.users.updateMe({
575 token: userToken,
576 p2pEnabled: true
577 })
578
579 const user = await server.users.getMyInfo({ token: userToken })
580 expect(user.p2pEnabled).to.be.true
581 }
582 })
583
562 it('Should be able to change the email attribute', async function () { 584 it('Should be able to change the email attribute', async function () {
563 await server.users.updateMe({ 585 await server.users.updateMe({
564 token: userToken, 586 token: userToken,
diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts
index 9c3dcd6d3..71540e603 100644
--- a/shared/models/server/server-config.model.ts
+++ b/shared/models/server/server-config.model.ts
@@ -55,6 +55,10 @@ export interface ServerConfig {
55 privacy: VideoPrivacy 55 privacy: VideoPrivacy
56 licence: number 56 licence: number
57 } 57 }
58
59 p2p: {
60 enabled: boolean
61 }
58 } 62 }
59 63
60 webadmin: { 64 webadmin: {
diff --git a/shared/models/users/user-update-me.model.ts b/shared/models/users/user-update-me.model.ts
index 6d7df38fb..e664e44b5 100644
--- a/shared/models/users/user-update-me.model.ts
+++ b/shared/models/users/user-update-me.model.ts
@@ -5,7 +5,10 @@ export interface UserUpdateMe {
5 description?: string 5 description?: string
6 nsfwPolicy?: NSFWPolicyType 6 nsfwPolicy?: NSFWPolicyType
7 7
8 // FIXME: deprecated in favour of p2pEnabled in 4.1
8 webTorrentEnabled?: boolean 9 webTorrentEnabled?: boolean
10 p2pEnabled?: boolean
11
9 autoPlayVideo?: boolean 12 autoPlayVideo?: boolean
10 autoPlayNextVideo?: boolean 13 autoPlayNextVideo?: boolean
11 autoPlayNextVideoPlaylist?: boolean 14 autoPlayNextVideoPlaylist?: boolean
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts
index 78870c556..63c5c8a92 100644
--- a/shared/models/users/user.model.ts
+++ b/shared/models/users/user.model.ts
@@ -20,7 +20,11 @@ export interface User {
20 autoPlayVideo: boolean 20 autoPlayVideo: boolean
21 autoPlayNextVideo: boolean 21 autoPlayNextVideo: boolean
22 autoPlayNextVideoPlaylist: boolean 22 autoPlayNextVideoPlaylist: boolean
23
24 // @deprecated in favour of p2pEnabled
23 webTorrentEnabled: boolean 25 webTorrentEnabled: boolean
26 p2pEnabled: boolean
27
24 videosHistoryEnabled: boolean 28 videosHistoryEnabled: boolean
25 videoLanguages: string[] 29 videoLanguages: string[]
26 30
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml
index 1d5581072..7b6e8a1e4 100644
--- a/support/doc/api/openapi.yaml
+++ b/support/doc/api/openapi.yaml
@@ -6679,7 +6679,7 @@ components:
6679 type: integer 6679 type: integer
6680 description: The user daily video quota in bytes 6680 description: The user daily video quota in bytes
6681 example: -1 6681 example: -1
6682 webtorrentEnabled: 6682 p2pEnabled:
6683 type: boolean 6683 type: boolean
6684 description: Enable P2P in the player 6684 description: Enable P2P in the player
6685 UserWithStats: 6685 UserWithStats:
@@ -6780,7 +6780,7 @@ components:
6780 - 'true' 6780 - 'true'
6781 - 'false' 6781 - 'false'
6782 - both 6782 - both
6783 webTorrentEnabled: 6783 p2pEnabled:
6784 type: boolean 6784 type: boolean
6785 description: whether to enable P2P in the player or not 6785 description: whether to enable P2P in the player or not
6786 autoPlayVideo: 6786 autoPlayVideo: