aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared/shared-user-subscription
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/shared/shared-user-subscription')
-rw-r--r--client/src/app/shared/shared-user-subscription/index.ts5
-rw-r--r--client/src/app/shared/shared-user-subscription/remote-subscribe.component.html32
-rw-r--r--client/src/app/shared/shared-user-subscription/remote-subscribe.component.scss6
-rw-r--r--client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts58
-rw-r--r--client/src/app/shared/shared-user-subscription/shared-user-subscription.module.ts29
-rw-r--r--client/src/app/shared/shared-user-subscription/subscribe-button.component.html67
-rw-r--r--client/src/app/shared/shared-user-subscription/subscribe-button.component.scss112
-rw-r--r--client/src/app/shared/shared-user-subscription/subscribe-button.component.ts196
-rw-r--r--client/src/app/shared/shared-user-subscription/user-subscription.service.ts182
9 files changed, 687 insertions, 0 deletions
diff --git a/client/src/app/shared/shared-user-subscription/index.ts b/client/src/app/shared/shared-user-subscription/index.ts
new file mode 100644
index 000000000..fd53d14b5
--- /dev/null
+++ b/client/src/app/shared/shared-user-subscription/index.ts
@@ -0,0 +1,5 @@
1export * from './user-subscription.service'
2export * from './subscribe-button.component'
3export * from './remote-subscribe.component'
4
5export * from './shared-user-subscription.module'
diff --git a/client/src/app/shared/shared-user-subscription/remote-subscribe.component.html b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.html
new file mode 100644
index 000000000..acfec0a8e
--- /dev/null
+++ b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.html
@@ -0,0 +1,32 @@
1<form novalidate [formGroup]="form" (ngSubmit)="formValidated()">
2 <div class="form-group mb-2">
3 <input type="email"
4 formControlName="text"
5 class="form-control"
6 (keyup.control.enter)="onValidKey()" (keyup.meta.enter)="onValidKey()"
7 placeholder="jane_doe@example.com">
8 </div>
9
10 <button type="submit" [disabled]="!form.valid" class="btn btn-sm btn-remote-follow" i18n>
11 <span *ngIf="!interact">Remote subscribe</span>
12 <span *ngIf="interact">Remote interact</span>
13 </button>
14
15 <my-help *ngIf="!interact && showHelp">
16 <ng-template ptTemplate="customHtml">
17 <ng-container i18n>
18 You can subscribe to the channel via any ActivityPub-capable fediverse instance.<br /><br />
19 For instance with Mastodon or Pleroma you can type the channel URL in the search box and subscribe there.
20 </ng-container>
21 </ng-template>
22 </my-help>
23
24 <my-help *ngIf="showHelp && interact">
25 <ng-template ptTemplate="customHtml">
26 <ng-container i18n>
27 You can interact with this via any ActivityPub-capable fediverse instance.<br /><br />
28 For instance with Mastodon or Pleroma you can type the current URL in the search box and interact with it there.
29 </ng-container>
30 </ng-template>
31 </my-help>
32</form>
diff --git a/client/src/app/shared/shared-user-subscription/remote-subscribe.component.scss b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.scss
new file mode 100644
index 000000000..698c5866a
--- /dev/null
+++ b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.scss
@@ -0,0 +1,6 @@
1@import '_mixins';
2
3.btn-remote-follow {
4 @include peertube-button;
5 @include orange-button;
6} \ No newline at end of file
diff --git a/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts
new file mode 100644
index 000000000..09164a5d3
--- /dev/null
+++ b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts
@@ -0,0 +1,58 @@
1import { Component, Input, OnInit } from '@angular/core'
2import { FormReactive, FormValidatorService, UserValidatorsService } from '@app/shared/shared-forms'
3
4@Component({
5 selector: 'my-remote-subscribe',
6 templateUrl: './remote-subscribe.component.html',
7 styleUrls: ['./remote-subscribe.component.scss']
8})
9export class RemoteSubscribeComponent extends FormReactive implements OnInit {
10 @Input() uri: string
11 @Input() interact = false
12 @Input() showHelp = false
13
14 constructor (
15 protected formValidatorService: FormValidatorService,
16 private userValidatorsService: UserValidatorsService
17 ) {
18 super()
19 }
20
21 ngOnInit () {
22 this.buildForm({
23 text: this.userValidatorsService.USER_EMAIL
24 })
25 }
26
27 onValidKey () {
28 this.check()
29 if (!this.form.valid) return
30
31 this.formValidated()
32 }
33
34 formValidated () {
35 const address = this.form.value['text']
36 const [ username, hostname ] = address.split('@')
37
38 // Should not have CORS error because https://tools.ietf.org/html/rfc7033#section-5
39 fetch(`https://${hostname}/.well-known/webfinger?resource=acct:${username}@${hostname}`)
40 .then(response => response.json())
41 .then(data => new Promise((resolve, reject) => {
42 console.log(data)
43
44 if (data && Array.isArray(data.links)) {
45 const link: { template: string } = data.links.find((link: any) => {
46 return link && typeof link.template === 'string' && link.rel === 'http://ostatus.org/schema/1.0/subscribe'
47 })
48
49 if (link && link.template.includes('{uri}')) {
50 resolve(link.template.replace('{uri}', encodeURIComponent(this.uri)))
51 }
52 }
53 reject()
54 }))
55 .then(window.open)
56 .catch(err => console.error(err))
57 }
58}
diff --git a/client/src/app/shared/shared-user-subscription/shared-user-subscription.module.ts b/client/src/app/shared/shared-user-subscription/shared-user-subscription.module.ts
new file mode 100644
index 000000000..cddea80bf
--- /dev/null
+++ b/client/src/app/shared/shared-user-subscription/shared-user-subscription.module.ts
@@ -0,0 +1,29 @@
1
2import { NgModule } from '@angular/core'
3import { SharedFormModule } from '../shared-forms'
4import { SharedMainModule } from '../shared-main/shared-main.module'
5import { RemoteSubscribeComponent } from './remote-subscribe.component'
6import { SubscribeButtonComponent } from './subscribe-button.component'
7import { UserSubscriptionService } from './user-subscription.service'
8
9@NgModule({
10 imports: [
11 SharedMainModule,
12 SharedFormModule
13 ],
14
15 declarations: [
16 RemoteSubscribeComponent,
17 SubscribeButtonComponent
18 ],
19
20 exports: [
21 RemoteSubscribeComponent,
22 SubscribeButtonComponent
23 ],
24
25 providers: [
26 UserSubscriptionService
27 ]
28})
29export class SharedUserSubscriptionModule { }
diff --git a/client/src/app/shared/shared-user-subscription/subscribe-button.component.html b/client/src/app/shared/shared-user-subscription/subscribe-button.component.html
new file mode 100644
index 000000000..85b3d1fdb
--- /dev/null
+++ b/client/src/app/shared/shared-user-subscription/subscribe-button.component.html
@@ -0,0 +1,67 @@
1<div class="btn-group-subscribe btn-group"
2 [ngClass]="{'subscribe-button': !isAllChannelsSubscribed, 'unsubscribe-button': isAllChannelsSubscribed, 'big': isBigButton }">
3
4 <ng-template #userLoggedOut>
5 <span [ngClass]="{ 'extra-text': isAtLeastOneChannelSubscribed }">
6 <ng-container *ngIf="account; then multiple; else single"></ng-container>
7 <ng-template i18n #single>Subscribe</ng-template>
8 <ng-template #multiple>
9 <span i18n>Subscribe to all channels</span>
10 <span *ngIf="isAtLeastOneChannelSubscribed">{{ subscribeStatus(true).length }}/{{ subscribed.size }}
11 <ng-container i18n>channels subscribed</ng-container>
12 </span>
13 </ng-template>
14 </span>
15 <span *ngIf="!isBigButton && displayFollowers && videoChannels.length > 1 && videoChannel.followersCount !== 0" class="followers-count">
16 {{ videoChannels[0].followersCount | myNumberFormatter }}
17 </span>
18 </ng-template>
19
20 <ng-template #userLoggedIn>
21 <button *ngIf="!isAllChannelsSubscribed" type="button"
22 class="btn btn-sm" role="button"
23 (click)="subscribe()">
24 <ng-template [ngTemplateOutlet]="userLoggedOut"></ng-template>
25 </button>
26
27 <button
28 *ngIf="isAllChannelsSubscribed" type="button"
29 class="btn btn-sm" role="button"
30 (click)="unsubscribe()">
31 <ng-container i18n>{account + "", select, undefined {Unsubscribe} other {Unsubscribe from all channels}}</ng-container>
32 </button>
33 </ng-template>
34
35 <ng-container
36 *ngIf="isUserLoggedIn(); then userLoggedIn">
37 </ng-container>
38
39 <div class="btn-group" ngbDropdown autoClose="outside"
40 placement="bottom-right" role="group"
41 aria-label="Multiple ways to subscribe to the current channel">
42 <button class="btn btn-sm dropdown-toggle-split" ngbDropdownToggle>
43 <ng-container
44 *ngIf="!isUserLoggedIn(); then userLoggedOut">
45 </ng-container>
46 </button>
47
48 <div class="dropdown-menu" ngbDropdownMenu>
49
50 <h6 class="dropdown-header" i18n>Using an ActivityPub account</h6>
51
52 <button class="dropdown-item" (click)="subscribe()">
53 <span *ngIf="!isUserLoggedIn()" i18n>Subscribe with an account on this instance</span>
54 <span *ngIf="isUserLoggedIn()" i18n>Subscribe with your local account</span>
55 </button>
56
57 <button class="dropdown-item dropdown-item-neutral" i18n>Subscribe with a Mastodon account:</button>
58 <my-remote-subscribe [showHelp]="true" [uri]="uri"></my-remote-subscribe>
59
60 <div class="dropdown-divider"></div>
61
62 <h6 class="dropdown-header" i18n>Using a syndication feed</h6>
63 <a [href]="rssUri" target="_blank" class="dropdown-item" i18n>Subscribe via RSS</a>
64
65 </div>
66 </div>
67</div>
diff --git a/client/src/app/shared/shared-user-subscription/subscribe-button.component.scss b/client/src/app/shared/shared-user-subscription/subscribe-button.component.scss
new file mode 100644
index 000000000..b739c5ae2
--- /dev/null
+++ b/client/src/app/shared/shared-user-subscription/subscribe-button.component.scss
@@ -0,0 +1,112 @@
1@import '_variables';
2@import '_mixins';
3
4.btn-group-subscribe {
5 @include peertube-button;
6 @include disable-default-a-behaviour;
7
8 float: right;
9 padding: 0;
10
11 & > .btn,
12 & > .dropdown > .dropdown-toggle {
13 font-size: 15px;
14 }
15
16 &:not(.big) {
17 white-space: nowrap;
18 }
19
20 &.big {
21 height: 35px;
22
23 & > button:first-child {
24 width: 175px;
25 }
26
27 button .extra-text {
28 span:first-child {
29 line-height: 80%;
30 }
31
32 span:not(:first-child) {
33 font-size: 75%;
34 }
35 }
36 }
37
38 // Unlogged
39 & > .dropdown > .dropdown-toggle span {
40 padding-right: 3px;
41 }
42
43 // Logged
44 & > .btn {
45 padding-right: 4px;
46
47 & + .dropdown > button {
48 padding-left: 2px;
49
50 &::after {
51 position: relative;
52 top: 1px;
53 }
54 }
55 }
56
57 &.subscribe-button {
58 .btn {
59 @include orange-button;
60 font-weight: 600;
61 }
62
63 span.followers-count {
64 padding-left: 5px;
65 }
66 }
67 &.unsubscribe-button {
68 .btn {
69 @include grey-button;
70 font-weight: 600;
71 }
72 }
73
74 .dropdown-menu {
75 cursor: default;
76
77 button {
78 cursor: pointer;
79 }
80
81 .dropdown-item-neutral {
82 cursor: default;
83
84 &:hover,
85 &:focus {
86 background-color: inherit;
87 }
88 }
89 }
90
91 ::ng-deep form {
92 padding: 0.25rem 1rem;
93 }
94
95 input {
96 @include peertube-input-text(100%);
97 }
98}
99
100.extra-text {
101 display: flex;
102 flex-direction: column;
103
104 span:first-child {
105 line-height: 75%;
106 }
107
108 span:not(:first-child) {
109 font-size: 60%;
110 text-align: left;
111 }
112}
diff --git a/client/src/app/shared/shared-user-subscription/subscribe-button.component.ts b/client/src/app/shared/shared-user-subscription/subscribe-button.component.ts
new file mode 100644
index 000000000..72fa3f4fd
--- /dev/null
+++ b/client/src/app/shared/shared-user-subscription/subscribe-button.component.ts
@@ -0,0 +1,196 @@
1import { concat, forkJoin, merge } from 'rxjs'
2import { Component, Input, OnChanges, OnInit } from '@angular/core'
3import { Router } from '@angular/router'
4import { AuthService, Notifier } from '@app/core'
5import { Account, VideoChannel, VideoService } from '@app/shared/shared-main'
6import { I18n } from '@ngx-translate/i18n-polyfill'
7import { FeedFormat } from '@shared/models'
8import { UserSubscriptionService } from './user-subscription.service'
9
10@Component({
11 selector: 'my-subscribe-button',
12 templateUrl: './subscribe-button.component.html',
13 styleUrls: [ './subscribe-button.component.scss' ]
14})
15export class SubscribeButtonComponent implements OnInit, OnChanges {
16 /**
17 * SubscribeButtonComponent can be used with a single VideoChannel passed as [VideoChannel],
18 * or with an account and a full list of that account's videoChannels. The latter is intended
19 * to allow mass un/subscription from an account's page, while keeping the channel-centric
20 * subscription model.
21 */
22 @Input() account: Account
23 @Input() videoChannels: VideoChannel[]
24 @Input() displayFollowers = false
25 @Input() size: 'small' | 'normal' = 'normal'
26
27 subscribed = new Map<string, boolean>()
28
29 constructor (
30 private authService: AuthService,
31 private router: Router,
32 private notifier: Notifier,
33 private userSubscriptionService: UserSubscriptionService,
34 private i18n: I18n,
35 private videoService: VideoService
36 ) { }
37
38 get handle () {
39 return this.account
40 ? this.account.nameWithHost
41 : this.videoChannel.name + '@' + this.videoChannel.host
42 }
43
44 get channelHandle () {
45 return this.getChannelHandler(this.videoChannel)
46 }
47
48 get uri () {
49 return this.account
50 ? this.account.url
51 : this.videoChannels[0].url
52 }
53
54 get rssUri () {
55 const rssFeed = this.account
56 ? this.videoService
57 .getAccountFeedUrls(this.account.id)
58 .find(i => i.format === FeedFormat.RSS)
59 : this.videoService
60 .getVideoChannelFeedUrls(this.videoChannels[0].id)
61 .find(i => i.format === FeedFormat.RSS)
62
63 return rssFeed.url
64 }
65
66 get videoChannel () {
67 return this.videoChannels[0]
68 }
69
70 get isAllChannelsSubscribed () {
71 return this.subscribeStatus(true).length === this.videoChannels.length
72 }
73
74 get isAtLeastOneChannelSubscribed () {
75 return this.subscribeStatus(true).length > 0
76 }
77
78 get isBigButton () {
79 return this.isUserLoggedIn() && this.videoChannels.length > 1 && this.isAtLeastOneChannelSubscribed
80 }
81
82 ngOnInit () {
83 this.loadSubscribedStatus()
84 }
85
86 ngOnChanges () {
87 this.ngOnInit()
88 }
89
90 subscribe () {
91 if (this.isUserLoggedIn()) {
92 return this.localSubscribe()
93 }
94
95 return this.gotoLogin()
96 }
97
98 localSubscribe () {
99 const subscribedStatus = this.subscribeStatus(false)
100
101 const observableBatch = this.videoChannels
102 .map(videoChannel => this.getChannelHandler(videoChannel))
103 .filter(handle => subscribedStatus.includes(handle))
104 .map(handle => this.userSubscriptionService.addSubscription(handle))
105
106 forkJoin(observableBatch)
107 .subscribe(
108 () => {
109 this.notifier.success(
110 this.account
111 ? this.i18n(
112 'Subscribed to all current channels of {{nameWithHost}}. You will be notified of all their new videos.',
113 { nameWithHost: this.account.displayName }
114 )
115 : this.i18n(
116 'Subscribed to {{nameWithHost}}. You will be notified of all their new videos.',
117 { nameWithHost: this.videoChannels[0].displayName }
118 )
119 ,
120 this.i18n('Subscribed')
121 )
122 },
123
124 err => this.notifier.error(err.message)
125 )
126 }
127
128 unsubscribe () {
129 if (this.isUserLoggedIn()) {
130 this.localUnsubscribe()
131 }
132 }
133
134 localUnsubscribe () {
135 const subscribeStatus = this.subscribeStatus(true)
136
137 const observableBatch = this.videoChannels
138 .map(videoChannel => this.getChannelHandler(videoChannel))
139 .filter(handle => subscribeStatus.includes(handle))
140 .map(handle => this.userSubscriptionService.deleteSubscription(handle))
141
142 concat(...observableBatch)
143 .subscribe({
144 complete: () => {
145 this.notifier.success(
146 this.account
147 ? this.i18n('Unsubscribed from all channels of {{nameWithHost}}', { nameWithHost: this.account.nameWithHost })
148 : this.i18n('Unsubscribed from {{nameWithHost}}', { nameWithHost: this.videoChannels[ 0 ].nameWithHost })
149 ,
150 this.i18n('Unsubscribed')
151 )
152 },
153
154 error: err => this.notifier.error(err.message)
155 })
156 }
157
158 isUserLoggedIn () {
159 return this.authService.isLoggedIn()
160 }
161
162 gotoLogin () {
163 this.router.navigate([ '/login' ])
164 }
165
166 subscribeStatus (subscribed: boolean) {
167 const accumulator: string[] = []
168 for (const [key, value] of this.subscribed.entries()) {
169 if (value === subscribed) accumulator.push(key)
170 }
171
172 return accumulator
173 }
174
175 private getChannelHandler (videoChannel: VideoChannel) {
176 return videoChannel.name + '@' + videoChannel.host
177 }
178
179 private loadSubscribedStatus () {
180 if (!this.isUserLoggedIn()) return
181
182 for (const videoChannel of this.videoChannels) {
183 const handle = this.getChannelHandler(videoChannel)
184 this.subscribed.set(handle, false)
185
186 merge(
187 this.userSubscriptionService.listenToSubscriptionCacheChange(handle),
188 this.userSubscriptionService.doesSubscriptionExist(handle)
189 ).subscribe(
190 res => this.subscribed.set(handle, res),
191
192 err => this.notifier.error(err.message)
193 )
194 }
195 }
196}
diff --git a/client/src/app/shared/shared-user-subscription/user-subscription.service.ts b/client/src/app/shared/shared-user-subscription/user-subscription.service.ts
new file mode 100644
index 000000000..732ed6bcb
--- /dev/null
+++ b/client/src/app/shared/shared-user-subscription/user-subscription.service.ts
@@ -0,0 +1,182 @@
1import * as debug from 'debug'
2import { uniq } from 'lodash-es'
3import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs'
4import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap } from 'rxjs/operators'
5import { HttpClient, HttpParams } from '@angular/common/http'
6import { Injectable, NgZone } from '@angular/core'
7import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core'
8import { enterZone, leaveZone } from '@app/helpers'
9import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
10import { ResultList, VideoChannel as VideoChannelServer, VideoSortField } from '@shared/models'
11import { environment } from '../../../environments/environment'
12
13const logger = debug('peertube:subscriptions:UserSubscriptionService')
14
15type SubscriptionExistResult = { [ uri: string ]: boolean }
16type SubscriptionExistResultObservable = { [ uri: string ]: Observable<boolean> }
17
18@Injectable()
19export class UserSubscriptionService {
20 static BASE_USER_SUBSCRIPTIONS_URL = environment.apiUrl + '/api/v1/users/me/subscriptions'
21
22 // Use a replay subject because we "next" a value before subscribing
23 private existsSubject = new ReplaySubject<string>(1)
24 private readonly existsObservable: Observable<SubscriptionExistResult>
25
26 private myAccountSubscriptionCache: SubscriptionExistResult = {}
27 private myAccountSubscriptionCacheObservable: SubscriptionExistResultObservable = {}
28 private myAccountSubscriptionCacheSubject = new Subject<SubscriptionExistResult>()
29
30 constructor (
31 private authHttp: HttpClient,
32 private restExtractor: RestExtractor,
33 private videoService: VideoService,
34 private restService: RestService,
35 private ngZone: NgZone
36 ) {
37 this.existsObservable = merge(
38 this.existsSubject.pipe(
39 // We leave Angular zone so Protractor does not get stuck
40 bufferTime(500, leaveZone(this.ngZone, asyncScheduler)),
41 filter(uris => uris.length !== 0),
42 map(uris => uniq(uris)),
43 observeOn(enterZone(this.ngZone, asyncScheduler)),
44 switchMap(uris => this.doSubscriptionsExist(uris)),
45 share()
46 ),
47
48 this.myAccountSubscriptionCacheSubject
49 )
50 }
51
52 getUserSubscriptionVideos (parameters: {
53 videoPagination: ComponentPaginationLight,
54 sort: VideoSortField,
55 skipCount?: boolean
56 }): Observable<ResultList<Video>> {
57 const { videoPagination, sort, skipCount } = parameters
58 const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
59
60 let params = new HttpParams()
61 params = this.restService.addRestGetParams(params, pagination, sort)
62
63 if (skipCount) params = params.set('skipCount', skipCount + '')
64
65 return this.authHttp
66 .get<ResultList<Video>>(UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/videos', { params })
67 .pipe(
68 switchMap(res => this.videoService.extractVideos(res)),
69 catchError(err => this.restExtractor.handleError(err))
70 )
71 }
72
73 /**
74 * Subscription part
75 */
76
77 deleteSubscription (nameWithHost: string) {
78 const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/' + nameWithHost
79
80 return this.authHttp.delete(url)
81 .pipe(
82 map(this.restExtractor.extractDataBool),
83 tap(() => {
84 this.myAccountSubscriptionCache[nameWithHost] = false
85
86 this.myAccountSubscriptionCacheSubject.next(this.myAccountSubscriptionCache)
87 }),
88 catchError(err => this.restExtractor.handleError(err))
89 )
90 }
91
92 addSubscription (nameWithHost: string) {
93 const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL
94
95 const body = { uri: nameWithHost }
96 return this.authHttp.post(url, body)
97 .pipe(
98 map(this.restExtractor.extractDataBool),
99 tap(() => {
100 this.myAccountSubscriptionCache[nameWithHost] = true
101
102 this.myAccountSubscriptionCacheSubject.next(this.myAccountSubscriptionCache)
103 }),
104 catchError(err => this.restExtractor.handleError(err))
105 )
106 }
107
108 listSubscriptions (componentPagination: ComponentPaginationLight): Observable<ResultList<VideoChannel>> {
109 const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL
110
111 const pagination = this.restService.componentPaginationToRestPagination(componentPagination)
112
113 let params = new HttpParams()
114 params = this.restService.addRestGetParams(params, pagination)
115
116 return this.authHttp.get<ResultList<VideoChannelServer>>(url, { params })
117 .pipe(
118 map(res => VideoChannelService.extractVideoChannels(res)),
119 catchError(err => this.restExtractor.handleError(err))
120 )
121 }
122
123 /**
124 * SubscriptionExist part
125 */
126
127 listenToMyAccountSubscriptionCacheSubject () {
128 return this.myAccountSubscriptionCacheSubject.asObservable()
129 }
130
131 listenToSubscriptionCacheChange (nameWithHost: string) {
132 if (nameWithHost in this.myAccountSubscriptionCacheObservable) {
133 return this.myAccountSubscriptionCacheObservable[ nameWithHost ]
134 }
135
136 const obs = this.existsObservable
137 .pipe(
138 filter(existsResult => existsResult[ nameWithHost ] !== undefined),
139 map(existsResult => existsResult[ nameWithHost ])
140 )
141
142 this.myAccountSubscriptionCacheObservable[ nameWithHost ] = obs
143 return obs
144 }
145
146 doesSubscriptionExist (nameWithHost: string) {
147 logger('Running subscription check for %d.', nameWithHost)
148
149 if (nameWithHost in this.myAccountSubscriptionCache) {
150 logger('Found cache for %d.', nameWithHost)
151
152 return of(this.myAccountSubscriptionCache[ nameWithHost ])
153 }
154
155 this.existsSubject.next(nameWithHost)
156
157 logger('Fetching from network for %d.', nameWithHost)
158 return this.existsObservable.pipe(
159 filter(existsResult => existsResult[ nameWithHost ] !== undefined),
160 map(existsResult => existsResult[ nameWithHost ]),
161 tap(result => this.myAccountSubscriptionCache[ nameWithHost ] = result)
162 )
163 }
164
165 private doSubscriptionsExist (uris: string[]): Observable<SubscriptionExistResult> {
166 const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/exist'
167 let params = new HttpParams()
168
169 params = this.restService.addObjectParams(params, { uris })
170
171 return this.authHttp.get<SubscriptionExistResult>(url, { params })
172 .pipe(
173 tap(res => {
174 this.myAccountSubscriptionCache = {
175 ...this.myAccountSubscriptionCache,
176 ...res
177 }
178 }),
179 catchError(err => this.restExtractor.handleError(err))
180 )
181 }
182}