-
Notifications
You must be signed in to change notification settings - Fork 557
Expand file tree
/
Copy pathbrowser-init.service.ts
More file actions
240 lines (217 loc) · 7.91 KB
/
Copy pathbrowser-init.service.ts
File metadata and controls
240 lines (217 loc) · 7.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
import {
Inject,
Injectable,
TransferState,
} from '@angular/core';
import { Router } from '@angular/router';
import {
APP_CONFIG,
APP_CONFIG_STATE,
AppConfig,
} from '@dspace/config/app-config.interface';
import { BuildConfig } from '@dspace/config/build-config.interface';
import { extendEnvironmentWithAppConfig } from '@dspace/config/config.util';
import { DefaultAppConfig } from '@dspace/config/default-app-config';
import { AuthService } from '@dspace/core/auth/auth.service';
import { OrejimeService } from '@dspace/core/cookies/orejime.service';
import { coreSelector } from '@dspace/core/core.selectors';
import { CorrelationIdService } from '@dspace/core/correlation-id/correlation-id.service';
import { RequestService } from '@dspace/core/data/request.service';
import { RootDataService } from '@dspace/core/data/root-data.service';
import { LocaleService } from '@dspace/core/locale/locale.service';
import { HeadTagService } from '@dspace/core/metadata/head-tag.service';
import { StoreActionTypes } from '@dspace/core/ngrx/type';
import { HALEndpointService } from '@dspace/core/shared/hal-endpoint.service';
import { isNotEmpty } from '@dspace/shared/utils/empty.util';
import { Store } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import {
firstValueFrom,
lastValueFrom,
Subscription,
} from 'rxjs';
import {
filter,
find,
map,
} from 'rxjs/operators';
import { logStartupMessage } from '../../../startup-message';
import { AppState } from '../../app/app.reducer';
import { BreadcrumbsService } from '../../app/breadcrumbs/breadcrumbs.service';
import { InitService } from '../../app/init.service';
import { MenuService } from '../../app/shared/menu/menu.service';
import { MenuProviderService } from '../../app/shared/menu/menu-provider.service';
import { ThemeService } from '../../app/shared/theme-support/theme.service';
import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider';
import { GoogleAnalyticsService } from '../../app/statistics/google-analytics.service';
import { MatomoService } from '../../app/statistics/matomo.service';
import { StoreAction } from '../../app/store.actions';
import { environment } from '../../environments/environment';
/**
* Performs client-side initialization.
*/
@Injectable()
export class BrowserInitService extends InitService {
sub: Subscription;
constructor(
protected store: Store<AppState>,
protected correlationIdService: CorrelationIdService,
protected transferState: TransferState,
@Inject(APP_CONFIG) protected appConfig: BuildConfig,
protected translate: TranslateService,
protected localeService: LocaleService,
protected angulartics2DSpace: Angulartics2DSpace,
protected googleAnalyticsService: GoogleAnalyticsService,
protected headTagService: HeadTagService,
protected breadcrumbsService: BreadcrumbsService,
protected orejimeService: OrejimeService,
protected authService: AuthService,
protected themeService: ThemeService,
protected menuService: MenuService,
private rootDataService: RootDataService,
protected router: Router,
private requestService: RequestService,
private halService: HALEndpointService,
private matomoService: MatomoService,
protected menuProviderService: MenuProviderService,
) {
super(
store,
correlationIdService,
appConfig,
translate,
localeService,
angulartics2DSpace,
headTagService,
breadcrumbsService,
themeService,
menuService,
menuProviderService,
);
}
protected static resolveAppConfig(
transferState: TransferState,
) {
if (transferState.hasKey<AppConfig>(APP_CONFIG_STATE)) {
const appConfig = transferState.get<AppConfig>(APP_CONFIG_STATE, new DefaultAppConfig());
// extend environment with app config for browser
extendEnvironmentWithAppConfig(environment, appConfig);
}
}
protected init(): () => Promise<boolean> {
return async () => {
await this.loadAppState();
this.checkAuthenticationToken();
this.externalAuthCheck();
this.initCorrelationId();
this.checkEnvironment();
logStartupMessage(environment);
this.initI18n();
this.initAngulartics();
this.initGoogleAnalytics();
this.initMatomo();
this.initRouteListeners();
this.themeService.listenForThemeChanges(true);
this.trackAuthTokenExpiration();
this.initOrejime();
await lastValueFrom(this.authenticationReady$());
this.menuProviderService.initPersistentMenus(false);
return true;
};
}
// Browser-only initialization steps
/**
* Retrieve server-side application state from the {@link NGRX_STATE} key and rehydrate the store.
* Resolves once the store is no longer empty.
* @private
*/
private async loadAppState(): Promise<boolean> {
// The app state can be transferred only when SSR and CSR are using the same base url for the REST API
if (this.appConfig.ssr.transferState) {
const state = this.transferState.get<any>(InitService.NGRX_STATE, null);
this.transferState.remove(InitService.NGRX_STATE);
this.store.dispatch(new StoreAction(StoreActionTypes.REHYDRATE, state));
return lastValueFrom(
this.store.select(coreSelector).pipe(
find((core: any) => isNotEmpty(core)),
map(() => true),
),
);
} else {
return Promise.resolve(true);
}
}
private trackAuthTokenExpiration(): void {
this.authService.trackTokenExpiration();
}
/**
* Initialize Orejime (once authentication is resolved)
* @protected
*/
protected initOrejime() {
this.authenticationReady$().subscribe(() => {
this.orejimeService.initialize();
});
}
protected initGoogleAnalytics() {
this.googleAnalyticsService.addTrackingIdToPage();
}
protected initMatomo(): void {
this.matomoService.init();
}
/**
* During an external authentication flow invalidate the
* data in the cache. This allows the app to fetch fresh content.
* @private
*/
private externalAuthCheck() {
this.sub = this.authService.isExternalAuthentication().pipe(
filter((externalAuth: boolean) => externalAuth),
).subscribe(() => {
this.requestService.setStaleByHrefSubstring(this.halService.getRootHref());
this.authService.setExternalAuthStatus(false);
},
);
this.closeAuthCheckSubscription();
}
/**
* Unsubscribe the external authentication subscription
* when authentication is no longer blocking.
* @private
*/
private closeAuthCheckSubscription() {
void firstValueFrom(this.authenticationReady$()).then(() => {
this.sub.unsubscribe();
});
}
/**
* Start route-listening subscriptions
* @protected
*/
protected initRouteListeners(): void {
super.initRouteListeners();
this.listenForRouteChanges();
this.menuProviderService.listenForRouteChanges(false);
}
/**
* Invalidate the cache for the root endpoint once, at startup, so the first request for it
* is guaranteed to hit the backend rather than serve a value cached before the app booted.
*
* This used to also run on every `NavigationStart`, but the root endpoint map does not change
* between navigations, so that repeated invalidation was unnecessary. Worse, it could mark the
* root request stale while {@link HALEndpointService} was still resolving it for an in-flight
* data request; the stale value is filtered out of the endpoint map and the retry for it can
* lose the race with the next invalidation, so `getEndpoint()` never resolves and the route
* that depends on it never finishes loading.
*/
protected listenForRouteChanges(): void {
this.rootDataService.invalidateRootCache();
}
}