@ama-mfe/ng-utils 14.0.0-next.2 → 14.0.0-next.3

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,13 +1,76 @@
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 { Injectable, inject, signal, effect, DestroyRef, afterNextRender, input, computed, ElementRef, Renderer2, Directive, SecurityContext, Pipe, untracked, HostBinding, provideAppInitializer, makeEnvironmentProviders } from '@angular/core';
5
- import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
4
+ import { input, inject, ElementRef, computed, SecurityContext, effect, HostBinding, Directive, Injectable, signal, DestroyRef, provideAppInitializer, Pipe, makeEnvironmentProviders, untracked, afterNextRender, Renderer2 } from '@angular/core';
5
+ import { DomSanitizer } from '@angular/platform-browser';
6
6
  import { LoggerService } from '@o3r/logger';
7
- import { RESIZE_MESSAGE_TYPE, NAVIGATION_MESSAGE_TYPE, THEME_MESSAGE_TYPE } from '@ama-mfe/messages';
7
+ import { HISTORY_MESSAGE_TYPE, NAVIGATION_MESSAGE_TYPE, RESIZE_MESSAGE_TYPE, THEME_MESSAGE_TYPE } from '@ama-mfe/messages';
8
+ import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
8
9
  import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';
9
10
  import { Subject, filter, map } from 'rxjs';
10
- import { DomSanitizer } from '@angular/platform-browser';
11
+
12
+ class ConnectDirective {
13
+ /**
14
+ * Binds the `src` attribute of the iframe to the sanitized source URL.
15
+ */
16
+ get srcAttr() {
17
+ return this.src();
18
+ }
19
+ constructor() {
20
+ /**
21
+ * The connection ID required for the message peer service.
22
+ */
23
+ this.connect = input.required(...(ngDevMode ? [{ debugName: "connect" }] : []));
24
+ /**
25
+ * The sanitized source URL for the iframe.
26
+ */
27
+ this.src = input(...(ngDevMode ? [undefined, { debugName: "src" }] : []));
28
+ this.messageService = inject(MessagePeerService);
29
+ this.domSanitizer = inject(DomSanitizer);
30
+ this.iframeElement = inject(ElementRef).nativeElement;
31
+ this.clientOrigin = computed(() => {
32
+ const src = this.src();
33
+ const srcString = src && this.domSanitizer.sanitize(SecurityContext.RESOURCE_URL, src);
34
+ return srcString && new URL(srcString).origin;
35
+ }, ...(ngDevMode ? [{ debugName: "clientOrigin" }] : []));
36
+ const logger = inject(LoggerService);
37
+ // When the origin or connection ID change - reconnect the message service
38
+ effect((onCleanup) => {
39
+ let stopHandshakeListening = () => { };
40
+ const origin = this.clientOrigin();
41
+ const id = this.connect();
42
+ const source = this.iframeElement.contentWindow;
43
+ // listen for handshakes only if we know the origin and were given a connection ID
44
+ if (origin && source && id) {
45
+ try {
46
+ stopHandshakeListening = this.messageService.listen({ id, source, origin });
47
+ }
48
+ catch (e) {
49
+ logger.error(`Failed to start listening for (connection ID: ${id})`, e);
50
+ }
51
+ }
52
+ // stop listening for handshakes and disconnect previous connection when:
53
+ // - origin/connection ID change
54
+ // - the directive is destroyed
55
+ onCleanup(() => {
56
+ stopHandshakeListening();
57
+ this.messageService.disconnect();
58
+ });
59
+ });
60
+ }
61
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ConnectDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
62
+ /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.13", type: ConnectDirective, isStandalone: true, selector: "iframe[connect]", inputs: { connect: { classPropertyName: "connect", publicName: "connect", isSignal: true, isRequired: true, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "src": "this.srcAttr" } }, ngImport: i0 }); }
63
+ }
64
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ConnectDirective, decorators: [{
65
+ type: Directive,
66
+ args: [{
67
+ selector: 'iframe[connect]',
68
+ standalone: true
69
+ }]
70
+ }], ctorParameters: () => [], propDecorators: { connect: [{ type: i0.Input, args: [{ isSignal: true, alias: "connect", required: true }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], srcAttr: [{
71
+ type: HostBinding,
72
+ args: ['src']
73
+ }] } });
11
74
 
12
75
  /**
13
76
  * Gets the available consumers and formats them into a {@see DeclareMessages} object.
@@ -74,14 +137,16 @@ class ProducerManagerService {
74
137
  .filter(({ types }) => (Array.isArray(types) ? types : [types]).includes(message.source.type));
75
138
  const handlersPresent = handlers.length > 0;
76
139
  if (handlersPresent) {
77
- await Promise.all(handlers.map((handler) => handler.handleError(message)));
140
+ await Promise.all(
141
+ // eslint-disable-next-line @typescript-eslint/await-thenable -- `handleError` can return void or Promise<void>
142
+ handlers.map((handler) => handler.handleError(message)));
78
143
  }
79
144
  return handlersPresent;
80
145
  }
81
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ProducerManagerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
82
- /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ProducerManagerService, providedIn: 'root' }); }
146
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ProducerManagerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
147
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ProducerManagerService, providedIn: 'root' }); }
83
148
  }
84
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ProducerManagerService, decorators: [{
149
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ProducerManagerService, decorators: [{
85
150
  type: Injectable,
86
151
  args: [{
87
152
  providedIn: 'root'
@@ -173,10 +238,10 @@ class ConsumerManagerService {
173
238
  return consumers.filter((c) => c !== consumer);
174
239
  });
175
240
  }
176
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ConsumerManagerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
177
- /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ConsumerManagerService, providedIn: 'root' }); }
241
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ConsumerManagerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
242
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ConsumerManagerService, providedIn: 'root' }); }
178
243
  }
179
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ConsumerManagerService, decorators: [{
244
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ConsumerManagerService, decorators: [{
180
245
  type: Injectable,
181
246
  args: [{
182
247
  providedIn: 'root'
@@ -208,53 +273,49 @@ const registerConsumer = (consumer) => {
208
273
  });
209
274
  };
210
275
 
211
- /** the error message type */
212
- const ERROR_MESSAGE_TYPE = 'error';
213
-
214
276
  /**
215
- * This service listens for resize messages and updates the height of elements based on the received messages.
277
+ * A service that handles history messages.
278
+ *
279
+ * This service listens for history messages and navigates accordingly.
216
280
  */
