@ama-mfe/ng-utils 14.0.0-next.8 → 14.0.0-next.9

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.
package/README.md CHANGED
@@ -295,3 +295,157 @@ The host information is stored in session storage so it won't be lost when navig
295
295
  When using iframes to embed applications, the browser history might be shared by the main page and the embedded iframe. For example `<iframe src="https://example.com" sandbox="allow-same-origin">` will share the same history as the main page. This can lead to unexpected behavior when using browser 'back' and 'forward' buttons.
296
296
 
297
297
  To avoid this, the `@ama-mfe/ng-utils` will forbid the application running in the iframe to alter the browser history. It will happen when connection is configured using `provideConection()` function. This will prevent the iframe to be able to use the `history.pushState` and `history.replaceState` methods.
298
+
299
+ ### User Activity Tracking
300
+
301
+ The User Activity Tracking feature allows a host application (shell) to monitor user interactions across embedded micro-frontends. This is useful for implementing session timeout functionality, analytics, or any feature that needs to know when users are actively interacting with the application.
302
+
303
+ #### How it works
304
+
305
+ - **Producer**: The `ActivityProducerService` listens for DOM events (click, keydown, scroll, touchstart, focus) and:
306
+ - Exposes a `localActivity` signal for consumers within the same application (not throttled for local detection).
307
+ - Sends throttled activity messages via the communication protocol to connected peers.
308
+ - **Consumer**: The `ActivityConsumerService` receives activity messages from connected peers via the communication protocol and exposes them via the `latestReceivedActivity` signal.
309
+
310
+ Both services can be used in either the shell (host) or embedded applications depending on your use case. For example:
311
+ - A shell can produce activity signals to notify embedded modules of user interactions in the host.
312
+ - An embedded module can consume activity signals from the shell.
313
+ - An application can use the producer's `localActivity` signal to detect activity locally.
314
+
315
+ The service automatically throttles messages sent to peers to prevent flooding the communication channel. High-frequency events like `scroll` have additional throttling.
316
+
317
+ #### Producer Configuration
318
+
319
+ Start the `ActivityProducerService` to send activity signals to connected peers:
320
+
321
+ ```typescript
322
+ import { inject, runInInjectionContext } from '@angular/core';
323
+ import { bootstrapApplication } from '@angular/platform-browser';
324
+ import { ConnectionService, ActivityProducerService } from '@ama-mfe/ng-utils';
325
+
326
+ bootstrapApplication(App, appConfig)
327
+ .then((m) => {
328
+ runInInjectionContext(m.injector, () => {
329
+ if (window.top !== window.self) {
330
+ inject(ConnectionService).connect('hostUniqueID');
331
+ // Start activity tracking with custom throttle
332
+ inject(ActivityProducerService).start({
333
+ throttleMs: 5000 // Send at most one message every 5 seconds
334
+ });
335
+ }
336
+ });
337
+ });
338
+ ```
339
+
340
+ ##### Configuration Options
341
+
342
+ | Option | Type | Default | Description |
343
+ |--------|------|---------|-------------|
344
+ | `throttleMs` | `number` | `1000` | Minimum interval between activity messages sent to the host |
345
+ | `trackNestedIframes` | `boolean` | `false` | Enable tracking of nested iframes within the application |
346
+ | `nestedIframePollIntervalMs` | `number` | `1000` | Polling interval for detecting iframe focus changes |
347
+ | `nestedIframeActivityEmitIntervalMs` | `number` | `30000` | Interval for sending activity signals while an iframe has focus |
348
+ | `highFrequencyThrottleMs` | `number` | `300` | Throttle time for high-frequency events (scroll) |
349
+ | `shouldBroadcast` | `(event: Event) => boolean` | - | Optional filter function to control which events are broadcast |
350
+
351
+ ##### Filtering Events with shouldBroadcast
352
+
353
+ The `shouldBroadcast` option allows you to filter which events trigger activity messages. This is useful in the shell application to exclude events that occur on iframes, since user activity inside embedded modules is already tracked via the communication protocol.
354
+
355
+ ```typescript
356
+ // In the shell application
357
+ inject(ActivityProducerService).start({
358
+ throttleMs: 1000,
359
+ shouldBroadcast: (event: Event) => {
360
+ // Exclude events on iframes - activity from embedded modules comes via the communication protocol
361
+ return !(event.target instanceof HTMLIFrameElement);
362
+ }
363
+ });
364
+ ```
365
+
366
+ ##### Tracking Nested Iframes
367
+
368
+ When a user interacts with content inside an iframe (e.g., a third-party widget, payment form, or embedded content), the parent application cannot detect those interactions directly. This is because:
369
+
370
+ 1. **Cross-origin restrictions**: DOM events inside cross-origin iframes do not bubble up to the parent document.
371
+ 2. **Focus isolation**: When an iframe has focus, the parent document stops receiving keyboard and mouse events.
372
+
373
+ This creates a problem for detection of user interactions: a user could be actively filling out a form inside an iframe, but the host application would see no activity and might incorrectly trigger a session timeout.
374
+
375
+ **Solution**: When `trackNestedIframes` is enabled, the `ActivityProducerService` polls `document.activeElement` to detect when an iframe gains focus. While an iframe has focus, the service simulates activity by periodically emitting `iframeinteraction` events. This ensures the host application knows the user is still active, even though it cannot see the actual interactions inside the iframe.
376
+
377
+ Enable nested iframe tracking in embedded applications that contain other iframes:
378
+
379
+ ```typescript
380
+ inject(ActivityProducerService).start({
381
+ throttleMs: 5000,
382
+ trackNestedIframes: true,
383
+ nestedIframePollIntervalMs: 1000, // Check for iframe focus every second
384
+ nestedIframeActivityEmitIntervalMs: 30000 // Send activity every 30s while iframe has focus
385
+ });
386
+ ```
387
+
388
+ **When to use**: Enable this option in embedded modules that contain iframes whose content you cannot modify to include activity tracking (e.g., third-party widgets, payment providers, or external content).
389
+
390
+ #### Consumer Configuration
391
+
392
+ Start the `ActivityConsumerService` to receive activity signals from connected peers:
393
+
394
+ ```typescript
395
+ import { Component, inject, effect } from '@angular/core';
396
+ import { ActivityConsumerService } from '@ama-mfe/ng-utils';
397
+
398
+ @Component({
399
+ selector: 'app-shell',
400
+ template: '...'
401
+ })
402
+ export class ShellComponent {
403
+ private readonly activityConsumer = inject(ActivityConsumerService);
404
+
405
+ constructor() {
406
+ // Start listening for activity messages
407
+ this.activityConsumer.start();
408
+
409
+ // React to activity changes
410
+ effect(() => {
411
+ const activity = this.activityConsumer.latestReceivedActivity();
412
+ if (activity) {
413
+ console.log(`Activity from ${activity.channelId}: ${activity.eventType} at ${activity.timestamp}`);
414
+ // Reset session timeout, update analytics, etc.
415
+ }
416
+ });
417
+ }
418
+
419
+ ngOnDestroy() {
420
+ this.activityConsumer.stop();
421
+ }
422
+ }
423
+ ```
424
+
425
+ #### Local Activity Signal
426
+
427
+ The `ActivityProducerService` also exposes a `localActivity` signal for detecting activity within the same application:
428
+
429
+ ```typescript
430
+ import { Component, inject, effect } from '@angular/core';
431
+ import { ActivityProducerService } from '@ama-mfe/ng-utils';
432
+
433
+ @Component({
434
+ selector: 'app-root',
435
+ template: '...'
436
+ })
437
+ export class AppComponent {
438
+ private readonly activityProducer = inject(ActivityProducerService);
439
+
440
+ constructor() {
441
+ this.activityProducer.start({ throttleMs: 1000 });
442
+
443
+ effect(() => {
444
+ const activity = this.activityProducer.localActivity();
445
+ if (activity) {
446
+ // React to local activity (not throttled for local detection)
447
+ }
448
+ });
449
+ }
450
+ }
451
+ ```
@@ -4,7 +4,7 @@ import * as i0 from '@angular/core';
4
4
  import { input, inject, ElementRef, computed, SecurityContext, effect, HostBinding, Directive, Injectable, signal, DestroyRef, provideAppInitializer, Pipe, makeEnvironmentProviders, untracked, afterNextRender, Renderer2 } from '@angular/core';
