|
5 | 5 | import { |
6 | 6 | Component, |
7 | 7 | Inject, |
| 8 | + NgZone, |
| 9 | + OnDestroy, |
8 | 10 | OnInit, |
9 | 11 | PLATFORM_ID, |
10 | 12 | } from '@angular/core'; |
@@ -79,7 +81,12 @@ export const SHOW_DISCOJUICE_POPUP_CACHE_NAME = 'SHOW_DISCOJUICE_POPUP'; |
79 | 81 | TranslateModule, |
80 | 82 | ], |
81 | 83 | }) |
82 | | -export class LogInPasswordComponent implements OnInit { |
| 84 | +export class LogInPasswordComponent implements OnInit, OnDestroy { |
| 85 | + |
| 86 | + /** |
| 87 | + * Handle of the pending DiscoJuice popup-open timer so it can be cancelled on destroy. |
| 88 | + */ |
| 89 | + private discoJuiceTimer: ReturnType<typeof setTimeout> = null; |
83 | 90 |
|
84 | 91 | /** |
85 | 92 | * The authentication method data. |
@@ -143,6 +150,7 @@ export class LogInPasswordComponent implements OnInit { |
143 | 150 | protected authorizationService: AuthorizationDataService, |
144 | 151 | @Inject(PLATFORM_ID) protected platformId: object, |
145 | 152 | protected storage: CookieService, |
| 153 | + protected zone: NgZone, |
146 | 154 | ) { |
147 | 155 | this.authMethod = injectedAuthMethodModel; |
148 | 156 | } |
@@ -254,14 +262,46 @@ export class LogInPasswordComponent implements OnInit { |
254 | 262 | } |
255 | 263 |
|
256 | 264 | /** |
257 | | - * Show DiscoJuice login modal using javascript functions. The timeout must be set because of angular component |
258 | | - * lifecycle. Discojuice won't be showed up without timeout. |
| 265 | + * Trigger the DiscoJuice popup by programmatically clicking the sign-on link that the AAI script |
| 266 | + * binds DiscoJuice to (rendered in the CLARIN top navbar). |
| 267 | + * |
| 268 | + * The AAI/DiscoJuice scripts are loaded asynchronously (and in parallel) by the navbar component, |
| 269 | + * so on a cold load DiscoJuice may not have bound its click handler by the time this component |
| 270 | + * initialises. Clicking too early is a silent no-op and the popup never opens. We therefore poll |
| 271 | + * (bounded) until DiscoJuice has created its popup markup (`div.discojuice`, built when it binds |
| 272 | + * to the sign-on link) and only then click, which reliably opens the popup regardless of how long |
| 273 | + * the scripts take to load. |
| 274 | + * |
| 275 | + * The polling runs OUTSIDE the Angular zone so it never keeps the application unstable (which |
| 276 | + * would stall SSR rendering and `fixture.whenStable()` in tests). A programmatic `click()` also |
| 277 | + * bypasses the sign-on link's `pointer-events: none` guard (that guard only blocks real pointer |
| 278 | + * input), so the popup opens even while the link is still visually disabled for the mouse. |
259 | 279 | * @private |
260 | 280 | */ |
261 | 281 | private popUpDiscoJuiceLogin() { |
262 | | - setTimeout(() => { |
263 | | - document?.getElementById('clarin-signon-discojuice')?.click(); |
264 | | - }, 250); |
| 282 | + const maxAttempts = 40; // ~10s at 250ms intervals — well beyond a normal script load |
| 283 | + let attempts = 0; |
| 284 | + this.zone.runOutsideAngular(() => { |
| 285 | + const tryOpen = () => { |
| 286 | + const signOnLink = document?.getElementById('clarin-signon-discojuice'); |
| 287 | + // `div.discojuice` is created (hidden) when DiscoJuice binds to the sign-on link, so its |
| 288 | + // presence means the click handler is wired and a click will actually open the popup. |
| 289 | + if (signOnLink && document?.querySelector('div.discojuice')) { |
| 290 | + signOnLink.click(); |
| 291 | + return; |
| 292 | + } |
| 293 | + if (++attempts < maxAttempts) { |
| 294 | + this.discoJuiceTimer = setTimeout(tryOpen, 250); |
| 295 | + } |
| 296 | + }; |
| 297 | + this.discoJuiceTimer = setTimeout(tryOpen, 250); |
| 298 | + }); |
| 299 | + } |
| 300 | + |
| 301 | + ngOnDestroy(): void { |
| 302 | + if (this.discoJuiceTimer) { |
| 303 | + clearTimeout(this.discoJuiceTimer); |
| 304 | + } |
265 | 305 | } |
266 | 306 |
|
267 | 307 | /** |
|
0 commit comments