217
- class ResizeConsumerService {
281
+ class HistoryConsumerService {
218
282
  constructor() {
219
- this.newHeight = signal(undefined, ...(ngDevMode ? [{ debugName: "newHeight" }] : []));
220
- /**
221
- * A readonly signal that provides the new height information from the channel.
222
- */
223
- this.newHeightFromChannel = this.newHeight.asReadonly();
224
283
  /**
225
- * The type of messages this service handles ('resize').
284
+ * The type of messages this service handles.
226
285
  */
227
- this.type = RESIZE_MESSAGE_TYPE;
286
+ this.type = HISTORY_MESSAGE_TYPE;
228
287
  /**
229
- * The supported versions of resize messages and their handlers.
288
+ * @inheritdoc
230
289
  */
231
290
  this.supportedVersions = {
232
291
  /**
233
- * Use the message paylod to compute a new height and emit it via the public signal
292
+ * Use the message payload to navigate in the history
234
293
  * @param message message to consume
235
294
  */
236
- '1.0': (message) => this.newHeight.set({ height: message.payload.height, channelId: message.from })
295
+ '1.0': (message) => {
296
+ history.go(message.payload.delta);
297
+ }
237
298
  };
238
299
  this.consumerManagerService = inject(ConsumerManagerService);
239
300
  this.start();
240
301
  inject(DestroyRef).onDestroy(() => this.stop());
241
302
  }
242
303
  /**
243
- * Starts the resize handler service by registering it into the consumer manager service.
304
+ * @inheritdoc
244
305
  */
245
306
  start() {
246
307
  this.consumerManagerService.register(this);
247
308
  }
248
309
  /**
249
- * Stops the resize handler service by unregistering it from the consumer manager service.
310
+ * @inheritdoc
250
311
  */
251
312
  stop() {
252
313
  this.consumerManagerService.unregister(this);
253
314
  }
254
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ResizeConsumerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
255
- /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ResizeConsumerService, providedIn: 'root' }); }
315
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: HistoryConsumerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
316
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: HistoryConsumerService, providedIn: 'root' }); }
256
317
  }
257
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ResizeConsumerService, decorators: [{
318
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: HistoryConsumerService, decorators: [{
258
319
  type: Injectable,
259
320
  args: [{
260
321
  providedIn: 'root'
@@ -262,102 +323,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImpor
262
323
  }], ctorParameters: () => [] });
263
324
 
264
325
  /**
265
- * This service observe changes in the document's body height.
266
- * When the height changes, it sends a resize message with the new height, to the connected peers
326
+ * Provides necessary overrides to make the module navigation in history work in an embedded context :
327
+ * - Prevent pushing states to history, replace state instead
328
+ * - Handle history navigation via History messages to let the host manage the states
267
329
  */
268
- class ResizeService {
269
- constructor() {
270
- this.messageService = inject((MessagePeerService));
271
- /**
272
- * @inheritdoc
273
- */
274
- this.types = RESIZE_MESSAGE_TYPE;
275
- registerProducer(this);
276
- }
277
- /**
278
- * @inheritdoc
279
- */
280
- handleError(message) {
281
- // eslint-disable-next-line no-console -- error handling placeholder
282
- console.error('Error in resize service message', message);
283
- }
284
- /**
285
- * This method sets up a `ResizeObserver` to observe changes in the document's body height.
286
- * When the height changes, it sends a resize message with the new height, to the connected peers
287
- */
288
- startResizeObserver() {
289
- this.resizeObserver = new ResizeObserver(() => {
290
- const newHeight = document.body.getBoundingClientRect().height;
291
- if (!this.actualHeight || newHeight !== this.actualHeight) {
292
- this.actualHeight = newHeight;
293
- const messageV10 = {
294
- type: 'resize',
295
- version: '1.0',
296
- height: this.actualHeight
297
- };
298
- // TODO: sendBest() is not implemented -- https://github.com/AmadeusITGroup/microfrontends/issues/11
299
- this.messageService.send(messageV10);
300
- }
330
+ function provideHistoryOverrides() {
331
+ return provideAppInitializer(() => {
332
+ const messageService = inject((MessagePeerService));
333
+ const navigate = (delta) => {
334
+ messageService.send({
335
+ type: 'history',
336
+ version: '1.0',
337
+ delta
338
+ });
339
+ };
340
+ Object.defineProperty(history, 'pushState', {
341
+ value: (data, unused, url) => {
342
+ history.replaceState(data, unused, url);
343
+ },
344
+ writable: false,
345
+ configurable: false
301
346
  });
302
- afterNextRender(() => this.resizeObserver?.observe(document.body));
303
- }
304
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ResizeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
305
- /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ResizeService, providedIn: 'root' }); }
306
- }
307
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ResizeService, decorators: [{
308
- type: Injectable,
309
- args: [{
310
- providedIn: 'root'
311
- }]
312
- }], ctorParameters: () => [] });
313
-
314
- /**
315
- * A directive that adjusts the height of an element based on resize messages from a specified channel.
316
- */
317
- class ScalableDirective {
318
- constructor() {
319
- /**
320
- * The connection ID for the element, used as channel id backup
321
- */
322
- this.connect = input(...(ngDevMode ? [undefined, { debugName: "connect" }] : []));
323
- /**
324
- * The channel id
325
- */
326
- this.scalable = input(...(ngDevMode ? [undefined, { debugName: "scalable" }] : []));
327
- this.resizeHandler = inject(ResizeConsumerService);
328
- /**
329
- * This signal checks if the current channel requesting the resize matches the channel ID from the resize handler.
330
- * If they match, it returns the new height information; otherwise, it returns undefined.
331
- */
332
- this.newHeightFromChannel = computed(() => {
333
- const channelAskingResize = this.scalable() || this.connect();
334
- const newHeightFromChannel = this.resizeHandler.newHeightFromChannel();
335
- if (channelAskingResize && newHeightFromChannel?.channelId === channelAskingResize) {
336
- return newHeightFromChannel;
337
- }
338
- return undefined;
339
- }, ...(ngDevMode ? [{ debugName: "newHeightFromChannel" }] : []));
340
- const elem = inject(ElementRef);
341
- const renderer = inject(Renderer2);
342
- this.resizeHandler.start();
343
- /** When a new height value is received set the height of the host element (in pixels) */
344
- effect(() => {
345
- const newHeightFromChannel = this.newHeightFromChannel();
346
- if (newHeightFromChannel) {
347
- renderer.setStyle(elem.nativeElement, 'height', `${newHeightFromChannel.height}px`);
348
- }
347
+ Object.defineProperty(history, 'back', {
348
+ value: () => navigate(-1),
349
+ writable: false,
350
+ configurable: false
349
351
  });
350
- }
351
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ScalableDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
352
- /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.2.4", type: ScalableDirective, isStandalone: true, selector: "[scalable]", inputs: { connect: { classPropertyName: "connect", publicName: "connect", isSignal: true, isRequired: false, transformFunction: null }, scalable: { classPropertyName: "scalable", publicName: "scalable", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
352
+ Object.defineProperty(history, 'forward', {
353
+ value: () => navigate(1),
354
+ writable: false,
355
+ configurable: false
356
+ });
357
+ Object.defineProperty(history, 'go', {
358
+ value: (delta) => navigate(delta),
359
+ writable: false,
360
+ configurable: false
361
+ });
362
+ });
353
363
  }
354
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ScalableDirective, decorators: [{
355
- type: Directive,
356
- args: [{
357
- selector: '[scalable]',
358
- standalone: true
359
- }]
360
- }], ctorParameters: () => [] });
361
364
 