5
5
  import { DomSanitizer } from '@angular/platform-browser';
6
6
  import { LoggerService } from '@o3r/logger';
7
- import { HISTORY_MESSAGE_TYPE, NAVIGATION_MESSAGE_TYPE, RESIZE_MESSAGE_TYPE, THEME_MESSAGE_TYPE } from '@ama-mfe/messages';
7
+ import { HISTORY_MESSAGE_TYPE, USER_ACTIVITY_MESSAGE_TYPE, NAVIGATION_MESSAGE_TYPE, RESIZE_MESSAGE_TYPE, THEME_MESSAGE_TYPE } from '@ama-mfe/messages';
8
8
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
9
9
  import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';
10
10
  import { Subject, filter, map } from 'rxjs';
@@ -444,6 +444,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
444
444
  /** the error message type */
445
445
  const ERROR_MESSAGE_TYPE = 'error';
446
446
 
447
+ /**
448
+ * Type guard to check if a message is a user activity message
449
+ * @param message The message to check
450
+ */
451
+ function isUserActivityMessage(message) {
452
+ return (typeof message === 'object'
453
+ && message !== null
454
+ && 'type' in message
455
+ && message.type === USER_ACTIVITY_MESSAGE_TYPE);
456
+ }
457
+
447
458
  /**
448
459
  * A constant array of known message types and their versions.
449
460
  */
