Compare commits

..

1 Commits

Author SHA1 Message Date
Thea Schöbl
cc4a4ee90d feat: improve search experience 2024-11-09 11:40:57 +00:00
6 changed files with 64 additions and 67 deletions

View File

@@ -86,8 +86,6 @@ export abstract class AuthService implements IAuthService {
private _authenticatedSubject = new BehaviorSubject<boolean>(false);
private _loggedInSubject = new BehaviorSubject<boolean>(false);
private _initComplete = new BehaviorSubject<boolean>(false);
protected tokenHandler: TokenRequestHandler;
@@ -135,13 +133,6 @@ export abstract class AuthService implements IAuthService {
return this._authenticatedSubject.asObservable();
}
/**
* Similar to isAuthenticated$, but will also return true if the token is expired
*/
get isLoggedIn$(): Observable<boolean> {
return this._loggedInSubject.asObservable();
}
get initComplete$(): Observable<boolean> {
return this._initComplete.asObservable();
}
@@ -192,20 +183,19 @@ export abstract class AuthService implements IAuthService {
protected notifyActionListers(action: IAuthAction) {
/* eslint-disable unicorn/no-useless-undefined */
switch (action.action) {
case AuthActions.RefreshFailed:
case AuthActions.SignInFailed:
case AuthActions.SignOutSuccess:
case AuthActions.SignOutFailed: {
this._tokenSubject.next(undefined);
this._userSubject.next(undefined);
this._authenticatedSubject.next(false);
this._loggedInSubject.next(false);
break;
}
case AuthActions.LoadTokenFromStorageFailed: {
this._tokenSubject.next(undefined);
this._userSubject.next(undefined);
this._authenticatedSubject.next(false);
this._loggedInSubject.next(false);
this._initComplete.next(true);
break;
}
@@ -213,13 +203,11 @@ export abstract class AuthService implements IAuthService {
case AuthActions.RefreshSuccess: {
this._tokenSubject.next(action.tokenResponse);
this._authenticatedSubject.next(true);
this._loggedInSubject.next(true);
break;
}
case AuthActions.LoadTokenFromStorageSuccess: {
this._tokenSubject.next(action.tokenResponse);
this._authenticatedSubject.next((action.tokenResponse as TokenResponse).isValid(0));
this._loggedInSubject.next(true);
this._initComplete.next(true);
break;
}
@@ -454,6 +442,7 @@ export abstract class AuthService implements IAuthService {
public async refreshToken() {
await this.requestTokenRefresh().catch(error => {
this.storage.removeItem(TOKEN_RESPONSE_KEY);
this.notifyActionListers(AuthActionBuilder.RefreshFailed(error));
});
}

View File

@@ -81,8 +81,6 @@ export class PAIAAuthService {
private _authenticatedSubject = new BehaviorSubject<boolean>(false);
private _loggedIn = new BehaviorSubject<boolean>(false);
private _initComplete = new BehaviorSubject<boolean>(false);
protected tokenHandler: PAIATokenRequestHandler;
@@ -120,13 +118,6 @@ export class PAIAAuthService {
return this._authenticatedSubject.asObservable();
}
/**
* Similar to isAuthenticated$, but will also return true if the token is expired
*/
get isLoggedIn$(): Observable<boolean> {
return this._loggedIn.asObservable();
}
get initComplete$(): Observable<boolean> {
return this._initComplete.asObservable();
}
@@ -179,27 +170,23 @@ export class PAIAAuthService {
this._tokenSubject.next(undefined);
this._userSubject.next(undefined);
this._authenticatedSubject.next(false);
this._loggedIn.next(false);
break;
}
case AuthActions.LoadTokenFromStorageFailed: {
this._tokenSubject.next(undefined);
this._userSubject.next(undefined);
this._authenticatedSubject.next(false);
this._loggedIn.next(false);
this._initComplete.next(true);
break;
}
case AuthActions.SignInSuccess: {
this._tokenSubject.next(action.tokenResponse);
this._authenticatedSubject.next(true);
this._loggedIn.next(true);
break;
}
case AuthActions.LoadTokenFromStorageSuccess: {
this._tokenSubject.next(action.tokenResponse);
this._authenticatedSubject.next((action.tokenResponse as TokenResponse).isValid(0));
this._loggedIn.next(true);
this._initComplete.next(true);
break;
}

View File

@@ -23,9 +23,9 @@ export class IdCardsProvider {
this.encryptedStorageProvider.get<SCIdCard[]>('id-cards') as Promise<SCIdCard[]>,
).pipe(filter(it => it !== undefined));
return auth.isLoggedIn$.pipe(
mergeMap(isLoggedIn =>
isLoggedIn
return auth.isAuthenticated$.pipe(
mergeMap(isAuthenticated =>
isAuthenticated
? feature
? storedIdCards.pipe(
concatWith(

View File

@@ -7,7 +7,7 @@ import {SCAuthorizationProviderType} from '@openstapps/core';
import {EncryptedStorageProvider} from '../storage/encrypted-storage.provider';
class FakeAuth {
isLoggedIn$ = new BehaviorSubject(false);
isAuthenticated$ = new BehaviorSubject(false);
// eslint-disable-next-line @typescript-eslint/no-empty-function
getValidToken() {}
@@ -42,7 +42,7 @@ describe('IdCards', () => {
});
it('should emit network result when logged in', async () => {
fakeAuth.isLoggedIn$.next(true);
fakeAuth.isAuthenticated$.next(true);
httpClient.get = jasmine.createSpy().and.returnValue(of(['abc']));
fakeAuth.getValidToken = jasmine.createSpy().and.resolveTo({accessToken: 'fake-token'});
const provider = new IdCardsProvider(authHelper, configProvider, httpClient, encryptedStorageProvider);
@@ -63,7 +63,7 @@ describe('IdCards', () => {
expect(await firstValueFrom(observable)).toEqual([]);
httpClient.get = jasmine.createSpy().and.returnValue(of(['abc']));
fakeAuth.getValidToken = jasmine.createSpy().and.resolveTo({accessToken: 'fake-token'});
fakeAuth.isLoggedIn$.next(true);
fakeAuth.isAuthenticated$.next(true);
// this is counter-intuitive, but because we unsubscribed above the first value
// will now contain the network result.
expect(await firstValueFrom(observable)).toEqual(['abc' as never]);

View File

@@ -6,56 +6,74 @@
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAr purpose. see the gnu general public license for
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* you should have received a copy of the gnu general public license along with
* this program. if not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, inject, input} from '@angular/core';
import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
import {SCSection} from '../../../../config/profile-page-sections';
import {AuthHelperService} from '../../auth/auth-helper.service';
import {mergeMap, of} from 'rxjs';
import {Observable} from 'rxjs';
import {SCAuthorizationProviderType} from '@openstapps/core';
import Swiper from 'swiper';
import {toObservable, toSignal} from '@angular/core/rxjs-interop';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Component({
selector: 'stapps-profile-page-section',
templateUrl: 'profile-page-section.html',
styleUrls: ['profile-page-section.scss'],
})
export class ProfilePageSectionComponent {
item = input.required<SCSection>();
export class ProfilePageSectionComponent implements OnInit {
@Input() item: SCSection;
minSlideWidth = input(110);
@Input() minSlideWidth = 110;
authHelper = inject(AuthHelperService);
loggedIn = toSignal(
toObservable(this.item).pipe(
mergeMap(item =>
item.authProvider ? this.authHelper.getProvider(item.authProvider).isLoggedIn$ : of(false),
),
),
);
isLoggedIn: boolean;
isEnd = false;
isBeginning = true;
slidesPerView?: number;
slidesPerView: number;
slidesFillScreen = false;
data: {
[key in SCAuthorizationProviderType]: {loggedIn$: Observable<boolean>};
} = {
default: {
loggedIn$: this.authHelper.getProvider('default').isAuthenticated$,
},
paia: {
loggedIn$: this.authHelper.getProvider('paia').isAuthenticated$,
},
};
destroy$ = inject(DestroyRef);
constructor(private authHelper: AuthHelperService) {}
ngOnInit() {
if (this.item.authProvider) {
this.data[this.item.authProvider].loggedIn$
.pipe(takeUntilDestroyed(this.destroy$))
.subscribe(loggedIn => {
this.isLoggedIn = loggedIn;
});
}
}
activeIndexChange(swiper: Swiper) {
this.isBeginning = swiper.isBeginning;
this.isEnd = swiper.isEnd;
this.slidesFillScreen = this.slidesPerView! >= swiper.slides.length;
this.slidesFillScreen = this.slidesPerView >= swiper.slides.length;
}
resizeSwiper(resizeEvent: ResizeObserverEntry, swiper: Swiper) {
const slidesPerView =
Math.floor((resizeEvent.contentRect.width - this.minSlideWidth() / 2) / this.minSlideWidth()) + 0.5;
Math.floor((resizeEvent.contentRect.width - this.minSlideWidth / 2) / this.minSlideWidth) + 0.5;
if (slidesPerView > 1 && slidesPerView !== this.slidesPerView) {
this.slidesPerView = slidesPerView;
@@ -66,13 +84,16 @@ export class ProfilePageSectionComponent {
}
async toggleLogIn() {
const providerType = this.item().authProvider;
if (!providerType) return;
if (this.loggedIn()) {
await this.authHelper.getProvider(providerType).signOut();
await this.authHelper.endBrowserSession(providerType);
} else {
await this.authHelper.getProvider(providerType).signIn();
}
if (!this.item.authProvider) return;
await (this.isLoggedIn ? this.signOut(this.item.authProvider) : this.signIn(this.item.authProvider));
}
async signIn(providerType: SCAuthorizationProviderType) {
await this.authHelper.getProvider(providerType).signIn();
}
async signOut(providerType: SCAuthorizationProviderType) {
await this.authHelper.getProvider(providerType).signOut();
await this.authHelper.endBrowserSession(providerType);
}
}

View File

@@ -13,24 +13,24 @@
~ this program. If not, see <https://www.gnu.org/licenses/>.
-->
<stapps-section [title]="'name' | translateSimple: item()">
@if (item().authProvider) {
<stapps-section [title]="'name' | translateSimple: item">
@if (item.authProvider) {
<ion-button slot="button-end" fill="clear" color="dark" (click)="toggleLogIn()">
@if (loggedIn()) {
@if (isLoggedIn) {
<ion-icon slot="end" name="logout"></ion-icon>
} @else {
<ion-icon slot="end" name="login"></ion-icon>
}
<ion-label>{{ 'profile.buttons.default.log_' + (loggedIn() ? 'out' : 'in') | translate }}</ion-label>
<ion-label>{{ 'profile.buttons.default.log_' + (isLoggedIn ? 'out' : 'in') | translate }}</ion-label>
</ion-button>
}
<simple-swiper>
@for (link of item().links; track link) {
@for (link of item.links; track link) {
<ion-item
lines="none"
[routerLink]="link.link"
[disabled]="link.needsAuth && !loggedIn()"
[disabled]="link.needsAuth && !isLoggedIn"
[detail]="false"
>
<div>