362
365
  const SESSION_STORAGE_KEY = 'ama-mfe-host-info';
363
366
  /**
@@ -384,12 +387,13 @@ const hostQueryParams = [MFE_HOST_URL_PARAM, MFE_HOST_APPLICATION_ID_PARAM, MFE_
384
387
  * - use `document.referrer` (will only work if called before any redirection in the iframe)
385
388
  * The host application ID is taken from the search parameter {@link MFE_HOST_APPLICATION_ID_PARAM} in the URL of the iframe
386
389
  * The module application ID is taken from the search parameter {@link MFE_APPLICATION_ID_PARAM} in the URL of the iframe
390
+ * @param locationParam - A {@link Location} object with information about the current location of the document. Defaults to global {@link location}.
387
391
  */
388
- function getHostInfo() {
389
- const searchParams = new URLSearchParams(location.search);
392
+ function getHostInfo(locationParam = location) {
393
+ const searchParams = new URLSearchParams(locationParam.search);
390
394
  const storedHostInfo = JSON.parse(sessionStorage.getItem(SESSION_STORAGE_KEY) || '{}');
391
395
  return {
392
- hostURL: searchParams.get(MFE_HOST_URL_PARAM) || storedHostInfo.hostURL || location.ancestorOrigins?.[0] || document.referrer,
396
+ hostURL: searchParams.get(MFE_HOST_URL_PARAM) || storedHostInfo.hostURL || locationParam.ancestorOrigins?.[0] || document.referrer,
393
397
  hostApplicationId: searchParams.get(MFE_HOST_APPLICATION_ID_PARAM) || storedHostInfo.hostApplicationId,
394
398
  moduleApplicationId: searchParams.get(MFE_MODULE_APPLICATION_ID_PARAM) || storedHostInfo.moduleApplicationId
395
399
  };
@@ -427,16 +431,81 @@ class HostInfoPipe {
427
431
  return typeof url === 'string' ? moduleUrlStringyfied : this.domSanitizer.bypassSecurityTrustResourceUrl(moduleUrlStringyfied);
428
432
  }
429
433
  }
430
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: HostInfoPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
431
- /** @nocollapse */ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.2.4", ngImport: i0, type: HostInfoPipe, isStandalone: true, name: "hostInfo" }); }
434
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: HostInfoPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
435
+ /** @nocollapse */ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.13", ngImport: i0, type: HostInfoPipe, isStandalone: true, name: "hostInfo" }); }
432
436
  }
433
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: HostInfoPipe, decorators: [{
437
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: HostInfoPipe, decorators: [{
434
438
  type: Pipe,
435
439
  args: [{
436
440
  name: 'hostInfo'
437
441
  }]
438
442
  }] });
439
443
 
