@ama-mfe/ng-utils 14.5.0-prerelease.34 → 14.5.0-prerelease.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
1
  import { MessagePeerService, MESSAGE_PEER_CONFIG, MESSAGE_PEER_CONNECT_OPTIONS } from '@amadeus-it-group/microfrontends-angular';
2
2
  export { MessagePeerService as ConnectionService } from '@amadeus-it-group/microfrontends-angular';
3
3
  import * as i0 from '@angular/core';
4
- import { input, inject, ElementRef, computed, SecurityContext, effect, HostBinding, Directive, Injectable, signal, DestroyRef, provideAppInitializer, Pipe, makeEnvironmentProviders, Renderer2, afterNextRender, ChangeDetectionStrategy, ViewEncapsulation, Component, untracked } from '@angular/core';
4
+ import { input, inject, ElementRef, computed, SecurityContext, effect, HostBinding, Directive, Injectable, signal, DestroyRef, provideAppInitializer, Pipe, makeEnvironmentProviders, Renderer2, afterNextRender, ChangeDetectionStrategy, ViewEncapsulation, Component, untracked, InjectionToken } from '@angular/core';
5
5
  import { DomSanitizer } from '@angular/platform-browser';
6
6
  import { LoggerService } from '@o3r/logger';
7
- import { HISTORY_MESSAGE_TYPE, USER_ACTIVITY_MESSAGE_TYPE, RESIZE_MESSAGE_TYPE, THEME_MESSAGE_TYPE, NAVIGATION_MESSAGE_TYPE } from '@ama-mfe/messages';
7
+ import { HISTORY_MESSAGE_TYPE, USER_ACTIVITY_MESSAGE_TYPE, RESIZE_MESSAGE_TYPE, THEME_MESSAGE_TYPE, NAVIGATION_MESSAGE_TYPE, NAVIGATION_BLOCK_STATE_MESSAGE_TYPE, NAVIGATION_REQUEST_MESSAGE_TYPE, NAVIGATION_DECISION_MESSAGE_TYPE } from '@ama-mfe/messages';
8
8
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
9
9
  import { fromEvent, throttleTime, Subject, filter, map } from 'rxjs';
10
10
  import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';
@@ -286,18 +286,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
286
286
  * To use this class, simply extend it and implement the required properties (type and supportedVersions) and any additional logic needed to consume messages.
287
287
  */
288
288
  class AbstractMessageConsumer {
289
- /**
290
- * Creates an instance of AbstractMessageConsumer and optionally starts it.
291
- * @param autoStart If true, the consumer will be started immediately after creation. Defaults to true.
292
- */
293
- constructor(autoStart = true) {
289
+ constructor() {
294
290
  /** The service used to register the consumer */
295
291
  this.consumerManagerService = inject(ConsumerManagerService);
296
292
  /** The reference used to handle the destruction of the consumer */
297
293
  this.destroyRef = inject(DestroyRef);
298
- if (autoStart) {
299
- this.start();
300
- }
294
+ /** Logger shared with subclasses. */
295
+ this.logger = inject(LoggerService);
301
296
  }
302
297
  /** @inheritdoc */
303
298
  start() {
@@ -362,6 +357,11 @@ class HistoryConsumerService extends AbstractMessageConsumer {
362
357
  history.go(message.payload.delta);
363
358
  }
364
359
  };
360
+ /**
361
+ * Auto-starts the consumer on creation.
362
+ * @deprecated The constructor auto-starts the consumer for backwards compatibility. It will be removed in v15;
363
+ */
364
+ this.start();
365
365
  }
366
366
  /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: HistoryConsumerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
367
367
  /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: HistoryConsumerService, providedIn: 'root' }); }
@@ -536,6 +536,17 @@ function getDefaultClientEndpointStartOptions() {
536
536
  function isEmbedded(windowParam = window) {
537
537
  return windowParam.top !== windowParam.self;
538
538
  }
539
+ /**
540
+ * Generate a string id suitable for correlating asynchronous request/reply messages across the micro-frontend bus.
541
+ * Uses `crypto.randomUUID` when the runtime supports it, otherwise falls back to a timestamp + random suffix.
542
+ * @param prefix Optional prefix used by the fallback path to make ids easier to identify in logs (ignored when `crypto.randomUUID` is available).
543
+ */
544
+ function generateCorrelationId(prefix = 'corr') {
545
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
546
+ return crypto.randomUUID();
547
+ }
548
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
549
+ }
539
550
 
540
551
  /**
541
552
  * Provide the communication protocol connection configuration
@@ -593,6 +604,11 @@ class ResizeConsumerService extends AbstractMessageConsumer {
593
604
  */
