Skip to content

Commit

Permalink
refactor: move apiToken cookie functionality to cookies.service.ts (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Eisie96 authored and SGrueber committed Mar 20, 2024
1 parent 3e8949d commit 5e71f94
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 67 deletions.
37 changes: 6 additions & 31 deletions src/app/core/services/api/api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ import {
identity,
iif,
of,
race,
throwError,
timer,
} from 'rxjs';
import { catchError, concatMap, filter, first, map, switchMap, take, withLatestFrom } from 'rxjs/operators';

Expand All @@ -30,7 +28,6 @@ import { communicationTimeoutError, serverError } from 'ish-core/store/core/erro
import { isServerConfigurationLoaded } from 'ish-core/store/core/server-config';
import { getBasketIdOrCurrent } from 'ish-core/store/customer/basket';
import { getLoggedInCustomer, getLoggedInUser, getPGID } from 'ish-core/store/customer/user';
import { ApiTokenCookie } from 'ish-core/utils/api-token/api-token.service';
import { CookiesService } from 'ish-core/utils/cookies/cookies.service';
import { whenTruthy } from 'ish-core/utils/operators';
import { encodeResourceID } from 'ish-core/utils/url-resource-ids';
Expand Down Expand Up @@ -137,25 +134,19 @@ export class ApiService {
return of(path);
}

// get current apiToken cookie information
const apiToken = this.getApiTokenFromCookie();

return combineLatest([
this.store.pipe(select(getRestEndpoint), whenTruthy()),
this.getLocale$(options),
this.getCurrency$(options),
of('/'),
of(path.includes('/') ? path.split('/')[0] : path),
// pgid
race(
this.store.pipe(
select(getPGID),
// when an apiToken is available, then the pgid has to be set when the options are enabled
apiToken && (options?.sendPGID || options?.sendSPGID) ? whenTruthy() : identity
),
// on timeout current pgid will be selected
timer(3000).pipe(switchMap(() => this.store.pipe(select(getPGID))))
).pipe(
this.store.pipe(
select(getPGID),
// when a user apiToken is available, then the pgid has to be set when the options are enabled
this.cookiesService.hasUserApiTokenCookie() && (options?.sendPGID || options?.sendSPGID)
? whenTruthy()
: identity,
map(pgid => (options?.sendPGID && pgid ? `;pgid=${pgid}` : options?.sendSPGID && pgid ? `;spgid=${pgid}` : ''))
),
// remaining path
Expand Down Expand Up @@ -216,22 +207,6 @@ export class ApiService {
]);
}

/**
* retrieve an apiToken when it is assigned to an authenticated user
*/
private getApiTokenFromCookie(): string {
const cookieContent = this.cookiesService.get('apiToken');
if (cookieContent) {
try {
const apiTokenCookie: ApiTokenCookie = JSON.parse(cookieContent);
return !apiTokenCookie?.isAnonymous ? apiTokenCookie.apiToken : undefined;
} catch (err) {
// ignore
}
}
return;
}

/**
* http get request
*/
Expand Down
10 changes: 5 additions & 5 deletions src/app/core/store/customer/user/user.effects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { CoreStoreModule } from 'ish-core/store/core/core-store.module';
import { displaySuccessMessage } from 'ish-core/store/core/messages';
import { loadServerConfigSuccess } from 'ish-core/store/core/server-config';
import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module';
import { ApiTokenService } from 'ish-core/utils/api-token/api-token.service';
import { CookiesService } from 'ish-core/utils/cookies/cookies.service';
import { makeHttpError } from 'ish-core/utils/dev/api-service-utils';
import { routerTestNavigatedAction } from 'ish-core/utils/dev/routing';

Expand Down Expand Up @@ -73,7 +73,7 @@ describe('User Effects', () => {
let store: Store;
let userServiceMock: UserService;
let paymentServiceMock: PaymentService;
let apiTokenServiceMock: ApiTokenService;
let cookiesServiceMock: CookiesService;
let oAuthServiceMock: OAuthService;
let tokenServiceMock: TokenService;
let router: Router;
Expand Down Expand Up @@ -103,7 +103,7 @@ describe('User Effects', () => {
beforeEach(() => {
userServiceMock = mock(UserService);
paymentServiceMock = mock(PaymentService);
apiTokenServiceMock = mock(ApiTokenService);
cookiesServiceMock = mock(CookiesService);
oAuthServiceMock = mock(OAuthService);
tokenServiceMock = mock(TokenService);

Expand All @@ -122,7 +122,7 @@ describe('User Effects', () => {
when(paymentServiceMock.getUserPaymentMethods(anything())).thenReturn(of([]));
when(paymentServiceMock.createUserPayment(anything(), anything())).thenReturn(of({ id: 'paymentInstrumentId' }));
when(paymentServiceMock.deleteUserPaymentInstrument(anyString(), anyString())).thenReturn(of(undefined));
when(apiTokenServiceMock.hasUserApiTokenCookie()).thenReturn(false);
when(cookiesServiceMock.hasUserApiTokenCookie()).thenReturn(false);
when(oAuthServiceMock.events).thenReturn(of());

TestBed.configureTestingModule({
Expand All @@ -132,7 +132,7 @@ describe('User Effects', () => {
RouterTestingModule.withRoutes([{ path: '**', children: [] }]),
],
providers: [
{ provide: ApiTokenService, useFactory: () => instance(apiTokenServiceMock) },
{ provide: CookiesService, useFactory: () => instance(cookiesServiceMock) },
{ provide: PaymentService, useFactory: () => instance(paymentServiceMock) },
{ provide: TokenService, useFactory: () => instance(tokenServiceMock) },
{ provide: UserService, useFactory: () => instance(userServiceMock) },
Expand Down
8 changes: 4 additions & 4 deletions src/app/core/store/customer/user/user.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { UserService } from 'ish-core/services/user/user.service';
import { displaySuccessMessage } from 'ish-core/store/core/messages';
import { selectQueryParam, selectUrl } from 'ish-core/store/core/router';
import { getServerConfigParameter } from 'ish-core/store/core/server-config';
import { ApiTokenService } from 'ish-core/utils/api-token/api-token.service';
import { CookiesService } from 'ish-core/utils/cookies/cookies.service';
import { mapErrorToAction, mapToPayload, mapToPayloadProperty, whenTruthy } from 'ish-core/utils/operators';

import { getPGID, personalizationStatusDetermined } from '.';
Expand Down Expand Up @@ -72,8 +72,8 @@ export class UserEffects {
private userService: UserService,
private paymentService: PaymentService,
private router: Router,
private apiTokenService: ApiTokenService,
private tokenService: TokenService
private tokenService: TokenService,
private cookiesService: CookiesService
) {}

loginUser$ = createEffect(() =>
Expand Down Expand Up @@ -280,7 +280,7 @@ export class UserEffects {
determinePersonalizationStatus$ = createEffect(() =>
this.store.pipe(
select(getPGID),
map(pgid => !this.apiTokenService.hasUserApiTokenCookie() || pgid),
map(pgid => !this.cookiesService.hasUserApiTokenCookie() || pgid),
whenTruthy(),
delay(100),
map(() => personalizationStatusDetermined())
Expand Down
16 changes: 11 additions & 5 deletions src/app/core/utils/api-token/api-token.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { ApiTokenCookie, ApiTokenService } from './api-token.service';
describe('Api Token Service', () => {
let apiTokenService: ApiTokenService;
let cookieServiceMock: CookiesService;

let store: Store;

const initialApiTokenCookie: ApiTokenCookie = {
Expand Down Expand Up @@ -57,7 +56,7 @@ describe('Api Token Service', () => {

describe('getInternalApiTokenCookieValue$', () => {
beforeEach(() => {
when(cookieServiceMock.get('apiToken')).thenReturn(JSON.stringify({ apiToken: '123' } as ApiTokenCookie));
when(cookieServiceMock.getApiTokenCookie()).thenReturn({ apiToken: '123', type: 'user' });
injectServices(cookieServiceMock);
});

Expand Down Expand Up @@ -98,10 +97,16 @@ describe('Api Token Service', () => {
});

it('should update apiToken information in cookie when apiToken changes', () => {
when(cookieServiceMock.getApiTokenCookie()).thenReturn({ apiToken: 'new-api-token', type: 'user' });

apiTokenService.setApiToken('new-api-token');

verify(
cookieServiceMock.put('apiToken', JSON.stringify({ apiToken: 'new-api-token' } as ApiTokenCookie), anything())
cookieServiceMock.put(
'apiToken',
JSON.stringify({ apiToken: 'new-api-token', type: 'user' } as ApiTokenCookie),
anything()
)
).once();
});
});
Expand All @@ -125,12 +130,13 @@ describe('Api Token Service', () => {

describe('cookieVanish$', () => {
beforeEach(() => {
when(cookieServiceMock.getApiTokenCookie()).thenReturn(initialApiTokenCookie);
when(cookieServiceMock.get('apiToken')).thenReturn(JSON.stringify(initialApiTokenCookie));
injectServices(cookieServiceMock);
});

it('should vanish apiToken information when cookie is removed unexpectedly from the outside', done => {
when(cookieServiceMock.get('apiToken')).thenReturn(JSON.stringify(initialApiTokenCookie), undefined);
when(cookieServiceMock.getApiTokenCookie()).thenReturn(initialApiTokenCookie, undefined);
combineLatest([apiTokenService.getApiToken$(), apiTokenService.getCookieVanishes$()]).subscribe(
([apiToken, cookieVanishes]) => {
expect(apiToken).toBeUndefined();
Expand All @@ -149,7 +155,7 @@ describe('Api Token Service', () => {
});

it('should set correct apiToken when new apiToken is set unexpectedly from the outside', done => {
when(cookieServiceMock.get('apiToken')).thenReturn(undefined, JSON.stringify(initialApiTokenCookie));
when(cookieServiceMock.getApiTokenCookie()).thenReturn(undefined, initialApiTokenCookie);
apiTokenService
.getApiToken$()
.pipe(skip(1))
Expand Down
26 changes: 4 additions & 22 deletions src/app/core/utils/api-token/api-token.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class ApiTokenService {

constructor(private cookiesService: CookiesService, private store: Store, private appRef: ApplicationRef) {
// setup initial values
const initialCookie = this.parseCookie();
const initialCookie = this.cookiesService.getApiTokenCookie();
this.initialCookie$ = new BehaviorSubject<ApiTokenCookie>(!SSR ? initialCookie : undefined);
this.apiToken$ = new BehaviorSubject<string>(initialCookie?.apiToken);

Expand Down Expand Up @@ -147,11 +147,6 @@ export class ApiTokenService {
this.apiToken$.next(apiToken);
}

hasUserApiTokenCookie() {
const apiTokenCookie = this.parseCookie();
return apiTokenCookie?.type === 'user' && !apiTokenCookie?.isAnonymous;
}

/**
* trigger actions to restore store information based on initial apiToken cookie
*/
Expand Down Expand Up @@ -259,7 +254,7 @@ export class ApiTokenService {
* will remove apiToken and inform cookieVanishes$ listeners that cookie in not working as expected
*/
private invalidateApiToken() {
const cookie = this.parseCookie();
const cookie = this.cookiesService.getApiTokenCookie();

this.removeApiToken();

Expand Down Expand Up @@ -313,7 +308,7 @@ export class ApiTokenService {
first(),
mergeMap(() =>
interval(1000).pipe(
map(() => this.parseCookie()),
map(() => this.cookiesService.getApiTokenCookie()),
pairwise(),
distinctUntilChanged((prev, curr) => isEqual(prev, curr))
)
Expand Down Expand Up @@ -417,24 +412,11 @@ export class ApiTokenService {
return { apiToken, type: 'order', orderId, creator: 'pwa' };
}

const apiTokenCookieString = this.cookiesService.get('apiToken');
const apiTokenCookie: ApiTokenCookie = apiTokenCookieString ? JSON.parse(apiTokenCookieString) : undefined;
const apiTokenCookie: ApiTokenCookie = this.cookiesService.getApiTokenCookie() || undefined;
if (apiToken && apiTokenCookie) {
return { ...apiTokenCookie, apiToken }; // overwrite existing cookie information with new apiToken
}
})
);
}

private parseCookie(): ApiTokenCookie {
const cookieContent = this.cookiesService.get('apiToken');
if (cookieContent) {
try {
return JSON.parse(cookieContent);
} catch (err) {
// ignore
}
}
return;
}
}
17 changes: 17 additions & 0 deletions src/app/core/utils/cookies/cookies.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Inject, Injectable, TransferState } from '@angular/core';
import { COOKIE_CONSENT_OPTIONS } from 'ish-core/configurations/injection-keys';
import { COOKIE_CONSENT_VERSION } from 'ish-core/configurations/state-keys';
import { CookieConsentSettings } from 'ish-core/models/cookies/cookies.model';
import { ApiTokenCookie } from 'ish-core/utils/api-token/api-token.service';
import { browserNameVersion } from 'ish-core/utils/browser-detection';
import { InjectSingle } from 'ish-core/utils/injection';

Expand Down Expand Up @@ -80,6 +81,22 @@ export class CookiesService {
}
}

getApiTokenCookie(): ApiTokenCookie {
if (!SSR) {
try {
return JSON.parse(this.get('apiToken') || 'null');
} catch (err) {
// ignore
}
}
return;
}

hasUserApiTokenCookie() {
const apiTokenCookie = this.getApiTokenCookie();
return apiTokenCookie?.type === 'user' && !apiTokenCookie?.isAnonymous;
}

/**
* Deletes all cookies except for the ones configured as 'allowedCookies' in the environments cookie consent options.
*/
Expand Down

0 comments on commit 5e71f94

Please sign in to comment.