444
+ /** the error message type */
445
+ const ERROR_MESSAGE_TYPE = 'error';
446
+
447
+ /**
448
+ * A constant array of known message types and their versions.
449
+ */
450
+ const KNOWN_MESSAGES = [
451
+ {
452
+ type: ERROR_MESSAGE_TYPE,
453
+ version: '1.0'
454
+ }
455
+ ];
456
+ /**
457
+ * Returns the default options for starting a client endpoint peer connection.
458
+ * As `origin`, it will take the hostURL from {@link getHostInfo} and the `window` will be the parent window.
459
+ */
460
+ function getDefaultClientEndpointStartOptions() {
461
+ const hostInfo = getHostInfo();
462
+ if (hostInfo.hostURL) {
463
+ return {
464
+ origin: new URL(hostInfo.hostURL).origin,
465
+ window: window.parent
466
+ };
467
+ }
468
+ return {};
469
+ }
470
+ /**
471
+ * Return `true` if embedded inside an iframe, `false` otherwise
472
+ * @param windowParam - A {@link window} object with information about the current window of the document. Defaults to global {@link window}.
473
+ */
474
+ function isEmbedded(windowParam = window) {
475
+ return windowParam.top !== windowParam.self;
476
+ }
477
+
478
+ /**
479
+ * Provide the communication protocol connection configuration
480
+ * @param connectionConfigOptions The identifier of the application in the communication protocol ecosystem plus the types of messages able to exchange and a logger object
481
+ */
482
+ function provideConnection(connectionConfigOptions) {
483
+ persistHostInfo();
484
+ const connectionId = (isEmbedded() && getHostInfo().moduleApplicationId) || connectionConfigOptions?.id;
485
+ if (!connectionId) {
486
+ (connectionConfigOptions?.logger || console).error('An id (moduleId) needs to be provided for the application in order to establish a connection inside the communication protocol');
487
+ return makeEnvironmentProviders([]);
488
+ }
489
+ const config = {
490
+ id: connectionId,
491
+ messageCheckStrategy: 'version',
492
+ knownMessages: [...KNOWN_MESSAGES, ...(connectionConfigOptions?.knownMessages || [])]
493
+ };
494
+ return makeEnvironmentProviders([
495
+ {
496
+ provide: MESSAGE_PEER_CONFIG, useValue: config
497
+ },
498
+ {
499
+ provide: MESSAGE_PEER_CONNECT_OPTIONS, useValue: getDefaultClientEndpointStartOptions()
500
+ },
501
+ {
502
+ // in the case of the ConnectionService will extend the base service 'useExisting' should be used
503
+ provide: MessagePeerService, useClass: MessagePeerService, deps: [MESSAGE_PEER_CONFIG]
504
+ },
505
+ ...isEmbedded() ? [provideHistoryOverrides()] : []
506
+ ]);
507
+ }
508
+
440
509
  /**
441
510
  * A service that handles navigation messages and routing.
442
511
  *
@@ -507,10 +576,10 @@ class NavigationConsumerService {
507
576
  stop() {
508
577
  this.consumerManagerService.unregister(this);
509
578
  }
510
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: NavigationConsumerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
511
- /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: NavigationConsumerService, providedIn: 'root' }); }
579
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: NavigationConsumerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
580
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: NavigationConsumerService, providedIn: 'root' }); }
512
581
  }
513
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: NavigationConsumerService, decorators: [{
582
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: NavigationConsumerService, decorators: [{
514
583
  type: Injectable,
515
584
  args: [{
516
585
  providedIn: 'root'
@@ -553,25 +622,76 @@ class RouteMemorizeService {
553
622
  getRoute(channelId) {
554
623
  return this.routeStack[channelId];
555
624
  }
556
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: RouteMemorizeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
557
- /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: RouteMemorizeService, providedIn: 'root' }); }
625
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: RouteMemorizeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
626
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: RouteMemorizeService, providedIn: 'root' }); }
558
627
  }
559
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: RouteMemorizeService, decorators: [{
628
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: RouteMemorizeService, decorators: [{
560
629
  type: Injectable,
561
630
  args: [{
562
631
  providedIn: 'root'
563
632
  }]
564
633
  }] });
565
634
 
566
- class RouteMemorizeDirective {
635
+ /**
636
+ * A pipe that restores a route with optional query parameters and memory channel ID.
637
+ *
638
+ * This pipe is used to transform a URL or SafeResourceUrl by appending query parameters
639
+ * and adjusting the pathname based on the current active route and memorized route.
640
+ */
641
+ class RestoreRoute {
567
642
  constructor() {
568
- /**
569
- * Whether to memorize the route.
570
- * Default is true.
571
- */
572
- this.memorizeRoute = input(true, ...(ngDevMode ? [{ debugName: "memorizeRoute" }] : []));
573
- /**
574
- * The ID used to memorize the route.
643
+ this.activeRoute = inject(ActivatedRoute);
644
+ this.domSanitizer = inject(DomSanitizer);
645
+ this.routeMemorizeService = inject(RouteMemorizeService, { optional: true });
646
+ this.window = inject(Window, { optional: true }) || window;
647
+ }
648
+ transform(url, options) {
649
+ const urlString = typeof url === 'string'
650
+ ? url
651
+ : this.domSanitizer.sanitize(SecurityContext.RESOURCE_URL, url || null);
652
+ if (!url) {
653
+ return undefined;
654
+ }
655
+ if (urlString) {
656
+ const moduleUrl = new URL(urlString);
657
+ const queryParamsModule = new URLSearchParams(moduleUrl.searchParams);
658
+ const channelId = options?.memoryChannelId;
659
+ const memorizedRoute = channelId && this.routeMemorizeService?.getRoute(channelId);
660
+ const topWindowUrl = new URL(memorizedRoute ? this.window.origin + memorizedRoute : this.window.location.href);
661
+ const queryParamsTopWindow = new URLSearchParams(topWindowUrl.search);
662
+ if (options?.propagateQueryParams) {
663
+ for (const [key, value] of queryParamsTopWindow) {
664
+ if (options?.overrideQueryParams || !queryParamsModule.has(key)) {
665
+ queryParamsModule.set(key, value);
666
+ }
667
+ }
668
+ }
669
+ moduleUrl.search = queryParamsModule.toString();
670
+ moduleUrl.pathname += topWindowUrl.pathname.split(`/${this.activeRoute.routeConfig?.path}`).pop() || '';
671
+ moduleUrl.pathname = moduleUrl.pathname.replace(/\/{2,}/g, '/');
672
+ const moduleUrlStringyfied = moduleUrl.toString();
673
+ return typeof url === 'string' ? moduleUrlStringyfied : this.domSanitizer.bypassSecurityTrustResourceUrl(moduleUrlStringyfied);
674
+ }
675
+ }
676
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: RestoreRoute, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
677
+ /** @nocollapse */ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.13", ngImport: i0, type: RestoreRoute, isStandalone: true, name: "restoreRoute" }); }
678
+ }
679
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: RestoreRoute, decorators: [{
680
+ type: Pipe,
681
+ args: [{
682
+ name: 'restoreRoute'
683
+ }]
684
+ }] });
685
+
686
+ class RouteMemorizeDirective {
687
+ constructor() {
688
+ /**
689
+ * Whether to memorize the route.
690
+ * Default is true.
691
+ */
692
+ this.memorizeRoute = input(true, ...(ngDevMode ? [{ debugName: "memorizeRoute" }] : []));
693
+ /**
694
+ * The ID used to memorize the route.
575
695
  */
576
696
  this.memorizeRouteId = input(...(ngDevMode ? [undefined, { debugName: "memorizeRouteId" }] : []));
577
697
  /**
@@ -603,52 +723,23 @@ class RouteMemorizeDirective {
603
723
  return;
604
724
  }
605
725
  const requested = requestedUrlSignal();
606
- const id = this.memorizeRouteId() || this.connect();
607
- if (requested && id && requested.channelId === id) {
726
+ const channelId = this.connect();
727
+ const id = this.memorizeRouteId() || channelId;
728
+ if (requested && id && requested.channelId === channelId) {
608
729
  memory.memorizeRoute(id, requested.url, untracked(this.maxAge));
609
730
  }
610
731
  });
611
732
  }
612
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: RouteMemorizeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
613
- /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.2.4", type: RouteMemorizeDirective, isStandalone: true, selector: "iframe[memorizeRoute]", inputs: { memorizeRoute: { classPropertyName: "memorizeRoute", publicName: "memorizeRoute", isSignal: true, isRequired: false, transformFunction: null }, memorizeRouteId: { classPropertyName: "memorizeRouteId", publicName: "memorizeRouteId", isSignal: true, isRequired: false, transformFunction: null }, memorizeMaxAge: { classPropertyName: "memorizeMaxAge", publicName: "memorizeMaxAge", isSignal: true, isRequired: false, transformFunction: null }, memorizeRouteMaxAge: { classPropertyName: "memorizeRouteMaxAge", publicName: "memorizeRouteMaxAge", isSignal: true, isRequired: false, transformFunction: null }, connect: { classPropertyName: "connect", publicName: "connect", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
733
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: RouteMemorizeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
734
+ /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.13", type: RouteMemorizeDirective, isStandalone: true, selector: "iframe[memorizeRoute]", inputs: { memorizeRoute: { classPropertyName: "memorizeRoute", publicName: "memorizeRoute", isSignal: true, isRequired: false, transformFunction: null }, memorizeRouteId: { classPropertyName: "memorizeRouteId", publicName: "memorizeRouteId", isSignal: true, isRequired: false, transformFunction: null }, memorizeMaxAge: { classPropertyName: "memorizeMaxAge", publicName: "memorizeMaxAge", isSignal: true, isRequired: false, transformFunction: null }, memorizeRouteMaxAge: { classPropertyName: "memorizeRouteMaxAge", publicName: "memorizeRouteMaxAge", isSignal: true, isRequired: false, transformFunction: null }, connect: { classPropertyName: "connect", publicName: "connect", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
614
735
  }
615
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: RouteMemorizeDirective, decorators: [{
736
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: RouteMemorizeDirective, decorators: [{
616
737
  type: Directive,
617
738
  args: [{
618
739
  selector: 'iframe[memorizeRoute]',
619
740
  standalone: true
620
741
  }]
621
- }], ctorParameters: () => [] });
622
-
623
- /**
624
- * A constant array of known message types and their versions.
625
- */
626
- const KNOWN_MESSAGES = [
627
- {
628
- type: ERROR_MESSAGE_TYPE,
629
- version: '1.0'
630
- }
631
- ];
632
- /**
633
- * Returns the default options for starting a client endpoint peer connection.
634
- * As `origin`, it will take the hostURL from {@link getHostInfo} and the `window` will be the parent window.
635
- */
636
- function getDefaultClientEndpointStartOptions() {
637
- const hostInfo = getHostInfo();
638
- if (hostInfo.hostURL) {
639
- return {
640
- origin: new URL(hostInfo.hostURL).origin,
641
- window: window.parent
642
- };
643
- }
644
- return {};
645
- }
646
- /**
647
- * Return `true` if embedded inside an iframe, `false` otherwise
648
- */
649
- function isEmbedded() {
650
- return window.top !== window.self;
651
- }
742
+ }], ctorParameters: () => [], propDecorators: { memorizeRoute: [{ type: i0.Input, args: [{ isSignal: true, alias: "memorizeRoute", required: false }] }], memorizeRouteId: [{ type: i0.Input, args: [{ isSignal: true, alias: "memorizeRouteId", required: false }] }], memorizeMaxAge: [{ type: i0.Input, args: [{ isSignal: true, alias: "memorizeMaxAge", required: false }] }], memorizeRouteMaxAge: [{ type: i0.Input, args: [{ isSignal: true, alias: "memorizeRouteMaxAge", required: false }] }], connect: [{ type: i0.Input, args: [{ isSignal: true, alias: "connect", required: false }] }] } });
652
743
 