@@ -1238,9 +1249,425 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
1238
1249
  }]
1239
1250
  }], ctorParameters: () => [] });
1240
1251
 
1252
+ /**
1253
+ * DOM events that indicate user activity
1254
+ */
1255
+ const ACTIVITY_EVENTS = [
1256
+ 'click',
1257
+ 'keydown',
1258
+ 'scroll',
1259
+ 'touchstart',
1260
+ 'focus'
1261
+ ];
1262
+ /**
1263
+ * Custom activity event type for iframe interactions.
1264
+ * Emitted programmatically when an iframe gains focus, not from a DOM event listener.
1265
+ */
1266
+ const IFRAME_INTERACTION_EVENT = 'iframeinteraction';
1267
+ /**
1268
+ * Custom activity event type for visibility changes.
1269
+ * Emitted programmatically when the document becomes visible, not from a DOM event listener.
1270
+ */
1271
+ const VISIBILITY_CHANGE_EVENT = 'visibilitychange';
1272
+ /**
1273
+ * High-frequency events that require throttling to avoid performance issues
1274
+ */
1275
+ const HIGH_FREQUENCY_EVENTS = [
1276
+ 'scroll'
1277
+ ];
1278
+ /**
1279
+ * Default configuration values for the ActivityProducerService
1280
+ */
1281
+ const DEFAULT_ACTIVITY_PRODUCER_CONFIG = {
1282
+ /** Default throttle time in milliseconds between activity messages */
1283
+ throttleMs: 1000,
1284
+ /** Default throttle time in milliseconds for high-frequency events */
1285
+ highFrequencyThrottleMs: 300,
1286
+ /** Whether to track nested iframes by default */
1287
+ trackNestedIframes: false,
1288
+ /** Default interval for iframe activity signals (30 seconds) */
1289
+ nestedIframeActivityEmitIntervalMs: 30_000,
1290
+ /** Default polling interval for detecting iframe focus changes (1 second) */
1291
+ nestedIframePollIntervalMs: 1000
1292
+ };
1293
+
1294
+ /**
1295
+ * Service that tracks user activity within nested iframes.
1296
+ *
1297
+ * Polls document.activeElement frequently to detect when an iframe has focus.
1298
+ * When an iframe gains focus, emits immediately and then at the configured interval.
1299
+ * When focus leaves the iframe, it stops emitting.
1300
+ *
1301
+ * This is needed because cross-origin iframes don't fire focus/blur events
1302
+ * that bubble to the parent, and the regular activity tracker can't detect
1303
+ * user interactions inside iframes.
1304
+ */
1305
+ class IframeActivityTrackerService {
1306
+ constructor() {
1307
+ /**
1308
+ * Bound visibility change handler for cleanup
1309
+ */
1310
+ this.visibilityChangeHandler = () => this.handleVisibilityChange();
1311
+ }
1312
+ /**
1313
+ * Whether the service has been started
1314
+ */
1315
+ get started() {
1316
+ return this.config !== undefined;
1317
+ }
1318
+ /**
1319
+ * Whether we are currently tracking iframe activity (iframe had focus on last poll)
1320
+ */
1321
+ get isTrackingIframeActivity() {
1322
+ return this.activityIntervalId !== undefined;
1323
+ }
1324
+ /**
1325
+ * Polls document.activeElement to detect iframe focus changes.
1326
+ * When iframe gains focus: emit immediately and start activity interval.
1327
+ * When iframe loses focus: stop activity interval.
1328
+ */
1329
+ checkActiveElement() {
1330
+ const activeElement = document.activeElement;
1331
+ const iframeFocused = activeElement instanceof HTMLIFrameElement;
1332
+ if (iframeFocused && !this.isTrackingIframeActivity) {
1333
+ // Iframe just gained focus - emit immediately and start activity interval
1334
+ this.config?.onActivity();
1335
+ this.startActivityInterval();
1336
+ }
1337
+ else if (!iframeFocused && this.isTrackingIframeActivity) {
1338
+ // Focus left the iframe - stop activity interval
1339
+ this.stopActivityInterval();
1340
+ }
1341
+ }
1342
+ /**
1343
+ * Handles visibility change events to pause/resume polling when tab visibility changes.
1344
+ */
1345
+ handleVisibilityChange() {
1346
+ if (document.visibilityState === 'visible') {
1347
+ this.startPolling();
1348
+ }
1349
+ else {
1350
+ this.stopPolling();
1351
+ this.stopActivityInterval();
1352
+ }
1353
+ }
1354
+ /**
1355
+ * Starts polling for active element changes.
1356
+ */
1357
+ startPolling() {
1358
+ if (this.pollIntervalId) {
1359
+ return;
1360
+ }
1361
+ this.pollIntervalId = setInterval(() => this.checkActiveElement(), this.config.pollIntervalMs);
1362
+ }
1363
+ /**
1364
+ * Stops polling for active element changes.
1365
+ */
1366
+ stopPolling() {
1367
+ if (this.pollIntervalId) {
1368
+ clearInterval(this.pollIntervalId);
1369
+ this.pollIntervalId = undefined;
1370
+ }
1371
+ }
1372
+ /**
1373
+ * Starts the activity emission interval.
1374
+ */
1375
+ startActivityInterval() {
1376
+ this.stopActivityInterval();
1377
+ this.activityIntervalId = setInterval(() => {
1378
+ this.config?.onActivity();
1379
+ }, this.config.activityIntervalMs);
1380
+ }
1381
+ /**
1382
+ * Stops the activity emission interval.
1383
+ */
1384
+ stopActivityInterval() {
1385
+ if (this.activityIntervalId) {
1386
+ clearInterval(this.activityIntervalId);
1387
+ this.activityIntervalId = undefined;
1388
+ }
1389
+ }
1390
+ /**
1391
+ * Starts tracking nested iframes within the document.
1392
+ * @param config Configuration for the tracker
1393
+ */
1394
+ start(config) {
1395
+ if (this.started) {
1396
+ return;
1397
+ }
1398
+ this.config = config;
1399
+ // Listen for visibility changes to pause/resume polling
1400
+ document.addEventListener('visibilitychange', this.visibilityChangeHandler);
1401
+ // Only start polling if document is currently visible
1402
+ if (document.visibilityState === 'visible') {
1403
+ this.startPolling();
1404
+ }
1405
+ }
1406
+ /**
1407
+ * Stops tracking nested iframes and cleans up resources.
1408
+ */
1409
+ stop() {
1410
+ if (!this.started) {
1411
+ return;
1412
+ }
1413
+ document.removeEventListener('visibilitychange', this.visibilityChangeHandler);
1414
+ this.stopPolling();
1415
+ this.stopActivityInterval();
1416
+ this.config = undefined;
1417
+ }
1418
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: IframeActivityTrackerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1419
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: IframeActivityTrackerService, providedIn: 'root' }); }
1420
+ }
1421
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: IframeActivityTrackerService, decorators: [{
1422
+ type: Injectable,
1423
+ args: [{
1424
+ providedIn: 'root'
1425
+ }]
1426
+ }] });
1427
+
1428
+ /**
1429
+ * Generic service that tracks user activity and sends messages.
1430
+ * Can be configured for different contexts (cockpit or modules) via start() method parameter.
1431
+ */
1432
+ class ActivityProducerService {
1433
+ constructor() {
1434
+ this.messageService = inject(MessagePeerService);
1435
+ this.destroyRef = inject(DestroyRef);
1436
+ this.iframeActivityTracker = inject(IframeActivityTrackerService);
1437
+ this.logger = inject(LoggerService);
1438
+ /**
1439
+ * Timestamp of the last sent activity message
1440
+ */
1441
+ this.lastSentTimestamp = 0;
1442
+ /**
1443
+ * Bound event listeners for cleanup
1444
+ */
1445
+ this.boundListeners = new Map();
1446
+ /**
1447
+ * Last emission timestamps for throttled high-frequency events
1448
+ */
1449
+ this.lastEmitTimestamps = new Map();
1450
+ /**
1451
+ * Whether the service has been started
1452
+ */
1453
+ this.started = false;
1454
+ /**
1455
+ * Signal that emits local activity information.
1456
+ * This allows consumers to react to activity detected by this producer.
1457
+ */
1458
+ this.localActivityWritable = signal(undefined, ...(ngDevMode ? [{ debugName: "localActivityWritable" }] : []));
1459
+ /**
1460
+ * Read-only signal containing the latest local activity info.
1461
+ * Use this signal to react to locally detected activity.
1462
+ */
1463
+ this.localActivity = this.localActivityWritable.asReadonly();
1464
+ /**
1465
+ * @inheritdoc
1466
+ */
1467
+ this.types = USER_ACTIVITY_MESSAGE_TYPE;
1468
+ registerProducer(this);
1469
+ this.destroyRef.onDestroy(() => this.stop());
1470
+ }
1471
+ /**
1472
+ * Handles high-frequency events by applying a per-eventType throttle before calling onActivity.
1473
+ *
1474
+ * Difference with onActivity:
1475
+ * - onActivityThrottled limits how often a given high-frequency event type (e.g. scroll) is processed
1476
+ * (based on highFrequencyThrottleMs and lastEmitTimestamps)
1477
+ * - onActivity updates the local activity signal and applies the global message throttle
1478
+ * (based on global throttleMs and lastSentTimestamp)
1479
+ * @param eventType The type of activity event that occurred
1480
+ * @param configObject
1481
+ */
1482
+ onActivityThrottled(eventType, configObject) {
1483
+ const now = Date.now();
1484
+ const lastEmit = this.lastEmitTimestamps.get(eventType) ?? 0;
1485
+ const throttleMs = configObject.highFrequencyThrottleMs;
1486
+ if (now - lastEmit >= throttleMs) {
1487
+ this.lastEmitTimestamps.set(eventType, now);
1488
+ this.onActivity(eventType, configObject);
1489
+ }
1490
+ }
1491
+ /**
1492
+ * Handles activity by sending a throttled message and emitting to local signal.
1493
+ * @param eventType The type of activity event that occurred
1494
+ * @param configObject
1495
+ */
1496
+ onActivity(eventType, configObject) {
1497
+ const now = Date.now();
1498
+ // Always emit local activity signal (not throttled for local detection)
1499
+ this.localActivityWritable.set({
1500
+ channelId: 'local',
1501
+ eventType,
1502
+ timestamp: now
1503
+ });
1504
+ // Send message with throttling
1505
+ if (now - this.lastSentTimestamp >= configObject.throttleMs) {
1506
+ this.lastSentTimestamp = now;
1507
+ this.sendActivityMessage(eventType, now);
1508
+ }
1509
+ }
1510
+ /**
1511
+ * Sends an activity message.
1512
+ * @param eventType The type of activity event
1513
+ * @param timestamp The timestamp of the event
1514
+ */
1515
+ sendActivityMessage(eventType, timestamp) {
1516
+ const message = {
1517
+ type: USER_ACTIVITY_MESSAGE_TYPE,
1518
+ version: '1.0',
1519
+ eventType,
1520
+ timestamp
1521
+ };
1522
+ const registeredPeersIdsForUserActivity = [...this.messageService.knownPeers.entries()]
1523
+ .filter(([peerId]) => peerId !== this.messageService.id)
1524
+ .filter(([, messages]) => messages.some((msg) => msg.type === USER_ACTIVITY_MESSAGE_TYPE))
1525
+ .map((peer) => peer[0]);
1526
+ // send messages to the peers waiting for user activity
1527
+ // avoids sending the message to modules which are not using it
1528
+ if (registeredPeersIdsForUserActivity.length > 0) {
1529
+ this.messageService.send(message, { to: registeredPeersIdsForUserActivity });
1530
+ }
1531
+ }
1532
+ /**
1533
+ * @inheritdoc
1534
+ */
1535
+ handleError(message) {
1536
+ this.logger.error('Error in user activity service message', message);
1537
+ }
1538
+ /**
1539
+ * Starts observing user activity events.
1540
+ * When activity is detected, it sends a throttled message.
1541
+ * Event listeners are attached after the next render to ensure DOM is ready.
1542
+ * @param config Configuration for the activity producer
1543
+ */
1544
+ start(config) {
1545
+ if (this.started) {
1546
+ return;
1547
+ }
1548
+ this.started = true;
1549
+ const configObject = { ...DEFAULT_ACTIVITY_PRODUCER_CONFIG, ...config };
1550
+ // Always use afterNextRender to ensure DOM is ready in all contexts
1551
+ afterNextRender(() => {
1552
+ ACTIVITY_EVENTS.forEach((eventType) => {
1553
+ const isHighFrequency = HIGH_FREQUENCY_EVENTS.includes(eventType);
1554
+ const listener = (event) => {
1555
+ // do nothing if the event is a key kept pressed
1556
+ if (eventType === 'keydown' && event instanceof KeyboardEvent && event.repeat) {
1557
+ return;
1558
+ }
1559
+ // Apply filter if provided
1560
+ if (configObject.shouldBroadcast?.(event) === false) {
1561
+ return;
1562
+ }
1563
+ if (isHighFrequency) {
1564
+ this.onActivityThrottled(eventType, configObject);
1565
+ }
1566
+ else {
1567
+ this.onActivity(eventType, configObject);
1568
+ }
1569
+ };
1570
+ this.boundListeners.set(eventType, listener);
1571
+ document.addEventListener(eventType, listener, { passive: true, capture: true });
1572
+ });
1573
+ // Also listen for visibility changes
1574
+ const visibilityListener = () => {
1575
+ if (document.visibilityState === 'visible') {
1576
+ this.onActivity(VISIBILITY_CHANGE_EVENT, configObject);
1577
+ }
1578
+ };
1579
+ this.boundListeners.set(VISIBILITY_CHANGE_EVENT, visibilityListener);
1580
+ document.addEventListener(VISIBILITY_CHANGE_EVENT, visibilityListener, { passive: true, capture: true });
1581
+ // Set up nested iframe tracking if enabled
1582
+ if (configObject.trackNestedIframes) {
1583
+ this.iframeActivityTracker.start({
1584
+ pollIntervalMs: configObject.nestedIframePollIntervalMs,
1585
+ activityIntervalMs: configObject.nestedIframeActivityEmitIntervalMs,
1586
+ onActivity: () => this.onActivity(IFRAME_INTERACTION_EVENT, configObject)
1587
+ });
1588
+ }
1589
+ });
1590
+ }
1591
+ /**
1592
+ * Stops observing user activity events.
1593
+ */
1594
+ stop() {
1595
+ this.boundListeners.forEach((listener, eventType) => {
1596
+ document.removeEventListener(eventType, listener, { capture: true });
1597
+ });
1598
+ this.boundListeners.clear();
1599
+ this.lastEmitTimestamps.clear();
1600
+ this.iframeActivityTracker.stop();
1601
+ }
1602
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ActivityProducerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1603
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ActivityProducerService, providedIn: 'root' }); }
1604
+ }
1605
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ActivityProducerService, decorators: [{
1606
+ type: Injectable,
1607
+ args: [{
1608
+ providedIn: 'root'
1609
+ }]
1610
+ }], ctorParameters: () => [] });
1611
+
1612
+ /**
1613
+ * Generic service that consumes user activity messages.
1614
+ * Can be used in both shell (to receive from modules) and modules (to receive from shell).
1615
+ */
1616
+ class ActivityConsumerService {
1617
+ constructor() {
1618
+ this.consumerManagerService = inject(ConsumerManagerService);
1619
+ /**
1620
+ * Signal containing the latest activity info
1621
+ */
1622
+ this.latestReceivedActivityWritable = signal(undefined, ...(ngDevMode ? [{ debugName: "latestReceivedActivityWritable" }] : []));
1623
+ /**
1624
+ * Read-only signal containing the latest activity info received from other peers via the message protocol.
1625
+ * Access the timestamp via latestReceivedActivity()?.timestamp
1626
+ */
1627
+ this.latestReceivedActivity = this.latestReceivedActivityWritable.asReadonly();
1628
+ /**
1629
+ * @inheritdoc
1630
+ */
1631
+ this.type = USER_ACTIVITY_MESSAGE_TYPE;
1632
+ /**
1633
+ * @inheritdoc
1634
+ */
1635
+ this.supportedVersions = {
1636
+ // eslint-disable-next-line @typescript-eslint/naming-convention -- Version keys follow message versioning convention
1637
+ '1.0': (message) => {
1638
+ this.latestReceivedActivityWritable.set({
1639
+ channelId: message.from || 'unknown',
1640
+ eventType: message.payload.eventType,
1641
+ timestamp: message.payload.timestamp
1642
+ });
1643
+ }
1644
+ };
1645
+ }
1646
+ /**
1647
+ * Starts the activity consumer service by registering it with the consumer manager.
1648
+ */
1649
+ start() {
1650
+ this.consumerManagerService.register(this);
1651
+ }
1652
+ /**
1653
+ * Stops the activity consumer service by unregistering it from the consumer manager.
1654
+ */
1655
+ stop() {
1656
+ this.consumerManagerService.unregister(this);
1657
+ }
1658
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ActivityConsumerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1659
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ActivityConsumerService, providedIn: 'root' }); }
1660
+ }
1661
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ActivityConsumerService, decorators: [{
1662
+ type: Injectable,
1663
+ args: [{
1664
+ providedIn: 'root'
1665
+ }]
1666
+ }] });
1667
+
1241
1668
  /**
1242
1669
  * Generated bundle index. Do not edit.
1243
1670
  */
1244
1671
 
1245
- export { ApplyTheme, ConnectDirective, ConsumerManagerService, ERROR_MESSAGE_TYPE, HistoryConsumerService, HostInfoPipe, 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, applyInitialTheme, applyTheme, downloadApplicationThemeCss, getAvailableConsumers, getDefaultClientEndpointStartOptions, getHostInfo, getStyle, hostQueryParams, isEmbedded, isErrorMessage, persistHostInfo, provideConnection, provideHistoryOverrides, registerConsumer, registerProducer, sendError };
1672
+ export { ACTIVITY_EVENTS, ActivityConsumerService, ActivityProducerService, ApplyTheme, ConnectDirective, ConsumerManagerService, DEFAULT_ACTIVITY_PRODUCER_CONFIG, ERROR_MESSAGE_TYPE, HIGH_FREQUENCY_EVENTS, HistoryConsumerService, HostInfoPipe, IFRAME_INTERACTION_EVENT, IframeActivityTrackerService, 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 };
1246
1673
  //# sourceMappingURL=ama-mfe-ng-utils.mjs.map