Skip to content

Commit 65bbfa0

Browse files
Merged dspace-cris-2024_02_x into task/dspace-cris-2024_02_x/DSC-1956
2 parents f29762a + 6eba316 commit 65bbfa0

36 files changed

Lines changed: 2140 additions & 1667 deletions

bitbucket-pipelines.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ definitions:
6262
- step: &angular-build
6363
name: angular-build
6464
image:
65-
name: cypress/browsers:node-20.18.0-chrome-130.0.6723.69-1-ff-131.0.3-edge-130.0.2849.52-1
65+
name: cypress/browsers:node-22.21.0-chrome-141.0.7390.107-1-ff-144.0-edge-141.0.3537.92-1
6666
run-as-user: 1000
6767
size: 4x
6868
caches:
@@ -78,7 +78,7 @@ definitions:
7878
- step: &unittest-code-checks
7979
name: test-code-checks
8080
image:
81-
name: cypress/browsers:node-20.18.0-chrome-130.0.6723.69-1-ff-131.0.3-edge-130.0.2849.52-1
81+
name: cypress/browsers:node-22.21.0-chrome-141.0.7390.107-1-ff-144.0-edge-141.0.3537.92-1
8282
run-as-user: 1000
8383
size: 4x
8484
caches:
@@ -96,7 +96,7 @@ definitions:
9696
- step: &run-e2e-tests
9797
name: Run E2E test
9898
image:
99-
name: cypress/browsers:node-20.18.0-chrome-130.0.6723.69-1-ff-131.0.3-edge-130.0.2849.52-1
99+
name: cypress/browsers:node-22.21.0-chrome-141.0.7390.107-1-ff-144.0-edge-141.0.3537.92-1
100100
run-as-user: 0
101101
size: 4x
102102
services:

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@
115115
"date-fns": "^2.29.3",
116116
"date-fns-tz": "^1.3.7",
117117
"deepmerge": "^4.3.1",
118-
"domino-ext": "^2.1.4",
119118
"ejs": "^3.1.10",
120119
"express": "^4.21.2",
121120
"express-rate-limit": "^5.1.3",

server.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import * as express from 'express';
2424
import * as ejs from 'ejs';
2525
import * as compression from 'compression';
2626
import * as expressStaticGzip from 'express-static-gzip';
27-
import * as domino from 'domino-ext';
2827
/* eslint-enable import/no-namespace */
2928
import axios from 'axios';
3029
import LRU from 'lru-cache';
@@ -85,20 +84,10 @@ let anonymousCache: LRU<string, any>;
8584
// extend environment with app config for server
8685
extendEnvironmentWithAppConfig(environment, appConfig);
8786

88-
// Create a DOM window object based on the template
89-
const _window = domino.createWindow(indexHtml);
90-
9187
// The REST server base URL
9288
const REST_BASE_URL = environment.rest.ssrBaseUrl || environment.rest.baseUrl;
9389

9490
const IIIF_ALLOWED_ORIGINS = environment.rest.allowedOrigins || [];
95-
96-
// Assign the DOM window and document objects to the global object
97-
(_window as any).screen = { deviceXDPI: 0, logicalXDPI: 0 };
98-
(global as any).window = _window;
99-
(global as any).document = _window.document;
100-
(global as any).navigator = _window.navigator;
101-
10291
// The Express app is exported so that it can be used by serverless Functions.
10392
export function app() {
10493

src/app/app.component.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
Component,
1111
HostListener,
1212
Inject,
13+
OnDestroy,
1314
OnInit,
1415
PLATFORM_ID,
1516
} from '@angular/core';
@@ -31,13 +32,15 @@ import { TranslateService } from '@ngx-translate/core';
3132
import {
3233
BehaviorSubject,
3334
Observable,
35+
Subject,
3436
} from 'rxjs';
3537
import {
3638
delay,
3739
distinctUntilChanged,
3840
map,
3941
switchMap,
4042
take,
43+
takeUntil,
4144
withLatestFrom,
4245
} from 'rxjs/operators';
4346