653
744
  /**
654
745
  * A service that keeps in sync Router navigation and navigation messages.
@@ -662,6 +753,7 @@ class RoutingService {
662
753
  this.activatedRoute = inject(ActivatedRoute);
663
754
  this.messageService = inject((MessagePeerService));
664
755
  this.logger = inject(LoggerService);
756
+ this.window = inject(Window, { optional: true }) || window;
665
757
  /**
666
758
  * @inheritdoc
667
759
  */
@@ -715,7 +807,7 @@ class RoutingService {
715
807
  url
716
808
  };
717
809
  // TODO: sendBest() is not implemented -- https://github.com/AmadeusITGroup/microfrontends/issues/11
718
- if (isEmbedded()) {
810
+ if (isEmbedded(this.window)) {
719
811
  this.messageService.send(messageV10);
720
812
  }
721
813
  else {
@@ -733,10 +825,10 @@ class RoutingService {
733
825
  }
734
826
  });
735
827
  }
736
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: RoutingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
737
- /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: RoutingService, providedIn: 'root' }); }
828
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: RoutingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
829
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: RoutingService, providedIn: 'root' }); }
738
830
  }
739
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: RoutingService, decorators: [{
831
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: RoutingService, decorators: [{
740
832
  type: Injectable,
741
833
  args: [{
742
834
  providedIn: 'root'
@@ -744,54 +836,152 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImpor
744
836
  }], ctorParameters: () => [] });
745
837
 
746
838
  /**
747
- * A pipe that restores a route with optional query parameters and memory channel ID.
748
- *
749
- * This pipe is used to transform a URL or SafeResourceUrl by appending query parameters
750
- * and adjusting the pathname based on the current active route and memorized route.
839
+ * This service listens for resize messages and updates the height of elements based on the received messages.
751
840
  */
752
- class RestoreRoute {
841
+ class ResizeConsumerService {
753
842
  constructor() {
754
- this.activeRoute = inject(ActivatedRoute);
755
- this.domSanitizer = inject(DomSanitizer);
756
- this.routeMemorizeService = inject(RouteMemorizeService, { optional: true });
843
+ this.newHeight = signal(undefined, ...(ngDevMode ? [{ debugName: "newHeight" }] : []));
844
+ /**
845
+ * A readonly signal that provides the new height information from the channel.
846
+ */
847
+ this.newHeightFromChannel = this.newHeight.asReadonly();
848
+ /**
849
+ * The type of messages this service handles ('resize').
850
+ */
851
+ this.type = RESIZE_MESSAGE_TYPE;
852
+ /**
853
+ * The supported versions of resize messages and their handlers.
854
+ */
855
+ this.supportedVersions = {
856
+ /**
857
+ * Use the message paylod to compute a new height and emit it via the public signal
858
+ * @param message message to consume
859
+ */
860
+ '1.0': (message) => this.newHeight.set({ height: message.payload.height, channelId: message.from })
861
+ };
862
+ this.consumerManagerService = inject(ConsumerManagerService);
863
+ this.start();
864
+ inject(DestroyRef).onDestroy(() => this.stop());
757
865
  }
758
- transform(url, options) {
759
- const urlString = typeof url === 'string'
760
- ? url
761
- : this.domSanitizer.sanitize(SecurityContext.RESOURCE_URL, url || null);
762
- if (!url) {
866
+ /**
867
+ * Starts the resize handler service by registering it into the consumer manager service.
868
+ */
869
+ start() {
870
+ this.consumerManagerService.register(this);
871
+ }
872
+ /**
873
+ * Stops the resize handler service by unregistering it from the consumer manager service.
874
+ */
875
+ stop() {
876
+ this.consumerManagerService.unregister(this);
877
+ }
878
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ResizeConsumerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
879
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ResizeConsumerService, providedIn: 'root' }); }
880
+ }
881
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ResizeConsumerService, decorators: [{
882
+ type: Injectable,
883
+ args: [{
884
+ providedIn: 'root'
885
+ }]
886
+ }], ctorParameters: () => [] });
887
+
888
+ /**
889
+ * This service observe changes in the document's body height.
890
+ * When the height changes, it sends a resize message with the new height, to the connected peers
891
+ */
892
+ class ResizeService {
893
+ constructor() {
894
+ this.messageService = inject((MessagePeerService));
895
+ /**
896
+ * @inheritdoc
897
+ */
898
+ this.types = RESIZE_MESSAGE_TYPE;
899
+ registerProducer(this);
900
+ }
901
+ /**
902
+ * @inheritdoc
903
+ */
904
+ handleError(message) {
905
+ // eslint-disable-next-line no-console -- error handling placeholder
906
+ console.error('Error in resize service message', message);
907
+ }
908
+ /**
909
+ * This method sets up a `ResizeObserver` to observe changes in the document's body height.
910
+ * When the height changes, it sends a resize message with the new height, to the connected peers
911
+ */
912
+ startResizeObserver() {
913
+ this.resizeObserver = new ResizeObserver(() => {
914
+ const newHeight = document.body.getBoundingClientRect().height;
915
+ if (!this.actualHeight || newHeight !== this.actualHeight) {
916
+ this.actualHeight = newHeight;
917
+ const messageV10 = {
918
+ type: 'resize',
919
+ version: '1.0',
920
+ height: this.actualHeight
921
+ };
922
+ // TODO: sendBest() is not implemented -- https://github.com/AmadeusITGroup/microfrontends/issues/11
923
+ this.messageService.send(messageV10);
924
+ }
925
+ });
926
+ afterNextRender(() => this.resizeObserver?.observe(document.body));
927
+ }
928
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ResizeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
929
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ResizeService, providedIn: 'root' }); }
930
+ }
931
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ResizeService, decorators: [{
932
+ type: Injectable,
933
+ args: [{
934
+ providedIn: 'root'
935
+ }]
936
+ }], ctorParameters: () => [] });
937
+
938
+ /**
939
+ * A directive that adjusts the height of an element based on resize messages from a specified channel.
940
+ */
941
+ class ScalableDirective {
942
+ constructor() {
943
+ /**
944
+ * The connection ID for the element, used as channel id backup
945
+ */
946
+ this.connect = input(...(ngDevMode ? [undefined, { debugName: "connect" }] : []));
947
+ /**
948
+ * The channel id
949
+ */
950
+ this.scalable = input(...(ngDevMode ? [undefined, { debugName: "scalable" }] : []));
951
+ this.resizeHandler = inject(ResizeConsumerService);
952
+ /**
953
+ * This signal checks if the current channel requesting the resize matches the channel ID from the resize handler.
954
+ * If they match, it returns the new height information; otherwise, it returns undefined.
955
+ */
956
+ this.newHeightFromChannel = computed(() => {
957
+ const channelAskingResize = this.scalable() || this.connect();
958
+ const newHeightFromChannel = this.resizeHandler.newHeightFromChannel();
959
+ if (channelAskingResize && newHeightFromChannel?.channelId === channelAskingResize) {
960
+ return newHeightFromChannel;
961
+ }
763
962
  return undefined;
764
- }
765
- if (urlString) {
766
- const moduleUrl = new URL(urlString);
767
- const queryParamsModule = new URLSearchParams(moduleUrl.searchParams);
768
- const channelId = options?.memoryChannelId;
769
- const memorizedRoute = channelId && this.routeMemorizeService?.getRoute(channelId);
770
- const topWindowUrl = new URL(memorizedRoute ? window.origin + memorizedRoute : window.location.href);
771
- const queryParamsTopWindow = new URLSearchParams(topWindowUrl.search);
772
- if (options?.propagateQueryParams) {
773
- for (const [key, value] of queryParamsTopWindow) {
774
- if (options?.overrideQueryParams || !queryParamsModule.has(key)) {
775
- queryParamsModule.set(key, value);
776
- }
777
- }
963
+ }, ...(ngDevMode ? [{ debugName: "newHeightFromChannel" }] : []));
964
+ const elem = inject(ElementRef);
965
+ const renderer = inject(Renderer2);
966
+ this.resizeHandler.start();
967
+ /** When a new height value is received set the height of the host element (in pixels) */
968
+ effect(() => {
969
+ const newHeightFromChannel = this.newHeightFromChannel();
970
+ if (newHeightFromChannel) {
971
+ renderer.setStyle(elem.nativeElement, 'height', `${newHeightFromChannel.height}px`);
778
972
  }
779
- moduleUrl.search = queryParamsModule.toString();
780
- moduleUrl.pathname += topWindowUrl.pathname.split(`/${this.activeRoute.routeConfig?.path}`).pop() || '';
781
- moduleUrl.pathname = moduleUrl.pathname.replace(/\/{2,}/g, '/');
782
- const moduleUrlStringyfied = moduleUrl.toString();
783
- return typeof url === 'string' ? moduleUrlStringyfied : this.domSanitizer.bypassSecurityTrustResourceUrl(moduleUrlStringyfied);
784
- }
973
+ });
785
974
  }
786
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: RestoreRoute, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
787
- /** @nocollapse */ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.2.4", ngImport: i0, type: RestoreRoute, isStandalone: true, name: "restoreRoute" }); }
975
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ScalableDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
976
+ /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.13", type: ScalableDirective, isStandalone: true, selector: "[scalable]", inputs: { connect: { classPropertyName: "connect", publicName: "connect", isSignal: true, isRequired: false, transformFunction: null }, scalable: { classPropertyName: "scalable", publicName: "scalable", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
788
977
  }
