diff options
Diffstat (limited to 'client')
39 files changed, 632 insertions, 390 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 @@ | |||
1 | import { getCheckbox } from '../utils' | ||
2 | |||
3 | export 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 @@ | |||
1 | import { go } from '../utils' | 1 | import { getCheckbox, go } from '../utils' |
2 | 2 | ||
3 | export class MyAccountPage { | 3 | export 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 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { clickOnCheckbox } from '../utils' | 2 | import { getCheckbox, selectCustomSelect } from '../utils' |
3 | 3 | ||
4 | export class VideoUploadPage { | 4 | export 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 @@ | |||
1 | import { LoginPage } from '../po/login.po' | 1 | import { LoginPage } from '../po/login.po' |
2 | import { MyAccountPage } from '../po/my-account' | 2 | import { MyAccountPage } from '../po/my-account.po' |
3 | import { PlayerPage } from '../po/player.po' | 3 | import { PlayerPage } from '../po/player.po' |
4 | import { VideoListPage } from '../po/video-list.po' | 4 | import { VideoListPage } from '../po/video-list.po' |
5 | import { VideoUpdatePage } from '../po/video-update.po' | 5 | import { 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 @@ | |||
1 | import { LoginPage } from '../po/login.po' | 1 | import { LoginPage } from '../po/login.po' |
2 | import { VideoUploadPage } from '../po/video-upload.po' | 2 | import { VideoUploadPage } from '../po/video-upload.po' |
3 | import { VideoWatchPage } from '../po/video-watch.po' | 3 | import { VideoWatchPage } from '../po/video-watch.po' |
4 | import { isMobileDevice, isSafari, waitServerUp } from '../utils' | 4 | import { go, isMobileDevice, isSafari, waitServerUp } from '../utils' |
5 | 5 | ||
6 | describe('Custom server defaults', () => { | 6 | describe('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 @@ | |||
1 | import { AnonymousSettingsPage } from '../po/anonymous-settings.po' | ||
2 | import { LoginPage } from '../po/login.po' | ||
3 | import { MyAccountPage } from '../po/my-account.po' | ||
4 | import { VideoUploadPage } from '../po/video-upload.po' | ||
5 | import { VideoWatchPage } from '../po/video-watch.po' | ||
6 | import { go, isMobileDevice, isSafari, waitServerUp } from '../utils' | ||
7 | |||
8 | describe('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 @@ | |||
1 | import { AdminConfigPage } from '../po/admin-config.po' | 1 | import { AdminConfigPage } from '../po/admin-config.po' |
2 | import { LoginPage } from '../po/login.po' | 2 | import { LoginPage } from '../po/login.po' |
3 | import { MyAccountPage } from '../po/my-account' | 3 | import { MyAccountPage } from '../po/my-account.po' |
4 | import { VideoListPage } from '../po/video-list.po' | 4 | import { VideoListPage } from '../po/video-list.po' |
5 | import { VideoSearchPage } from '../po/video-search.po' | 5 | import { VideoSearchPage } from '../po/video-search.po' |
6 | import { VideoUploadPage } from '../po/video-upload.po' | 6 | import { 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 @@ | |||
1 | function clickOnCheckbox (name: string) { | 1 | function getCheckbox (name: string) { |
2 | return $(`my-peertube-checkbox[inputname=${name}] label`).click() | 2 | return $(`my-peertube-checkbox[inputname=${name}] label`) |
3 | } | ||
4 | |||
5 | async 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 | ||
5 | export { | 19 | export { |
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 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | 1 | import { Component, Input, OnInit } from '@angular/core' |
2 | import { ServerService } from '@app/core' | 2 | import { ServerService, User, UserService } from '@app/core' |
3 | import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' | 3 | import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' |
4 | import { HTMLServerConfig, Video } from '@shared/models' | 4 | import { HTMLServerConfig, Video } from '@shared/models' |
5 | import { getStoredP2PEnabled } from '../../../../../assets/player/peertube-player-local-storage' | 5 | import { isP2PEnabled } from '../../../../../assets/player/utils' |
6 | import { 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 @@ | |||
1 | import { Component, EventEmitter, Input, Output } from '@angular/core' | 1 | import { Component, EventEmitter, Input, Output } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { | 3 | import { 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' | ||
12 | import { VideoPlaylist, VideoPlaylistElement, VideoPlaylistService } from '@app/shared/shared-video-playlist' | 4 | import { VideoPlaylist, VideoPlaylistElement, VideoPlaylistService } from '@app/shared/shared-video-playlist' |
13 | import { peertubeLocalStorage, peertubeSessionStorage } from '@root-helpers/peertube-web-storage' | 5 | import { peertubeSessionStorage } from '@root-helpers/peertube-web-storage' |
6 | import { getBoolOrDefault } from '@root-helpers/local-storage-utils' | ||
14 | import { VideoPlaylistPrivacy } from '@shared/models' | 7 | import { 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 | }) |
21 | export class VideoWatchPlaylistComponent { | 14 | export 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 @@ | |||
1 | import { Observable } from 'rxjs' | 1 | import { Observable } from 'rxjs' |
2 | import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core' | 2 | import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core' |
3 | import { AuthService, Notifier, SessionStorageService, User, UserService } from '@app/core' | 3 | import { AuthService, Notifier, User, UserService } from '@app/core' |
4 | import { Video } from '@app/shared/shared-main' | 4 | import { Video } from '@app/shared/shared-main' |
5 | import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature' | 5 | import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature' |
6 | import { VideoPlaylist } from '@app/shared/shared-video-playlist' | 6 | import { VideoPlaylist } from '@app/shared/shared-video-playlist' |
7 | import { UserLocalStorageKeys } from '@root-helpers/users' | ||
8 | import { RecommendationInfo } from './recommendation-info.model' | 7 | import { RecommendationInfo } from './recommendation-info.model' |
9 | import { RecommendedVideosStore } from './recommended-videos.store' | 8 | import { 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 @@ | |||
1 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' | 1 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' |
2 | import { forkJoin, Subscription } from 'rxjs' | 2 | import { forkJoin, Subscription } from 'rxjs' |
3 | import { isP2PEnabled } from 'src/assets/player/utils' | ||
3 | import { PlatformLocation } from '@angular/common' | 4 | import { PlatformLocation } from '@angular/common' |
4 | import { Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' | 5 | import { Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' |
5 | import { ActivatedRoute, Router } from '@angular/router' | 6 | import { 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' |
19 | import { HooksService } from '@app/core/plugins/hooks.service' | 21 | import { 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' |
19 | import { HooksService } from '@app/core/plugins/hooks.service' | 20 | import { HooksService } from '@app/core/plugins/hooks.service' |
20 | import { PluginService } from '@app/core/plugins/plugin.service' | 21 | import { 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 @@ | |||
1 | import { Observable, of } from 'rxjs' | 1 | import { Observable, of } from 'rxjs' |
2 | import { map } from 'rxjs/operators' | 2 | import { map } from 'rxjs/operators' |
3 | import { User } from '@app/core/users/user.model' | 3 | import { User } from '@app/core/users/user.model' |
4 | import { | 4 | import { UserTokens } from '@root-helpers/users' |
5 | flushUserInfoFromLocalStorage, | ||
6 | getUserInfoFromLocalStorage, | ||
7 | saveUserInfoIntoLocalStorage, | ||
8 | TokenOptions, | ||
9 | Tokens | ||
10 | } from '@root-helpers/users' | ||
11 | import { hasUserRight } from '@shared/core-utils/users' | 5 | import { hasUserRight } from '@shared/core-utils/users' |
12 | import { | 6 | import { |
13 | MyUser as ServerMyUserModel, | 7 | MyUser as ServerMyUserModel, |
@@ -19,31 +13,15 @@ import { | |||
19 | } from '@shared/models' | 13 | } from '@shared/models' |
20 | 14 | ||
21 | export class AuthUser extends User implements ServerMyUserModel { | 15 | export 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' | |||
5 | import { Injectable } from '@angular/core' | 5 | import { Injectable } from '@angular/core' |
6 | import { Router } from '@angular/router' | 6 | import { Router } from '@angular/router' |
7 | import { Notifier } from '@app/core/notification/notifier.service' | 7 | import { Notifier } from '@app/core/notification/notifier.service' |
8 | import { objectToUrlEncoded, peertubeLocalStorage } from '@root-helpers/index' | 8 | import { objectToUrlEncoded, peertubeLocalStorage, UserTokens } from '@root-helpers/index' |
9 | import { HttpStatusCode, MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models' | 9 | import { HttpStatusCode, MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models' |
10 | import { environment } from '../../../environments/environment' | 10 | import { environment } from '../../../environments/environment' |
11 | import { RestExtractor } from '../rest/rest-extractor.service' | 11 | import { 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' | |||
30 | import { ScopedTokensService } from './scoped-tokens' | 30 | import { ScopedTokensService } from './scoped-tokens' |
31 | import { ServerService } from './server' | 31 | import { ServerService } from './server' |
32 | import { ThemeService } from './theme' | 32 | import { ThemeService } from './theme' |
33 | import { UserService } from './users' | 33 | import { UserLocalStorageService, UserService } from './users' |
34 | import { LocalStorageService, ScreenService, SessionStorageService } from './wrappers' | 34 | import { 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 @@ | |||
1 | export * from './user-local-storage.service' | ||
1 | export * from './user.model' | 2 | export * from './user.model' |
2 | export * from './user.service' | 3 | export * from './user.service' |
diff --git a/client/src/app/core/users/user-local-storage.service.ts b/client/src/app/core/users/user-local-storage.service.ts new file mode 100644 index 000000000..85da46e0d --- /dev/null +++ b/client/src/app/core/users/user-local-storage.service.ts | |||
@@ -0,0 +1,186 @@ | |||
1 | |||
2 | import { filter, throttleTime } from 'rxjs' | ||
3 | import { Injectable } from '@angular/core' | ||
4 | import { AuthService, AuthStatus } from '@app/core/auth' | ||
5 | import { UserLocalStorageKeys, UserTokens } from '@root-helpers/users' | ||
6 | import { getBoolOrDefault } from '@root-helpers/local-storage-utils' | ||
7 | import { UserRole, UserUpdateMe } from '@shared/models' | ||
8 | import { NSFWPolicyType } from '@shared/models/videos' | ||
9 | import { ServerService } from '../server' | ||
10 | import { LocalStorageService } from '../wrappers/storage.service' | ||
11 | |||
12 | @Injectable() | ||
13 | export 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 @@ | |||
1 | import { SortMeta } from 'primeng/api' | 1 | import { SortMeta } from 'primeng/api' |
2 | import { from, Observable, of } from 'rxjs' | 2 | import { from, Observable, of } from 'rxjs' |
3 | import { catchError, concatMap, filter, first, map, shareReplay, tap, throttleTime, toArray } from 'rxjs/operators' | 3 | import { catchError, concatMap, first, map, shareReplay, tap, toArray } from 'rxjs/operators' |
4 | import { HttpClient, HttpParams } from '@angular/common/http' | 4 | import { HttpClient, HttpParams } from '@angular/common/http' |
5 | import { Injectable } from '@angular/core' | 5 | import { Injectable } from '@angular/core' |
6 | import { AuthService } from '@app/core/auth' | 6 | import { AuthService } from '@app/core/auth' |
7 | import { getBytes } from '@root-helpers/bytes' | 7 | import { getBytes } from '@root-helpers/bytes' |
8 | import { UserLocalStorageKeys } from '@root-helpers/users' | ||
9 | import { | 8 | import { |
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' |
20 | import { ServerService } from '../' | ||
21 | import { environment } from '../../../environments/environment' | 19 | import { environment } from '../../../environments/environment' |
22 | import { RestExtractor, RestPagination, RestService } from '../rest' | 20 | import { RestExtractor, RestPagination, RestService } from '../rest' |
23 | import { LocalStorageService, SessionStorageService } from '../wrappers/storage.service' | 21 | import { UserLocalStorageService } from './' |
24 | import { User } from './user.model' | 22 | import { 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' | |||
4 | import { Injectable } from '@angular/core' | 4 | import { Injectable } from '@angular/core' |
5 | import { ComponentPaginationLight, RestExtractor, RestPagination, RestService } from '@app/core' | 5 | import { ComponentPaginationLight, RestExtractor, RestPagination, RestService } from '@app/core' |
6 | import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' | 6 | import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' |
7 | import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' | ||
8 | import { | 7 | import { |
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 | ||
13 | function 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 | |||
21 | function getStoredMute () { | 13 | function 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 | ||
124 | export { | 116 | export { |
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' | |||
31 | import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager' | 31 | import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager' |
32 | import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder' | 32 | import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder' |
33 | import { segmentValidatorFactory } from './p2p-media-loader/segment-validator' | 33 | import { segmentValidatorFactory } from './p2p-media-loader/segment-validator' |
34 | import { getAverageBandwidthInStore, getStoredP2PEnabled, saveAverageBandwidth } from './peertube-player-local-storage' | 34 | import { getAverageBandwidthInStore, saveAverageBandwidth } from './peertube-player-local-storage' |
35 | import { | 35 | import { |
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 | ||
142 | type P2PMediaLoaderPluginOptions = { | 144 | type 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 @@ | |||
1 | import { VideoFile } from '@shared/models' | 1 | import { HTMLServerConfig, Video, VideoFile } from '@shared/models' |
2 | 2 | ||
3 | function toTitleCase (str: string) { | 3 | function 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 | ||
11 | function 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 | |||
11 | function isIOS () { | 18 | function 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' | |||
2 | import * as WebTorrent from 'webtorrent' | 2 | import * as WebTorrent from 'webtorrent' |
3 | import { timeToInt } from '@shared/core-utils' | 3 | import { timeToInt } from '@shared/core-utils' |
4 | import { VideoFile } from '@shared/models' | 4 | import { VideoFile } from '@shared/models' |
5 | import { | 5 | import { 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' | ||
12 | import { PeerTubeResolution, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings' | 6 | import { PeerTubeResolution, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings' |
13 | import { getRtcConfig, isIOS, videoFileMaxByResolution, videoFileMinByResolution } from '../utils' | 7 | import { getRtcConfig, isIOS, videoFileMaxByResolution, videoFileMinByResolution } from '../utils' |
14 | import { PeertubeChunkStore } from './peertube-chunk-store' | 8 | import { 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 @@ | |||
1 | export * from './users' | 1 | export * from './users' |
2 | export * from './bytes' | 2 | export * from './bytes' |
3 | export * from './images' | 3 | export * from './images' |
4 | export * from './local-storage-utils' | ||
4 | export * from './peertube-web-storage' | 5 | export * from './peertube-web-storage' |
5 | export * from './utils' | 6 | export * from './utils' |
6 | export * from './plugins-manager' | 7 | export * from './plugins-manager' |
diff --git a/client/src/root-helpers/local-storage-utils.ts b/client/src/root-helpers/local-storage-utils.ts new file mode 100644 index 000000000..c2b3f9035 --- /dev/null +++ b/client/src/root-helpers/local-storage-utils.ts | |||
@@ -0,0 +1,10 @@ | |||
1 | function getBoolOrDefault (value: string, defaultValue: boolean) { | ||
2 | if (value === 'true') return true | ||
3 | if (value === 'false') return false | ||
4 | |||
5 | return defaultValue | ||
6 | } | ||
7 | |||
8 | export { | ||
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 @@ | |||
1 | export * from './user-local-storage-keys' | 1 | export * from './user-local-storage-keys' |
2 | export * from './user-local-storage-manager' | ||
3 | export * from './user-tokens' | 2 | export * 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 @@ | |||
1 | export const UserLocalStorageKeys = { | 1 | export 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 | |||
21 | export 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 @@ | |||
1 | import { NSFWPolicyType, UserRole } from '@shared/models' | ||
2 | import { peertubeLocalStorage } from '../peertube-web-storage' | ||
3 | import { UserLocalStorageKeys } from './user-local-storage-keys' | ||
4 | |||
5 | function 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 | |||
22 | function 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 | |||
33 | function 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 | |||
51 | export { | ||
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 @@ | |||
1 | import { peertubeLocalStorage } from '../peertube-web-storage' | 1 | import { UserTokenLocalStorageKeys } from './user-local-storage-keys' |
2 | |||
3 | export type TokenOptions = { | ||
4 | accessToken: string | ||
5 | refreshToken: string | ||
6 | tokenType: string | ||
7 | } | ||
8 | |||
9 | // Private class only used by User | ||
10 | export class Tokens { | ||
11 | private static KEYS = { | ||
12 | ACCESS_TOKEN: 'access_token', | ||
13 | REFRESH_TOKEN: 'refresh_token', | ||
14 | TOKEN_TYPE: 'token_type' | ||
15 | } | ||
16 | 2 | ||
3 | export 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 { | |||
16 | import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager' | 17 | import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager' |
17 | import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' | 18 | import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' |
18 | import { TranslationsManager } from '../../assets/player/translations-manager' | 19 | import { TranslationsManager } from '../../assets/player/translations-manager' |
20 | import { isP2PEnabled } from '../../assets/player/utils' | ||
21 | import { getBoolOrDefault } from '../../root-helpers/local-storage-utils' | ||
19 | import { peertubeLocalStorage } from '../../root-helpers/peertube-web-storage' | 22 | import { peertubeLocalStorage } from '../../root-helpers/peertube-web-storage' |
20 | import { PluginsManager } from '../../root-helpers/plugins-manager' | 23 | import { PluginsManager } from '../../root-helpers/plugins-manager' |
21 | import { Tokens } from '../../root-helpers/users' | 24 | import { UserLocalStorageKeys, UserTokens } from '../../root-helpers/users' |
22 | import { objectToUrlEncoded } from '../../root-helpers/utils' | 25 | import { objectToUrlEncoded } from '../../root-helpers/utils' |
23 | import { RegisterClientHelpers } from '../../types/register-client-option.model' | 26 | import { RegisterClientHelpers } from '../../types/register-client-option.model' |
24 | import { PeerTubeEmbedApi } from './embed-api' | 27 | import { 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 | ||
789 | PeerTubeEmbed.main() | 803 | PeerTubeEmbed.main() |