594
605
  '1.0': (message) => this.newHeight.set({ height: message.payload.height, channelId: message.from })
595
606
  };
607
+ /**
608
+ * Auto-starts the consumer on creation.
609
+ * @deprecated The constructor auto-starts the consumer for backwards compatibility. It will be removed in v15;
610
+ */
611
+ this.start();
596
612
  }
597
613
  /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ResizeConsumerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
598
614
  /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ResizeConsumerService, providedIn: 'root' }); }
@@ -1055,6 +1071,11 @@ class NavigationConsumerService extends AbstractMessageConsumer {
1055
1071
  this.navigate(message.payload.url, message.payload.extras);
1056
1072
  }
1057
1073
  };
1074
+ /**
1075
+ * Auto-starts the consumer on creation.
1076
+ * @deprecated The constructor auto-starts the consumer for backwards compatibility. It will be removed in v15;
1077
+ */
1078
+ this.start();
1058
1079
  }
1059
1080
  /**
1060
1081
  * Parses a URL and returns an object containing the paths and query parameters.
@@ -1363,6 +1384,564 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
1363
1384
  }]
1364
1385
  }], ctorParameters: () => [] });
1365
1386
 
1387
+ /**
1388
+ * Module-side writable source of truth for the navigation block state.
1389
+ *
1390
+ * Feature code (forms, editors, etc.) calls {@link block} when unsaved changes appear and {@link unblock} once they are saved or discarded.
1391
+ * The {@link NavigationBlockStateProducerService} watches this signal and mirrors the state to the shell;
1392
+ * the {@link navigationBlockModuleGuard} reads it to decide whether to intercept in-module navigation.
1393
+ */
1394
+ class NavigationBlockService {
1395
+ constructor() {
1396
+ this.stateSignal = signal({ blocked: false }, ...(ngDevMode ? [{ debugName: "stateSignal" }] : /* istanbul ignore next */ []));
1397
+ /** Readonly view of the current navigation block state. */
1398
+ this.state = this.stateSignal.asReadonly();
1399
+ }
1400
+ /**
1401
+ * Mark the module as blocked (unsaved changes present).
1402
+ * @param reason Optional human-readable reason shown in the confirmation modal.
1403
+ */
1404
+ block(reason) {
1405
+ this.stateSignal.set({ blocked: true, reason });
1406
+ }
1407
+ /** Mark the module as unblocked (no unsaved changes). */
1408
+ unblock() {
1409
+ this.stateSignal.set({ blocked: false });
1410
+ }
1411
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationBlockService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1412
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationBlockService, providedIn: 'root' }); }
1413
+ }
1414
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationBlockService, decorators: [{
1415
+ type: Injectable,
1416
+ args: [{
1417
+ providedIn: 'root'
1418
+ }]
1419
+ }] });
1420
+
1421
+ /**
1422
+ * Module-side producer that mirrors {@link NavigationBlockService} to the shell via `navigation-block-state` messages.
1423
+ * Every state change triggers a broadcast so the shell guard can decide without a round-trip.
1424
+ *
1425
+ * Runs module-side only (it watches the module's writable state). The shell never instantiates it.
1426
+ */
1427
+ class NavigationBlockStateProducerService {
1428
+ constructor() {
1429
+ /**
1430
+ * @inheritdoc
1431
+ */
1432
+ this.types = NAVIGATION_BLOCK_STATE_MESSAGE_TYPE;
1433
+ this.connectionService = inject(MessagePeerService);
1434
+ this.producerManagerService = inject(ProducerManagerService);
1435
+ this.logger = inject(LoggerService);
1436
+ this.store = inject(NavigationBlockService);
1437
+ this.producerManagerService.register(this);
1438
+ inject(DestroyRef).onDestroy(() => this.producerManagerService.unregister(this));
1439
+ effect(() => {
1440
+ const state = this.store.state();
1441
+ const message = {
1442
+ type: NAVIGATION_BLOCK_STATE_MESSAGE_TYPE,
1443
+ version: '1.0',
1444
+ blocked: state.blocked,
1445
+ reason: state.reason
1446
+ };
1447
+ this.connectionService.send(message);
1448
+ });
1449
+ }
1450
+ /**
1451
+ * @inheritdoc
1452
+ */
1453
+ handleError(message) {
1454
+ this.logger.error('navigation-block state message could not be consumed by the shell', message);
1455
+ }
1456
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationBlockStateProducerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1457
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationBlockStateProducerService, providedIn: 'root' }); }
1458
+ }
1459
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationBlockStateProducerService, decorators: [{
1460
+ type: Injectable,
1461
+ args: [{
1462
+ providedIn: 'root'
1463
+ }]
1464
+ }], ctorParameters: () => [] });
1465
+
1466
+ /**
1467
+ * Shell-side consumer for `navigation-block-state` messages sent by modules.
1468
+ *
1469
+ * Keeps the last received state in a signal that the shell guard reads to decide whether to intercept shell-initiated navigation.
1470
+ * The state stays `undefined` until any module reports a value (interpreted as "no block known" which means navigation is allowed).
1471
+ */
1472
+ class NavigationBlockStateConsumerService extends AbstractMessageConsumer {
1473
+ constructor() {
1474
+ super(...arguments);
1475
+ /**
1476
+ * @inheritdoc
1477
+ */
1478
+ this.type = NAVIGATION_BLOCK_STATE_MESSAGE_TYPE;
1479
+ /**
1480
+ * @inheritdoc
1481
+ */
1482
+ this.supportedVersions = {
1483
+ // eslint-disable-next-line @typescript-eslint/naming-convention -- expected naming for versions
1484
+ '1.0': (message) => {
1485
+ this.blockStateSignal.set({
1486
+ blocked: message.payload.blocked,
1487
+ reason: message.payload.reason,
1488
+ channelId: message.from
1489
+ });
1490
+ }
1491
+ };
1492
+ this.blockStateSignal = signal(undefined, ...(ngDevMode ? [{ debugName: "blockStateSignal" }] : /* istanbul ignore next */ []));
1493
+ /** Last navigation block state reported by any module. */
1494
+ this.blockState = this.blockStateSignal.asReadonly();
1495
+ }
1496
+ /**
1497
+ * Mark the state as unblocked after the shell has resolved a confirmation (e.g. the user confirmed a sidebar-initiated navigation).
1498
+ */
1499
+ clear() {
1500
+ this.blockStateSignal.set({ blocked: false });
1501
+ }
1502
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationBlockStateConsumerService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
1503
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationBlockStateConsumerService, providedIn: 'root' }); }
1504
+ }
1505
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationBlockStateConsumerService, decorators: [{
1506
+ type: Injectable,
1507
+ args: [{
1508
+ providedIn: 'root'
1509
+ }]
1510
+ }] });
1511
+
1512
+ /**
1513
+ * Manages navigation request/decision round-trip communication for **both** shell and module.
1514
+ * Sends `navigation-request` messages, tracks pending requests, and sends `navigation-decision` replies.
1515
+ * Correlates responses with their originating requests using correlation IDs.
1516
+ *
1517
+ * - Module-initiated navigation → guard calls `requestNavigation()` with no target, broadcasting to the shell;
1518
+ * the shell's handler opens the modal and replies.
1519
+ * - Shell-initiated navigation → guard calls `requestNavigation(channelId)` after the user already confirmed locally,
1520
+ * so the module can run its unblock / draft-persistence logic and reply.
1521
+ *
1522
+ * If the peer never replies the returned promise stays pending and the navigation is held — callers rely on Angular
1523
+ * tearing their context down (the producer's `ngOnDestroy` resolves pending promises to `false`).
1524
+ */
1525
+ class NavigationRequestManagerService {
1526
+ constructor() {
1527
+ /**
1528
+ * @inheritdoc
1529
+ */
1530
+ this.types = [NAVIGATION_REQUEST_MESSAGE_TYPE, NAVIGATION_DECISION_MESSAGE_TYPE];
1531
+ this.connectionService = inject(MessagePeerService);
1532
+ this.producerManagerService = inject(ProducerManagerService);
1533
+ this.logger = inject(LoggerService);
1534
+ this.producerManagerService.register(this);
1535
+ }
1536
+ /**
1537
+ * Ask the peer to handle the navigation. Resolves when the peer replies with a `navigation-decision`.
1538
+ * If the peer never replies the promise stays pending.
1539
+ *
1540
+ * Re-entrant calls while a request is pending (e.g. the same navigation firing both `canActivate` and `canActivateChild`)
1541
+ * return the same promise, so only one round-trip is sent.
1542
+ * @param target Optional target peer id. Omit to broadcast (module → shell).
1543
+ * @param reason Optional human-readable reason surfaced in the shell modal.
1544
+ */
1545
+ requestNavigation(target, reason) {
1546
+ if (this.pendingRequest) {
1547
+ return this.pendingRequest.promise;
1548
+ }
1549
+ const correlationId = generateCorrelationId('nav-req');
1550
+ let resolveFn;
1551
+ const promise = new Promise((resolve) => {
1552
+ resolveFn = resolve;
1553
+ });
1554
+ this.pendingRequest = { correlationId, promise, resolve: resolveFn };
1555
+ const message = {
1556
+ type: NAVIGATION_REQUEST_MESSAGE_TYPE,
1557
+ version: '1.0',
1558
+ correlationId,
1559
+ reason
1560
+ };
1561
+ this.connectionService.send(message, target ? { to: target } : undefined);
1562
+ return promise;
1563
+ }
1564
+ /**
1565
+ * Send a `navigation-decision` reply with the given `correlationId`.
1566
+ * Called by {@link NavigationNegotiationConsumerService} after the side-specific handler has finished.
1567
+ * @param correlationId id echoed from the request
1568
+ * @param target peer id of the original sender
1569
+ * @param proceed whether to allow navigation (default true)
1570
+ */
1571
+ sendDecision(correlationId, target, proceed = true) {
1572
+ const message = {
1573
+ type: NAVIGATION_DECISION_MESSAGE_TYPE,
1574
+ version: '1.0',
1575
+ correlationId,
1576
+ proceed
1577
+ };
1578
+ this.connectionService.send(message, { to: target });
1579
+ }
1580
+ /**
1581
+ * Called by {@link NavigationDecisionConsumerService} when a decision arrives.
1582
+ * Resolves the pending promise if the `correlationId` matches.
1583
+ * @param correlationId
1584
+ * @param proceed
1585
+ */
1586
+ resolvePendingRequest(correlationId, proceed) {
1587
+ const pending = this.pendingRequest;
1588
+ if (!pending || pending.correlationId !== correlationId) {
1589
+ return;
1590
+ }
1591
+ this.pendingRequest = undefined;
1592
+ pending.resolve(proceed);
1593
+ }
1594
+ /**
1595
+ * @inheritdoc
1596
+ */
1597
+ handleError(message) {
1598
+ this.logger.error('navigation negotiation message could not be consumed by a peer', message);
1599
+ }
1600
+ ngOnDestroy() {
1601
+ if (this.pendingRequest) {
1602
+ this.pendingRequest.resolve(false);
1603
+ this.pendingRequest = undefined;
1604
+ }
1605
+ this.producerManagerService.unregister(this);
1606
+ }
1607
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationRequestManagerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1608
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationRequestManagerService, providedIn: 'root' }); }
1609
+ }
1610
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationRequestManagerService, decorators: [{
1611
+ type: Injectable,
1612
+ args: [{
1613
+ providedIn: 'root'
1614
+ }]
1615
+ }], ctorParameters: () => [] });
1616
+
1617
+ /**
1618
+ * Default module-side `navigation-request` handler. Runs the module's local cleanup — at minimum clearing the block state — and resolves.
1619
+ * Modules cannot refuse a navigation (only the shell modal can), so this always resolves and a `navigation-decision` is always sent back.
1620
+ *
1621
+ * Used as the default factory of {@link NAVIGATION_REQUEST_HANDLER}, so a module gets this behavior without any explicit provider.
1622
+ * Apps that need to persist drafts before unblocking can override the token with their own handler.
1623
+ */
1624
+ const createNavigationRequestHandler = () => {
1625
+ const store = inject(NavigationBlockService);
1626
+ return {
1627
+ handle: () => {
1628
+ store.unblock();
1629
+ }
1630
+ };
1631
+ };
1632
+ /**
1633
+ * Injection token for the per-side request handler.
1634
+ *
1635
+ * **Default behavior (modules):** Clears the block state (unblock) — see {@link createNavigationRequestHandler}.
1636
+ * **Shell override:** Opens confirmation modal and awaits user decision.
1637
+ *
1638
+ * The module handler is provided by default at root level. Only the shell application needs to override it
1639
+ * with `provideNavigationRequestShellHandler()`.
1640
+ */
1641
+ const NAVIGATION_REQUEST_HANDLER = new InjectionToken('NAVIGATION_REQUEST_HANDLER', {
1642
+ providedIn: 'root',
1643
+ factory: createNavigationRequestHandler
1644
+ });
1645
+
1646
+ /**
1647
+ * Agnostic consumer for `navigation-request` messages.
1648
+ *
1649
+ * On each incoming request, invokes the side-specific handler registered under {@link NAVIGATION_REQUEST_HANDLER}:
1650
+ * - **Cockpit side** — handler opens the confirmation modal and awaits the user's answer. If the user cancels the handler
1651
+ * throws; this consumer catches it and replies with a decision carrying `proceed: false`, so the module's pending promise
1652
+ * resolves to `false` and the in-iframe navigation is held until a fresh request is made.
1653
+ * - **Module side** — handler runs the unblock / future draft-persistence logic and resolves. Modules cannot refuse;
1654
+ * a decision is always sent.
1655
+ */
1656
+ class NavigationRequestConsumerService extends AbstractMessageConsumer {
1657
+ constructor() {
1658
+ super(...arguments);
1659
+ /**
1660
+ * @inheritdoc
1661
+ */
1662
+ this.type = NAVIGATION_REQUEST_MESSAGE_TYPE;
1663
+ /**
1664
+ * @inheritdoc
1665
+ */
1666
+ this.supportedVersions = {
1667
+ // eslint-disable-next-line @typescript-eslint/naming-convention -- expected naming for versions
1668
+ '1.0': (message) => this.handleRequest(message)
1669
+ };
1670
+ this.producerService = inject(NavigationRequestManagerService);
1671
+ this.handler = inject(NAVIGATION_REQUEST_HANDLER);
1672
+ }
1673
+ async handleRequest(message) {
1674
+ try {
1675
+ await this.handler.handle({ from: message.from, reason: message.payload.reason });
1676
+ }
1677
+ catch (error) {
1678
+ // Handler rejected — usually the user cancelled, but any unexpected
1679
+ // exception ends up here too. Log it so the failure is observable, then
1680
+ // send a decision with proceed=false so the requester's pending promise
1681
+ // resolves and subsequent navigation attempts can create a fresh request.
1682
+ this.logger.error('navigation-request handler rejected', error, message);
1683
+ this.producerService.sendDecision(message.payload.correlationId, message.from, false);
1684
+ return;
1685
+ }
1686
+ this.producerService.sendDecision(message.payload.correlationId, message.from);
1687
+ }
1688
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationRequestConsumerService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
1689
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationRequestConsumerService, providedIn: 'root' }); }
1690
+ }
1691
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationRequestConsumerService, decorators: [{
1692
+ type: Injectable,
1693
+ args: [{
1694
+ providedIn: 'root'
1695
+ }]
1696
+ }] });
1697
+
1698
+ /**
1699
+ * Provider wiring {@link NAVIGATION_REQUEST_HANDLER} to the module handler.
1700
+ * Not normally needed — the token already defaults to this handler — but
1701
+ * available for apps that want to register it explicitly.
1702
+ */
1703
+ const provideNavigationRequestModuleHandler = () => makeEnvironmentProviders([
1704
+ {
1705
+ provide: NAVIGATION_REQUEST_HANDLER,
1706
+ useFactory: createNavigationRequestHandler
1707
+ }
1708
+ ]);
1709
+
1710
+ /**
1711
+ * Default confirmation strategy using browser's native `window.confirm()`.
1712
+ * Simple, no dependencies, works in any environment. Suitable for:
1713
+ * - Development/testing
1714
+ * - Apps without custom modal libraries
1715
+ * - `@ama-mfe` packages that need basic confirmation
1716
+ *
1717
+ * Production apps typically override this with a modal-based strategy.
1718
+ */
1719
+ class BrowserConfirmationStrategy {
1720
+ /**
1721
+ * Show native browser confirmation dialog.
1722
+ * @param reason Optional reason displayed in the message
1723
+ * @returns Promise<true> if user clicks OK, Promise<false> if user clicks Cancel
1724
+ */
1725
+ confirm(reason) {
1726
+ const message = `${reason ? reason + '\n' : ''}Do you want to leave this page?`;
1727
+ // eslint-disable-next-line no-alert -- intentional use of window.confirm for basic confirmation strategy
1728
+ return Promise.resolve(window.confirm(message));
1729
+ }
1730
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: BrowserConfirmationStrategy, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1731
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: BrowserConfirmationStrategy, providedIn: 'root' }); }
1732
+ }
1733
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: BrowserConfirmationStrategy, decorators: [{
1734
+ type: Injectable,
1735
+ args: [{
1736
+ providedIn: 'root'
1737
+ }]
1738
+ }] });
1739
+
1740
+ /**
1741
+ * Injection token for the navigation block confirmation strategy.
1742
+ * Defaults to {@link BrowserConfirmationStrategy} (uses `window.confirm()`).
1743
+ * Apps with a styled modal override the default by passing their own class via `provideNavigationConfig({ confirmationStrategy })`
1744
+ * or by binding this token directly.
1745
+ */
1746
+ const NAVIGATION_BLOCK_CONFIRMATION = new InjectionToken('NAVIGATION_BLOCK_CONFIRMATION', {
1747
+ providedIn: 'root',
1748
+ factory: () => inject(BrowserConfirmationStrategy)
1749
+ });
1750
+
1751
+ /**
1752
+ * Orchestrates navigation block confirmation by delegating to a pluggable strategy.
1753
+ * Handles concurrent call deduplication to prevent duplicate confirmations when guards run multiple times.
1754
+ *
1755
+ * The confirmation UI is read from the `NAVIGATION_BLOCK_CONFIRMATION` token, which defaults to {@link BrowserConfirmationStrategy}
1756
+ * (no setup required). Apps with a styled modal override the token via `provideNavigationConfig({ confirmationStrategy })`
1757
+ * or by binding the token directly.
1758
+ */
1759
+ class NavigationBlockConfirmationService {
1760
+ constructor() {
1761
+ this.strategy = inject(NAVIGATION_BLOCK_CONFIRMATION);
1762
+ }
1763
+ /**
1764
+ * Show confirmation and await user decision. Concurrent calls return the same promise to prevent duplicate confirmations.
1765
+ * @param reason Optional reason displayed in the confirmation UI
1766
+ * @returns `true` when the user confirms, `false` when they cancel
1767
+ */
1768
+ confirm(reason) {
1769
+ // Deduplication: if confirmation already in progress, return same promise
1770
+ if (this.pendingConfirmation) {
1771
+ return this.pendingConfirmation;
1772
+ }
1773
+ // Delegate to injected strategy. Clear the slot only once the promise
1774
+ // settles, so concurrent callers in the meantime get the same promise.
1775
+ const pending = this.strategy.confirm(reason).finally(() => {
1776
+ this.pendingConfirmation = undefined;
1777
+ });
1778
+ this.pendingConfirmation = pending;
1779
+ return pending;
1780
+ }
1781
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationBlockConfirmationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1782
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationBlockConfirmationService, providedIn: 'root' }); }
1783
+ }
1784
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationBlockConfirmationService, decorators: [{
1785
+ type: Injectable,
1786
+ args: [{
1787
+ providedIn: 'root'
1788
+ }]
1789
+ }] });
1790
+
1791
+ /**
1792
+ * Shell application `navigation-request` handler used when a module initiates the navigation (in-iframe link click).
1793
+ * Opens the shared confirmation modal, awaits the user's answer, and — on confirm — clears the shell's block mirror.
1794
+ * On cancel the handler throws; the consumer catches it and replies with a `navigation-decision` carrying `proceed: false`,
1795
+ * so the module's pending promise resolves to `false` and the iframe navigation is held until a fresh request is made.
1796
+ */
1797
+ const createNavigationRequestShellHandler = () => {
1798
+ const confirmation = inject(NavigationBlockConfirmationService);
1799
+ const blockConsumer = inject(NavigationBlockStateConsumerService);
1800
+ return {
1801
+ handle: async ({ reason }) => {
1802
+ const confirmed = await confirmation.confirm(reason);
1803
+ if (!confirmed) {
1804
+ throw new Error('navigation-request cancelled by user');
1805
+ }
1806
+ blockConsumer.clear();
1807
+ }
1808
+ };
1809
+ };
1810
+ /**
1811
+ * Provider wiring {@link NAVIGATION_REQUEST_HANDLER} to the shell application
1812
+ * handler. Register once in the shell app providers to override the default
1813
+ * module handler.
1814
+ */
1815
+ const provideNavigationRequestShellHandler = () => makeEnvironmentProviders([
1816
+ {
1817
+ provide: NAVIGATION_REQUEST_HANDLER,
1818
+ useFactory: createNavigationRequestShellHandler
1819
+ }
1820
+ ]);
1821
+
1822
+ /**
1823
+ * Agnostic consumer for `navigation-decision` messages — the reply to a
1824
+ * previously sent `navigation-request`. Forwards the decision to
1825
+ * {@link NavigationRequestManagerService.resolvePendingRequest} so
1826
+ * the awaiting caller's promise resolves.
1827
+ */
1828
+ class NavigationDecisionConsumerService extends AbstractMessageConsumer {
1829
+ constructor() {
1830
+ super(...arguments);
1831
+ /**
1832
+ * @inheritdoc
1833
+ */
1834
+ this.type = NAVIGATION_DECISION_MESSAGE_TYPE;
1835
+ /**
1836
+ * @inheritdoc
1837
+ */
1838
+ this.supportedVersions = {
1839
+ // eslint-disable-next-line @typescript-eslint/naming-convention -- expected naming for versions
1840
+ '1.0': (message) => {
1841
+ this.producerService.resolvePendingRequest(message.payload.correlationId, message.payload.proceed);
1842
+ }
1843
+ };
1844
+ this.producerService = inject(NavigationRequestManagerService);
1845
+ }
1846
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationDecisionConsumerService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
1847
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationDecisionConsumerService, providedIn: 'root' }); }
1848
+ }
1849
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NavigationDecisionConsumerService, decorators: [{
1850
+ type: Injectable,
1851
+ args: [{
1852
+ providedIn: 'root'
1853
+ }]
1854
+ }] });
1855
+
1856
+ /**
1857
+ * Shell application guard intercepting shell-initiated navigation (sidebar clicks, URL bar, programmatic `router.navigate`)
1858
+ * when the last state received from a module is `blocked`.
1859
+ *
1860
+ * Can be used as both `canActivate` and `canActivateChild` to ensure the guard runs when entering a route directly
1861
+ * or navigating to its children.
1862
+ *
1863
+ * Flow:
1864
+ * - No state received, or `blocked === false` → navigation proceeds.
1865
+ * - `blocked === true` → open the confirmation modal.
1866
+ * - On cancel/dismiss → return `false`, state is untouched, no message sent to the module.
1867
+ * - On confirm → clear the shell mirror, send a `navigation-request` to the originating module (so it can run its unblock /
1868
+ * future draft-persistence logic), await its `navigation-decision`, then return `true`.
1869
+ */
1870
+ const navigationBlockShellGuard = async () => {
1871
+ const consumer = inject(NavigationBlockStateConsumerService);
1872
+ const confirmation = inject(NavigationBlockConfirmationService);
1873
+ const negotiation = inject(NavigationRequestManagerService);
1874
+ const state = consumer.blockState();
1875
+ if (!state?.blocked) {
1876
+ return true;
1877
+ }
1878
+ const confirmed = await confirmation.confirm(state.reason);
1879
+ if (!confirmed) {
1880
+ return false;
1881
+ }
1882
+ consumer.clear();
1883
+ if (!state.channelId) {
1884
+ return true;
1885
+ }
1886
+ return negotiation.requestNavigation(state.channelId, state.reason);
1887
+ };
1888
+
1889
+ /**
1890
+ * Module-side guard intercepting navigation away from a page that holds unsaved changes.
1891
+ *
1892
+ * - `blocked === false` → navigation proceeds.
1893
+ * - `blocked === true` → broadcast a `navigation-request` to the shell and await the reply. The shell opens the confirmation modal
1894
+ * and replies in both cases: `proceed === true` on confirm, `proceed === false` on cancel/dismiss. On proceed, clear the local
1895
+ * state and return `true`; on cancel, return `false` and leave the block state intact so the navigation is held and a later
1896
+ * attempt can negotiate again.
1897
+ *
1898
+ * Wire it as `canDeactivate` only on the routes that can actually hold unsaved work — modules opt in per page.
1899
+ */
1900
+ const navigationBlockModuleGuard = async () => {
1901
+ const store = inject(NavigationBlockService);
1902
+ const negotiation = inject(NavigationRequestManagerService);
1903
+ const { blocked, reason } = store.state();
1904
+ if (!blocked) {
1905
+ return true;
1906
+ }
1907
+ const proceed = await negotiation.requestNavigation(undefined, reason);
1908
+ if (proceed) {
1909
+ store.unblock();
1910
+ }
1911
+ return proceed;
1912
+ };
1913
+
1914
+ /**
1915
+ * Convenience helper that explicitly binds the {@link NAVIGATION_BLOCK_CONFIRMATION} token to {@link BrowserConfirmationStrategy}.
1916
+ *
1917
+ * **Usually unnecessary** — the token already defaults to `BrowserConfirmationStrategy` via its tree-shakable factory,
1918
+ * so the helper only re-binds the same value. Use it when you want the binding to be visible at the call site
1919
+ * (e.g. for documentation in `app.config.ts`) or when you've previously overridden the token and want to switch back
1920
+ * to the default in a child injector.
1921
+ *
1922
+ * Production apps with a styled modal pass their own strategy via `provideNavigationConfig({ confirmationStrategy })` instead.
1923
+ * @example
1924
+ * ```typescript
1925
+ * // app.config.ts
1926
+ * export const appConfig: ApplicationConfig = {
1927
+ * providers: [
1928
+ * provideDefaultNavigationBlockConfirmation(),
1929
+ * // ... other providers
1930
+ * ]
1931
+ * };
1932
+ * ```
1933
+ */
1934
+ function provideDefaultNavigationBlockConfirmation() {
1935
+ return makeEnvironmentProviders([
1936
+ {
1937
+ provide: NAVIGATION_BLOCK_CONFIRMATION,
1938
+ useClass: BrowserConfirmationStrategy
1939
+ }
1940
+ ]);
1941
+ }
1942
+
1943
+ // Block state (module state, its producer, and the shell-side mirror consumer)
1944
+
1366
1945
  /**
1367
1946
  * This service observe changes in the document's body height.
1368
1947
  * When the height changes, it sends a resize message with the new height, to the connected peers
@@ -1420,7 +1999,6 @@ class ThemeConsumerService extends AbstractMessageConsumer {
1420
1999
  constructor() {
1421
2000
  super();
1422
2001
  this.domSanitizer = inject(DomSanitizer);
1423
- this.logger = inject(LoggerService);
1424
2002
  /**
1425
2003
  * The type of messages this service handles ('theme').
1426
2004
  */