789
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: RestoreRoute, decorators: [{
790
- type: Pipe,
978
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ScalableDirective, decorators: [{
979
+ type: Directive,
791
980
  args: [{
792
- name: 'restoreRoute'
981
+ selector: '[scalable]',
982
+ standalone: true
793
983
  }]
794
- }] });
984
+ }], ctorParameters: () => [], propDecorators: { connect: [{ type: i0.Input, args: [{ isSignal: true, alias: "connect", required: false }] }], scalable: [{ type: i0.Input, args: [{ isSignal: true, alias: "scalable", required: false }] }] } });
795
985
 
796
986
  /** Default suffix for an url containing a theme css file */
797
987
  const THEME_URL_SUFFIX = '-theme.css';
@@ -871,13 +1061,14 @@ class ThemeProducerService {
871
1061
  constructor() {
872
1062
  this.messageService = inject((MessagePeerService));
873
1063
  this.logger = inject(LoggerService);
1064
+ this.window = inject(Window, { optional: true }) || window;
874
1065
  /**
875
1066
  * The type of messages this service handles ('theme').
876
1067
  */
877
1068
  this.types = THEME_MESSAGE_TYPE;
878
1069
  registerProducer(this);
879
1070
  // get the current theme name from the url (if any) and emit a first value for the current theme
880
- const parentUrl = new URL(window.location.toString());
1071
+ const parentUrl = new URL(this.window.location.toString());
881
1072
  const selectedThemeName = parentUrl.searchParams.get(THEME_QUERY_PARAM_NAME);
882
1073
  this.currentThemeSelection = signal(selectedThemeName
883
1074
  ? {
@@ -941,10 +1132,10 @@ class ThemeProducerService {
941
1132
  this.logger.error('Error in theme service message', message);
942
1133
  this.revertToPreviousTheme();
943
1134
  }
944
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ThemeProducerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
945
- /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ThemeProducerService, providedIn: 'root' }); }
1135
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ThemeProducerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1136
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ThemeProducerService, providedIn: 'root' }); }
946
1137
  }