@@ -78,10 +81,15 @@ import { SocialService } from './social/social.service';
7881
SocialComponent,
7982
],
8083
})
81-
export class AppComponent implements OnInit, AfterViewInit {
84+
export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
8285
notificationOptions;
8386
models;
8487

88+
/**
89+
* Subject to signal component destruction and clean up subscriptions
90+
*/
91+
private destroy$: Subject<void> = new Subject<void>();
92+
8593
/**
8694
* Whether or not the authentication is currently blocking the UI
8795
*/
@@ -172,6 +180,7 @@ export class AppComponent implements OnInit, AfterViewInit {
172180
take(1),
173181
map((currentUrl) => [currentUrl, event]),
174182
)),
183+
takeUntil(this.destroy$),
175184
).subscribe(([currentUrl, event]: [string, any]) => {
176185
if (event instanceof NavigationStart) {
177186
const nextUrl = event.url;
@@ -188,6 +197,11 @@ export class AppComponent implements OnInit, AfterViewInit {
188197
});
189198
}
190199

200+
ngOnDestroy(): void {
201+
this.destroy$.next();
202+
this.destroy$.complete();
203+
}
204+
191205
@HostListener('window:resize', ['$event'])
192206
public onResize(event): void {
193207
this.dispatchWindowSize(event.target.innerWidth, event.target.innerHeight);
@@ -202,7 +216,10 @@ export class AppComponent implements OnInit, AfterViewInit {
202216
private trackIdleModal() {
203217
const isIdle$ = this.authService.isUserIdle();
204218
const isAuthenticated$ = this.authService.isAuthenticated();
205-
isIdle$.pipe(withLatestFrom(isAuthenticated$))
219+
isIdle$.pipe(
220+
withLatestFrom(isAuthenticated$),
221+
takeUntil(this.destroy$),
222+
)
206223
.subscribe(([userIdle, authenticated]) => {
207224
if (userIdle && authenticated) {
208225
if (!this.idleModalOpen) {

src/app/app.metareducers.spec.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { universalMetaReducer } from './app.metareducers';
2+
import { StoreActionTypes } from './store.actions';
3+
4+
describe('universalMetaReducer', () => {
5+
const mockReducer = (state: any, action: any) => state;
6+
7+
it('should pass state through for unknown action', () => {
8+
const state = { core: { route: { queryParams: { a: '1' }, params: {} } } };
9+
const result = universalMetaReducer(mockReducer)(state, { type: 'UNKNOWN' });
10+
expect(result).toEqual(state);
11+
});
12+
13+
describe('REHYDRATE', () => {
14+
it('should merge payload into state', () => {
15+
const state = { otherProp: 'keep-me' };
16+
const payload = { core: { route: { queryParams: { f: ['x'] }, params: {} } } };
17+
const result = universalMetaReducer(mockReducer)(state, {
18+
type: StoreActionTypes.REHYDRATE,
19+
payload,
20+
});
21+
expect(result.otherProp).toBe('keep-me');
22+
expect(result.core.route).toEqual({ queryParams: {}, params: {} });
23+
});
24+
25+
it('should reset core.route to empty after rehydration', () => {
26+
const state = { core: { route: { queryParams: { f: ['x'] }, params: { id: '123' } } } };
27+
const payload = { core: { route: { queryParams: { stale: ['val'] }, params: { stale: 'val' } } } };
28+
const result = universalMetaReducer(mockReducer)(state, {
29+
type: StoreActionTypes.REHYDRATE,
30+
payload,
31+
});
32+
expect(result.core.route).toEqual({ queryParams: {}, params: {} });
33+
});
34+
35+
it('should not crash when state.core is undefined', () => {
36+
const state = {};
37+
const payload = { core: { route: { queryParams: { a: '1' }, params: {} } } };
38+
const result = universalMetaReducer(mockReducer)(state, {
39+
type: StoreActionTypes.REHYDRATE,
40+
payload,
41+
});
42+
expect(result.core.route).toEqual({ queryParams: {}, params: {} });
43+
});
44+
45+
it('should preserve other core properties when resetting route', () => {
46+
const state = { core: { route: {} } };
47+
const payload = { core: { route: { queryParams: { x: '1' }, params: {} }, otherProp: 'keep-me' } };
48+
const result = universalMetaReducer(mockReducer)(state, {
49+
type: StoreActionTypes.REHYDRATE,
50+
payload,
51+
});
52+
expect(result.core.otherProp).toBe('keep-me');
53+
expect(result.core.route).toEqual({ queryParams: {}, params: {} });
54+
});
55+
56+
it('should handle REPLAY action (no-op)', () => {
57+
const state = { core: { route: { queryParams: { a: '1' }, params: {} } } };
58+
const result = universalMetaReducer(mockReducer)(state, {
59+
type: StoreActionTypes.REPLAY,
60+
});
61+
expect(result).toEqual(state);
62+
});
63+
});
64+
});

src/app/app.metareducers.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ export function universalMetaReducer(reducer) {
1919
switch (action.type) {
2020
case StoreActionTypes.REHYDRATE:
2121
state = Object.assign({}, state, action.payload);
22+
if (state.core) {
23+
state.core = Object.assign({}, state.core, {
24+
route: { queryParams: {}, params: {} },
25+
});
26+
}
2227
break;
2328
case StoreActionTypes.REPLAY:
2429
default:

src/app/core/auth/auth.actions.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const AuthActionTypes = {
3535
LOG_OUT_ERROR: type('dspace/auth/LOG_OUT_ERROR'),
3636
LOG_OUT_SUCCESS: type('dspace/auth/LOG_OUT_SUCCESS'),
3737
SET_REDIRECT_URL: type('dspace/auth/SET_REDIRECT_URL'),
38+
SET_REDIRECT_URL_AND_NAVIGATE: type('dspace/auth/SET_REDIRECT_URL_AND_NAVIGATE'),
3839
RETRIEVE_AUTHENTICATED_EPERSON: type('dspace/auth/RETRIEVE_AUTHENTICATED_EPERSON'),
3940
RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS: type('dspace/auth/RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS'),
4041
RETRIEVE_AUTHENTICATED_EPERSON_ERROR: type('dspace/auth/RETRIEVE_AUTHENTICATED_EPERSON_ERROR'),
@@ -360,6 +361,23 @@ export class SetRedirectUrlAction implements Action {
360361
}
361362
}
362363

364+
/**
365+
* Change the redirect url.
366+
* @class SetRedirectUrlAction
367+
* @implements {Action}
368+
*/
369+
export class SetRedirectUrlAndNavigateAction implements Action {
370+
public type: string = AuthActionTypes.SET_REDIRECT_URL_AND_NAVIGATE;
371+
payload: {
372+
redirectUrl: string;
373+
navigateUrl: string;
374+
};
375+
376+
constructor(redirectUrl: string, navigateUrl: string) {
377+
this.payload = { redirectUrl, navigateUrl };
378+
}
379+
}
380+
363381
/**
364382
* Start loading for a hard redirect
365383
* @class StartHardRedirectLoadingAction

src/app/core/auth/auth.effects.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import {
7979
RetrieveAuthMethodsErrorAction,
8080
RetrieveAuthMethodsSuccessAction,
8181
RetrieveTokenAction,
82+
SetRedirectUrlAndNavigateAction,
8283
SetUserAsIdleAction,
8384
} from './auth.actions';
8485
// import services
@@ -164,6 +165,13 @@ export class AuthEffects {
164165
}),
165166
), { dispatch: false });
166167

168+
public redirectAndNavigate$: Observable<Action> = createEffect(() => this.actions$
169+
.pipe(ofType(AuthActionTypes.SET_REDIRECT_URL_AND_NAVIGATE),
170+
tap((action: SetRedirectUrlAndNavigateAction) => this.router.navigate([decodeURIComponent(action.payload.navigateUrl)])),
171+
),
172+
{ dispatch: false },
173+
);
174+
167175
// It means "reacts to this action but don't send another"
168176
public authenticatedError$: Observable<Action> = createEffect(() => this.actions$.pipe(
169177
ofType(AuthActionTypes.AUTHENTICATED_ERROR),

src/app/core/auth/auth.reducer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
RetrieveAuthMethodsSuccessAction,
1818
SetAuthCookieStatus,
1919
SetRedirectUrlAction,
20+
SetRedirectUrlAndNavigateAction,
2021
} from './auth.actions';
2122
import { AuthMethod } from './models/auth.method';
2223
import { AuthMethodType } from './models/auth.method-type';
@@ -267,6 +268,11 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
267268
redirectUrl: (action as SetRedirectUrlAction).payload,
268269
});
269270

271+
case AuthActionTypes.SET_REDIRECT_URL_AND_NAVIGATE:
272+
return Object.assign({}, state, {
273+
redirectUrl: (action as SetRedirectUrlAndNavigateAction).payload.redirectUrl,
274+
});
275+
270276
case AuthActionTypes.REDIRECT_AFTER_LOGIN_SUCCESS:
271277
return Object.assign({}, state, {
272278
loading: true,

src/app/core/auth/auth.service.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import {
6464
ResetAuthenticationMessagesAction,
6565
SetAuthCookieStatus,
6666
SetRedirectUrlAction,
67+
SetRedirectUrlAndNavigateAction,
6768
SetUserAsIdleAction,
6869
UnsetUserAsIdleAction,
6970
} from './auth.actions';
@@ -556,15 +557,19 @@ export class AuthService {
556557
/**
557558
* Set redirect url
558559
*/
559-
setRedirectUrl(url: string) {
560+
setRedirectUrl(redirectUrl: string, navigateUrl?: string) {
560561
// Add 1 hour to the current date
561562
const expireDate = Date.now() + (1000 * 60 * 60);
562563

563564
// Set the cookie expire date
564565
const expires = new Date(expireDate);
565-
const options: CookieAttributes = { expires: expires };
566-
this.storage.set(REDIRECT_COOKIE, url, options);
567-
this.store.dispatch(new SetRedirectUrlAction(isNotUndefined(url) ? url : ''));
566+
const options: Cookies.CookieAttributes = { expires: expires };
567+
this.storage.set(REDIRECT_COOKIE, redirectUrl, options);
568+
if (hasValue(navigateUrl)) {
569+
this.store.dispatch(new SetRedirectUrlAndNavigateAction(isNotUndefined(redirectUrl) ? redirectUrl : '', navigateUrl));
570+
} else {
571+
this.store.dispatch(new SetRedirectUrlAction(isNotUndefined(redirectUrl) ? redirectUrl : ''));
572+
}
568573
}
569574

570575
/**

0 commit comments

Comments
 (0)