@@ -1447,6 +2025,11 @@ class ThemeConsumerService extends AbstractMessageConsumer {
1447
2025
  }
1448
2026
  }
1449
2027
  };
2028
+ /**
2029
+ * Auto-starts the consumer on creation.
2030
+ * @deprecated The constructor auto-starts the consumer for backwards compatibility. It will be removed in v15;
2031
+ */
2032
+ this.start();
1450
2033
  }
1451
2034
  /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ThemeConsumerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1452
2035
  /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ThemeConsumerService, providedIn: 'root' }); }
@@ -1874,5 +2457,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
1874
2457
  * Generated bundle index. Do not edit.
1875
2458
  */
1876
2459
 
1877
- export { ACTIVITY_EVENTS, AbstractMessageConsumer, ActivityConsumerService, ActivityProducerService, ApplyTheme, ConnectDirective, ConsumerManagerService, DEFAULT_ACTIVITY_PRODUCER_CONFIG, ERROR_MESSAGE_TYPE, HIGH_FREQUENCY_EVENTS, HistoryConsumerService, HostInfoPipe, IFRAME_INTERACTION_EVENT, IframeActivityTrackerService, IframeEmbedComponent, KNOWN_MESSAGES, MFE_HOST_APPLICATION_ID_PARAM, MFE_HOST_URL_PARAM, MFE_MODULE_APPLICATION_ID_PARAM, NavigationConsumerService, ProducerManagerService, ResizeConsumerService, ResizeService, RestoreRoute, RouteMemorizeDirective, RouteMemorizeService, RoutingService, ScalableDirective, THEME_QUERY_PARAM_NAME, THEME_URL_SUFFIX, ThemeConsumerService, ThemeProducerService, VISIBILITY_CHANGE_EVENT, applyInitialTheme, applyTheme, downloadApplicationThemeCss, getAvailableConsumers, getDefaultClientEndpointStartOptions, getHostInfo, getStyle, hostQueryParams, isEmbedded, isErrorMessage, isUserActivityMessage, persistHostInfo, provideConnection, provideHistoryOverrides, registerConsumer, registerProducer, sendError };
2460
+ export { ACTIVITY_EVENTS, AbstractMessageConsumer, ActivityConsumerService, ActivityProducerService, ApplyTheme, BrowserConfirmationStrategy, ConnectDirective, ConsumerManagerService, DEFAULT_ACTIVITY_PRODUCER_CONFIG, ERROR_MESSAGE_TYPE, HIGH_FREQUENCY_EVENTS, HistoryConsumerService, HostInfoPipe, IFRAME_INTERACTION_EVENT, IframeActivityTrackerService, IframeEmbedComponent, KNOWN_MESSAGES, MFE_HOST_APPLICATION_ID_PARAM, MFE_HOST_URL_PARAM, MFE_MODULE_APPLICATION_ID_PARAM, NAVIGATION_BLOCK_CONFIRMATION, NAVIGATION_REQUEST_HANDLER, NavigationBlockConfirmationService, NavigationBlockService, NavigationBlockStateConsumerService, NavigationBlockStateProducerService, NavigationConsumerService, NavigationDecisionConsumerService, NavigationRequestConsumerService, NavigationRequestManagerService, ProducerManagerService, ResizeConsumerService, ResizeService, RestoreRoute, RouteMemorizeDirective, RouteMemorizeService, RoutingService, ScalableDirective, THEME_QUERY_PARAM_NAME, THEME_URL_SUFFIX, ThemeConsumerService, ThemeProducerService, VISIBILITY_CHANGE_EVENT, applyInitialTheme, applyTheme, createNavigationRequestHandler, createNavigationRequestShellHandler, downloadApplicationThemeCss, generateCorrelationId, getAvailableConsumers, getDefaultClientEndpointStartOptions, getHostInfo, getStyle, hostQueryParams, isEmbedded, isErrorMessage, isUserActivityMessage, navigationBlockModuleGuard, navigationBlockShellGuard, persistHostInfo, provideConnection, provideDefaultNavigationBlockConfirmation, provideHistoryOverrides, provideNavigationRequestModuleHandler, provideNavigationRequestShellHandler, registerConsumer, registerProducer, sendError };
1878
2461
  //# sourceMappingURL=ama-mfe-ng-utils.mjs.map