947
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ThemeProducerService, decorators: [{
1138
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ThemeProducerService, decorators: [{
948
1139
  type: Injectable,
949
1140
  args: [{
950
1141
  providedIn: 'root'
@@ -977,10 +1168,10 @@ class ApplyTheme {
977
1168
  }
978
1169
  return undefined;
979
1170
  }
980
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ApplyTheme, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
981
- /** @nocollapse */ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.2.4", ngImport: i0, type: ApplyTheme, isStandalone: true, name: "applyTheme" }); }
1171
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ApplyTheme, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
1172
+ /** @nocollapse */ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.13", ngImport: i0, type: ApplyTheme, isStandalone: true, name: "applyTheme" }); }
982
1173
  }
983
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ApplyTheme, decorators: [{
1174
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ApplyTheme, decorators: [{
984
1175
  type: Pipe,
985
1176
  args: [{
986
1177
  name: 'applyTheme'
@@ -1036,132 +1227,19 @@ class ThemeConsumerService {
1036
1227
  stop() {
1037
1228
  this.consumerManagerService.unregister(this);
1038
1229
  }
1039
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ThemeConsumerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1040
- /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ThemeConsumerService, providedIn: 'root' }); }
1230
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ThemeConsumerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1231
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ThemeConsumerService, providedIn: 'root' }); }
1041
1232
  }
1042
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ThemeConsumerService, decorators: [{
1233
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: ThemeConsumerService, decorators: [{
1043
1234
  type: Injectable,
1044
1235
  args: [{
1045
1236
  providedIn: 'root'
1046
1237
  }]
1047
1238
  }], ctorParameters: () => [] });
1048
1239
 
1049
- class ConnectDirective {
1050
- /**
1051
- * Binds the `src` attribute of the iframe to the sanitized source URL.
1052
- */
1053
- get srcAttr() {
1054
- return this.src();
1055
- }
1056
- constructor() {
1057
- /**
1058
- * The connection ID required for the message peer service.
1059
- */
1060
- this.connect = input.required(...(ngDevMode ? [{ debugName: "connect" }] : []));
1061
- /**
1062
- * The sanitized source URL for the iframe.
1063
- */
1064
- this.src = input(...(ngDevMode ? [undefined, { debugName: "src" }] : []));
1065
- this.messageService = inject(MessagePeerService);
1066
- this.domSanitizer = inject(DomSanitizer);
1067
- this.iframeElement = inject(ElementRef).nativeElement;
1068
- this.clientOrigin = computed(() => {
1069
- const src = this.src();
1070
- const srcString = src && this.domSanitizer.sanitize(SecurityContext.RESOURCE_URL, src);
1071
- return srcString && new URL(srcString).origin;
1072
- }, ...(ngDevMode ? [{ debugName: "clientOrigin" }] : []));
1073
- const logger = inject(LoggerService);
1074
- // When the origin or connection ID change - reconnect the message service
1075
- effect((onCleanup) => {
1076
- let stopHandshakeListening = () => { };
1077
- const origin = this.clientOrigin();
1078
- const id = this.connect();
1079
- const source = this.iframeElement.contentWindow;
1080
- // listen for handshakes only if we know the origin and were given a connection ID
1081
- if (origin && source && id) {
1082
- try {
1083
- stopHandshakeListening = this.messageService.listen({ id, source, origin });
1084
- }
1085
- catch (e) {
1086
- logger.error(`Failed to start listening for (connection ID: ${id})`, e);
1087
- }
1088
- }
1089
- // stop listening for handshakes and disconnect previous connection when:
1090
- // - origin/connection ID change
1091
- // - the directive is destroyed
1092
- onCleanup(() => {
1093
- stopHandshakeListening();
1094
- this.messageService.disconnect();
1095
- });
1096
- });
1097
- }
1098
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ConnectDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1099
- /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.2.4", type: ConnectDirective, isStandalone: true, selector: "iframe[connect]", inputs: { connect: { classPropertyName: "connect", publicName: "connect", isSignal: true, isRequired: true, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "src": "this.srcAttr" } }, ngImport: i0 }); }
1100
- }
1101
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: ConnectDirective, decorators: [{
1102
- type: Directive,
1103
- args: [{
1104
- selector: 'iframe[connect]',
1105
- standalone: true
1106
- }]
1107
- }], ctorParameters: () => [], propDecorators: { srcAttr: [{
1108
- type: HostBinding,
1109
- args: ['src']
1110
- }] } });
1111
-
1112
- /**
1113
- * No operation function used to override the history API methods.
1114
- */
1115
- function noop() { }
1116
- /**
1117
- * If an iframe is not sandboxed or is of the same origin, navigation inside it will mess up the main window history.
1118
- * This disables writing to and overriding history API from inside the iframe to prevent this.
1119
- * Theoretically, this might break applications that rely on reading history API from inside the iframe.
1120
- *
1121
- * This should also allow having `CustomPathLocationStrategy` in the iframe if necessary.
1122
- */
1123
- function provideDisableHistoryWrites() {
1124
- return provideAppInitializer(() => {
1125
- Object.defineProperty(history, 'pushState', { value: noop, writable: false, configurable: false });
1126
- Object.defineProperty(history, 'replaceState', { value: noop, writable: false, configurable: false });
1127
- });
1128
- }
1129
-
1130
- /**
1131
- * Provide the communication protocol connection configuration
1132
- * @param connectionConfigOptions The identifier of the application in the communication protocol ecosystem plus the types of messages able to exchange and a logger object
1133
- */
1134
- function provideConnection(connectionConfigOptions) {
1135
- persistHostInfo();
1136
- const connectionId = (isEmbedded() && getHostInfo().moduleApplicationId) || connectionConfigOptions?.id;
1137
- if (!connectionId) {
1138
- (connectionConfigOptions?.logger || console).error('An id (moduleId) needs to be provided for the application in order to establish a connection inside the communication protocol');
1139
- return makeEnvironmentProviders([]);
1140
- }
1141
- const config = {
1142
- id: connectionId,
1143
- messageCheckStrategy: 'version',
1144
- knownMessages: [...KNOWN_MESSAGES, ...(connectionConfigOptions?.knownMessages || [])]
1145
- };
1146
- return makeEnvironmentProviders([
1147
- {
1148
- provide: MESSAGE_PEER_CONFIG, useValue: config
1149
- },
1150
- {
1151
- provide: MESSAGE_PEER_CONNECT_OPTIONS, useValue: getDefaultClientEndpointStartOptions()
1152
- },
1153
- {
1154
- // in the case of the ConnectionService will extend the base service 'useExisting' should be used
1155
- provide: MessagePeerService, useClass: MessagePeerService, deps: [MESSAGE_PEER_CONFIG]
1156
- },
1157
- // deactivate history writes to avoid embedded app writing to the host history
1158
- ...isEmbedded() ? [provideDisableHistoryWrites()] : []
1159
- ]);
1160
- }
1161
-
1162
1240
  /**
1163
1241
  * Generated bundle index. Do not edit.
1164
1242
  */
1165
1243
 
1166
- export { ApplyTheme, ConnectDirective, ConsumerManagerService, ERROR_MESSAGE_TYPE, 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, registerConsumer, registerProducer, sendError };
1244
+ 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 };
1167
1245
  //# sourceMappingURL=ama-mfe-ng-utils.mjs.map