From 152d0f539f06425ed013a66cbc345eed15556118 Mon Sep 17 00:00:00 2001 From: Waldemar Mazurek Date: Tue, 24 Sep 2024 14:34:12 +0200 Subject: [PATCH 01/10] Adds option to disable luigiCookie --- client/src/lifecycleManager.js | 6 +++++- core/src/App.svelte | 1 + docs/general-settings.md | 9 +++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/client/src/lifecycleManager.js b/client/src/lifecycleManager.js index ed62c0e1cd..f94cad4383 100644 --- a/client/src/lifecycleManager.js +++ b/client/src/lifecycleManager.js @@ -91,6 +91,7 @@ class LifecycleManager extends LuigiClientBase { helpers.setLuigiCoreDomain(e.origin); this.luigiInitialized = true; this._notifyInit(e.origin); + this._tpcCheck(); helpers.sendPostMessageToLuigiCore({ msg: 'luigi.init.ok' }); }); @@ -135,10 +136,13 @@ class LifecycleManager extends LuigiClientBase { }, '*' ); - this._tpcCheck(); } _tpcCheck() { + if (this.currentContext?.internal?.thirdPartyCookieCheck?.disabled) { + window.parent.postMessage('luigi.tpcDisabled', '*'); + return; + } let tpc = 'enabled'; let cookies = document.cookie; let luigiCookie; diff --git a/core/src/App.svelte b/core/src/App.svelte index f716a76725..045b1e09f4 100644 --- a/core/src/App.svelte +++ b/core/src/App.svelte @@ -120,6 +120,7 @@ isNavigateBack, viewStackSize: preservedViews.length, clientPermissions: iframeConf.nextViewUrl ? iframeConf.nextClientPermissions : iframeConf.clientPermissions, + thirdPartyCookieCheck: await LuigiConfig.getConfigValue('settings.thirdPartyCookieCheck'), userSettings: hasUserSettings ? userSettingGroups[userSettingsGroupName] : null, anchor: LuigiRouting.getAnchor(), cssVariables: await LuigiTheming.getCSSVariables() diff --git a/docs/general-settings.md b/docs/general-settings.md index 776228e52b..18b9b8f8a9 100644 --- a/docs/general-settings.md +++ b/docs/general-settings.md @@ -53,6 +53,7 @@ settings: { hideAutomatically: true }, thirdPartyCookieCheck = { + //disabled: true, //thirdPartyCookieScriptLocation: 'https://domain/init.html', thirdPartyCookieErrorHandling: () => { const alert = { @@ -298,7 +299,7 @@ This function is called with the following parameters: ## Third-party cookies support check -You can check whether the user's browser supports third-party cookies by defining a **thirdPartyCookieCheck** object which expects a function called **thirdPartyCookieErrorHandling** and an optional **thirdPartyCookiesScriptLocation** parameter. When **thirdPartyCookiesScriptLocation** is set, the Luigi Core application checks third-party cookie support only once and not on every micro frontend call. If it is *not* set, the Luigi Core application checks third-party cookie support whenever a micro frontend is loaded. +You can check whether the user's browser supports third-party cookies by defining a **thirdPartyCookieCheck** object which expects a function called **thirdPartyCookieErrorHandling** and optional **disabled** and **thirdPartyCookiesScriptLocation** parameters. When **thirdPartyCookiesScriptLocation** is set, the Luigi Core application checks third-party cookie support only once and not on every micro frontend call. If it is *not* set, the Luigi Core application checks third-party cookie support whenever a micro frontend is loaded. To detect whether the user's browser supports the mechanism, use the script in the [`third-party-cookies`](https://github.com/SAP/luigi/tree/main/core/third-party-cookies) catalog. Deploy this file on a domain different from your main application's and set **thirdPartyCookieScriptLocation** to the `init.html` file. During initialization, Luigi detects cookies support and produces an alert if cookies are disabled in the user's browser. @@ -306,7 +307,11 @@ To detect whether the user's browser supports the mechanism, use the script in t #### thirdPartyCookieCheck - **type**: object -- **description**: object defined in the general settings part of the Luigi configuration file, containing the **thirdPartyCookieErrorHandling** function and an optional **thirdPartyCookiesScriptLocation** parameter. +- **description**: object defined in the general settings part of the Luigi configuration file, containing the **thirdPartyCookieErrorHandling** function and optional **disabled** and **thirdPartyCookiesScriptLocation** parameters. + +#### disabled +- **type**: boolean +- **description**: if set to true **thirdPartyCookieCheck** is ignored. #### thirdPartyCookieErrorHandling - **type**: function From 8d6a12e8c1d28ab5397b118b0e8ecda250fd3e31 Mon Sep 17 00:00:00 2001 From: Waldemar Mazurek Date: Wed, 25 Sep 2024 15:11:06 +0200 Subject: [PATCH 02/10] Adds cookie prop for container --- container/src/LuigiCompoundContainer.svelte | 3 +++ container/src/LuigiContainer.svelte | 3 +++ container/src/services/container.service.ts | 6 +++++- container/typings/LuigiCompoundContainer.svelte.d.ts | 6 ++++++ container/typings/LuigiContainer.svelte.d.ts | 6 ++++++ docs/luigi-compound-container-api.md | 10 ++++++++++ docs/luigi-container-api.md | 10 ++++++++++ 7 files changed, 43 insertions(+), 1 deletion(-) diff --git a/container/src/LuigiCompoundContainer.svelte b/container/src/LuigiCompoundContainer.svelte index d3f2abc28d..71bc8fc212 100644 --- a/container/src/LuigiCompoundContainer.svelte +++ b/container/src/LuigiCompoundContainer.svelte @@ -28,6 +28,7 @@ reflect: false, attribute: 'search-params', }, + skipCookieCheck: { type: 'Boolean', reflect: false, attribute: 'skip-cookie-check' }, theme: { type: 'String', reflect: false, attribute: 'theme' }, userSettings: { type: 'Object', @@ -79,6 +80,7 @@ export let nodeParams: any; export let pathParams: any; export let searchParams: any; + export let skipCookieCheck: boolean; export let theme: string; export let userSettings: any; export let viewurl: string; @@ -104,6 +106,7 @@ nodeParams && pathParams && searchParams && + skipCookieCheck && theme && userSettings ); diff --git a/container/src/LuigiContainer.svelte b/container/src/LuigiContainer.svelte index 974f91c70b..207bb3a381 100644 --- a/container/src/LuigiContainer.svelte +++ b/container/src/LuigiContainer.svelte @@ -20,6 +20,7 @@ pathParams: { type: 'Object', reflect: false, attribute: 'path-params' }, sandboxRules: { type: 'Array', reflect: false, attribute: 'sandbox-rules' }, searchParams: { type: 'Object', reflect: false, attribute: 'search-params' }, + skipCookieCheck: { type: 'Boolean', reflect: false, attribute: 'skip-cookie-check' }, skipInitCheck: { type: 'Boolean', reflect: false, attribute: 'skip-init-check' }, theme: { type: 'String', reflect: false, attribute: 'theme' }, userSettings: { type: 'Object', reflect: false, attribute: 'user-settings' }, @@ -79,6 +80,7 @@ export let pathParams: any; export let sandboxRules: string[]; export let searchParams: any; + export let skipCookieCheck: boolean; export let skipInitCheck: boolean; export let theme: string; export let userSettings: any; @@ -113,6 +115,7 @@ pathParams && sandboxRules && searchParams && + skipCookieCheck && skipInitCheck && theme && userSettings diff --git a/container/src/services/container.service.ts b/container/src/services/container.service.ts index c25d3d53af..027874cfc7 100644 --- a/container/src/services/container.service.ts +++ b/container/src/services/container.service.ts @@ -106,7 +106,11 @@ export class ContainerService { { msg: LuigiInternalMessageID.SEND_CONTEXT_HANDSHAKE, context: targetCnt.context || {}, - internal: {}, + internal: { + thirdPartyCookieCheck: { + disabled: targetCnt.getAttribute('skip-cookie-check') === 'true' + } + }, authData: targetCnt.authData || {} }, '*' diff --git a/container/typings/LuigiCompoundContainer.svelte.d.ts b/container/typings/LuigiCompoundContainer.svelte.d.ts index d519a26d71..60c3036057 100644 --- a/container/typings/LuigiCompoundContainer.svelte.d.ts +++ b/container/typings/LuigiCompoundContainer.svelte.d.ts @@ -109,6 +109,12 @@ export default class LuigiCompoundContainer extends HTMLElement { */ activeFeatureToggleList: string[]; + /** + * If set to true, skips third party cookie check + * @since NEXT_RELEASE_CONTAINER + */ + skipCookieCheck: boolean; + /** * The theme to be passed to the compound microfrontend. * @since NEXT_RELEASE_CONTAINER diff --git a/container/typings/LuigiContainer.svelte.d.ts b/container/typings/LuigiContainer.svelte.d.ts index 6d763c1238..2442bf1e4e 100644 --- a/container/typings/LuigiContainer.svelte.d.ts +++ b/container/typings/LuigiContainer.svelte.d.ts @@ -71,6 +71,12 @@ export default class LuigiContainer extends HTMLElement { */ activeFeatureToggleList: string[]; + /** + * If set to true, skips third party cookie check + * @since NEXT_RELEASE_CONTAINER + */ + skipCookieCheck: boolean; + /** * If set to true, skips handshake and ready event is fired immediately * @since 1.0.0 diff --git a/docs/luigi-compound-container-api.md b/docs/luigi-compound-container-api.md index 86e7c8f921..d13b4548d0 100644 --- a/docs/luigi-compound-container-api.md +++ b/docs/luigi-compound-container-api.md @@ -190,6 +190,16 @@ Type: [Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global * **since**: NEXT_RELEASE_CONTAINER +### skipCookieCheck + +If set to true, skips third party cookie check + +Type: [boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean) + +**Meta** + +* **since**: NEXT_RELEASE_CONTAINER + ### theme The theme to be passed to the compound microfrontend. diff --git a/docs/luigi-container-api.md b/docs/luigi-container-api.md index 1128a99e5e..533aff8dc0 100644 --- a/docs/luigi-container-api.md +++ b/docs/luigi-container-api.md @@ -121,6 +121,16 @@ Type: [Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global * **since**: 1.0.0 +### skipCookieCheck + +If set to true, skips third party cookie check + +Type: [boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean) + +**Meta** + +* **since**: NEXT_RELEASE_CONTAINER + ### skipInitCheck If set to true, skips handshake and ready event is fired immediately From 8414709928c279a1bf77f9bc49acb26d87bfdbc5 Mon Sep 17 00:00:00 2001 From: Waldemar Mazurek Date: Wed, 25 Sep 2024 15:51:35 +0200 Subject: [PATCH 03/10] Fixes failing test --- container/src/services/container.service.ts | 2 +- container/test/services/container.service.spec.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/container/src/services/container.service.ts b/container/src/services/container.service.ts index 027874cfc7..39e615ccb6 100644 --- a/container/src/services/container.service.ts +++ b/container/src/services/container.service.ts @@ -108,7 +108,7 @@ export class ContainerService { context: targetCnt.context || {}, internal: { thirdPartyCookieCheck: { - disabled: targetCnt.getAttribute('skip-cookie-check') === 'true' + disabled: !!(targetCnt.getAttribute && targetCnt.getAttribute('skip-cookie-check') === 'true') } }, authData: targetCnt.authData || {} diff --git a/container/test/services/container.service.spec.ts b/container/test/services/container.service.spec.ts index 5c146767d0..92359dbf84 100644 --- a/container/test/services/container.service.spec.ts +++ b/container/test/services/container.service.spec.ts @@ -68,7 +68,7 @@ describe('getContainerManager messageListener', () => { gtcSpy.mockRestore(); }); - it('test get context message', () => { + it('test get context message', () => { const event = { source: cw, data: { @@ -87,7 +87,16 @@ describe('getContainerManager messageListener', () => { cw.postMessage = postMessageMock; // Define the message to send and target Origin - const message = {"context": {}, "internal": {}, "msg": "luigi.init", "authData":{}}; + const message = { + "authData":{}, + "context": {}, + "internal": { + "thirdPartyCookieCheck": { + "disabled": false + } + }, + "msg": "luigi.init" + }; const targetOrigin = "*"; // Call the method that should trigger postMessage From a2f5a1e8ee7afec529d997fc7e8031866b2e9537 Mon Sep 17 00:00:00 2001 From: Waldemar Mazurek Date: Thu, 26 Sep 2024 09:36:49 +0200 Subject: [PATCH 04/10] Fixes code review issues --- client/src/lifecycleManager.js | 1 - container/src/services/container.service.ts | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/client/src/lifecycleManager.js b/client/src/lifecycleManager.js index f94cad4383..b46cf7a885 100644 --- a/client/src/lifecycleManager.js +++ b/client/src/lifecycleManager.js @@ -140,7 +140,6 @@ class LifecycleManager extends LuigiClientBase { _tpcCheck() { if (this.currentContext?.internal?.thirdPartyCookieCheck?.disabled) { - window.parent.postMessage('luigi.tpcDisabled', '*'); return; } let tpc = 'enabled'; diff --git a/container/src/services/container.service.ts b/container/src/services/container.service.ts index 39e615ccb6..b57cf934f4 100644 --- a/container/src/services/container.service.ts +++ b/container/src/services/container.service.ts @@ -108,7 +108,7 @@ export class ContainerService { context: targetCnt.context || {}, internal: { thirdPartyCookieCheck: { - disabled: !!(targetCnt.getAttribute && targetCnt.getAttribute('skip-cookie-check') === 'true') + disabled: this.handleCookieCheck(targetCnt) } }, authData: targetCnt.authData || {} @@ -196,6 +196,20 @@ export class ContainerService { registerContainer(thisComponent: HTMLElement): void { this.getContainerManager().container.push(thisComponent); } + + private handleCookieCheck(targetCnt): boolean { + let skipCookieCheck = false; + + if (targetCnt.getAttribute) { + if (typeof targetCnt.getAttribute('skip-cookie-check') === 'string') { + skipCookieCheck = targetCnt.getAttribute('skip-cookie-check') === 'true'; + } else if (typeof targetCnt.skipCookieCheck !== 'undefined') { + skipCookieCheck = targetCnt.skipCookieCheck; + } + } + + return skipCookieCheck; + } } export const containerService = new ContainerService(); From 4d3e4485607f73da4a27db357cce631f998ddb19 Mon Sep 17 00:00:00 2001 From: Waldemar Mazurek Date: Thu, 26 Sep 2024 15:44:30 +0200 Subject: [PATCH 05/10] Fixes code review issues --- container/src/LuigiCompoundContainer.svelte | 3 --- container/typings/LuigiCompoundContainer.svelte.d.ts | 6 ------ docs/luigi-compound-container-api.md | 10 ---------- 3 files changed, 19 deletions(-) diff --git a/container/src/LuigiCompoundContainer.svelte b/container/src/LuigiCompoundContainer.svelte index 71bc8fc212..d3f2abc28d 100644 --- a/container/src/LuigiCompoundContainer.svelte +++ b/container/src/LuigiCompoundContainer.svelte @@ -28,7 +28,6 @@ reflect: false, attribute: 'search-params', }, - skipCookieCheck: { type: 'Boolean', reflect: false, attribute: 'skip-cookie-check' }, theme: { type: 'String', reflect: false, attribute: 'theme' }, userSettings: { type: 'Object', @@ -80,7 +79,6 @@ export let nodeParams: any; export let pathParams: any; export let searchParams: any; - export let skipCookieCheck: boolean; export let theme: string; export let userSettings: any; export let viewurl: string; @@ -106,7 +104,6 @@ nodeParams && pathParams && searchParams && - skipCookieCheck && theme && userSettings ); diff --git a/container/typings/LuigiCompoundContainer.svelte.d.ts b/container/typings/LuigiCompoundContainer.svelte.d.ts index 60c3036057..d519a26d71 100644 --- a/container/typings/LuigiCompoundContainer.svelte.d.ts +++ b/container/typings/LuigiCompoundContainer.svelte.d.ts @@ -109,12 +109,6 @@ export default class LuigiCompoundContainer extends HTMLElement { */ activeFeatureToggleList: string[]; - /** - * If set to true, skips third party cookie check - * @since NEXT_RELEASE_CONTAINER - */ - skipCookieCheck: boolean; - /** * The theme to be passed to the compound microfrontend. * @since NEXT_RELEASE_CONTAINER diff --git a/docs/luigi-compound-container-api.md b/docs/luigi-compound-container-api.md index d13b4548d0..86e7c8f921 100644 --- a/docs/luigi-compound-container-api.md +++ b/docs/luigi-compound-container-api.md @@ -190,16 +190,6 @@ Type: [Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global * **since**: NEXT_RELEASE_CONTAINER -### skipCookieCheck - -If set to true, skips third party cookie check - -Type: [boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean) - -**Meta** - -* **since**: NEXT_RELEASE_CONTAINER - ### theme The theme to be passed to the compound microfrontend. From 89b3f9c5a82fd550f63f7dbb7222c0f77675ff34 Mon Sep 17 00:00:00 2001 From: Waldemar Mazurek Date: Mon, 30 Sep 2024 12:17:16 +0200 Subject: [PATCH 06/10] Fixes code review issues --- container/.gitignore | 1 + .../test-app/iframe/iframe-container.cy.js | 14 +++++++ .../e2e/test-app/iframe/iframe-cookies.cy.js | 24 ++++++++++++ container/package.json | 3 +- container/src/LuigiContainer.svelte | 2 +- container/src/services/container.service.ts | 16 +------- container/test-app/iframe/iframe-cookies.html | 39 +++++++++++++++++++ container/test-app/iframe/microfrontend.html | 2 +- container/test-app/index.html | 5 +++ container/typings/LuigiContainer.svelte.d.ts | 2 +- docs/luigi-container-api.md | 2 +- 11 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 container/cypress/e2e/test-app/iframe/iframe-cookies.cy.js create mode 100644 container/test-app/iframe/iframe-cookies.html diff --git a/container/.gitignore b/container/.gitignore index 5d7df85d75..b2415001fb 100644 --- a/container/.gitignore +++ b/container/.gitignore @@ -9,6 +9,7 @@ /test-app/bundle.js /test-app/bundle.js.map /test-app/compound/luigi-element.js +/test-app/iframe/luigi-client.js /public/**/*.ts /coverage diff --git a/container/cypress/e2e/test-app/iframe/iframe-container.cy.js b/container/cypress/e2e/test-app/iframe/iframe-container.cy.js index b659bc112d..732edd1fcc 100644 --- a/container/cypress/e2e/test-app/iframe/iframe-container.cy.js +++ b/container/cypress/e2e/test-app/iframe/iframe-container.cy.js @@ -7,6 +7,20 @@ describe('Iframe Container Test', () => { stub = cy.stub(); }); + it('should sent third party cookies request', () => { + cy.on('window:alert', stub); + + cy.get(containerSelector) + .should('not.have.attr', 'skip-cookie-check'); + cy.get(containerSelector) + .shadow() + .get('iframe') + .then(() => { + cy.wrap(stub).should('have.been.calledWith', 'set-third-party-cookies-request'); + cy.getCookie('luigiCookie').should('exist') + }); + }); + it('navigation sent', () => { cy.get(containerSelector) .shadow() diff --git a/container/cypress/e2e/test-app/iframe/iframe-cookies.cy.js b/container/cypress/e2e/test-app/iframe/iframe-cookies.cy.js new file mode 100644 index 0000000000..6abec51150 --- /dev/null +++ b/container/cypress/e2e/test-app/iframe/iframe-cookies.cy.js @@ -0,0 +1,24 @@ +describe('Iframe Cookies Test', () => { + const containerSelector = '[data-test-id="iframe-based-container-test"]'; + let stub; + + beforeEach(() => { + cy.visit('http://localhost:8080/iframe/iframe-cookies.html'); + stub = cy.stub(); + }); + + it('should not sent third party cookies request', () => { + cy.on('window:alert', stub); + + cy.get(containerSelector) + .should('have.attr', 'skip-cookie-check') + .and('match', /true/); + cy.get(containerSelector) + .shadow() + .get('iframe') + .then(() => { + cy.wrap(stub).should('not.have.been.calledWith', 'set-third-party-cookies-request'); + cy.getCookie('luigiCookie').should('not.exist') + }); + }); +}); diff --git a/container/package.json b/container/package.json index 37c3d22c48..66f42352c4 100644 --- a/container/package.json +++ b/container/package.json @@ -18,8 +18,9 @@ "bundle": "npm run build", "dev": "rollup -c -w", "copyBundle": "cp public/bundle.js public/bundle.js.map test-app/ && cp public/bundle.js public/bundle.js.map examples/ || COPY public\\* test-app\\", + "copyLuigiClient": "cp ../client/public/luigi-client.js test-app/iframe", "copyLuigiElement": "cp ../client/src/luigi-element.js test-app/compound", - "serve": "npm run build && npm run copyLuigiElement && npm run copyBundle && sirv -D -c test-app --no-clear", + "serve": "npm run build && npm run copyLuigiClient && npm run copyLuigiElement && npm run copyBundle && sirv -D -c test-app --no-clear", "bundle:watch": "chokidar \"src/**/*.*\" -c \"npm run build && npm run copyBundle\"", "start": "concurrently -k \"npm run serve\" \"npm run bundle:watch\"", "start-examples":"npm run copyBundle && sirv -D -c examples --no-clear", diff --git a/container/src/LuigiContainer.svelte b/container/src/LuigiContainer.svelte index 207bb3a381..9f51a0feeb 100644 --- a/container/src/LuigiContainer.svelte +++ b/container/src/LuigiContainer.svelte @@ -20,7 +20,7 @@ pathParams: { type: 'Object', reflect: false, attribute: 'path-params' }, sandboxRules: { type: 'Array', reflect: false, attribute: 'sandbox-rules' }, searchParams: { type: 'Object', reflect: false, attribute: 'search-params' }, - skipCookieCheck: { type: 'Boolean', reflect: false, attribute: 'skip-cookie-check' }, + skipCookieCheck: { type: 'String', reflect: false, attribute: 'skip-cookie-check' }, skipInitCheck: { type: 'Boolean', reflect: false, attribute: 'skip-init-check' }, theme: { type: 'String', reflect: false, attribute: 'theme' }, userSettings: { type: 'Object', reflect: false, attribute: 'user-settings' }, diff --git a/container/src/services/container.service.ts b/container/src/services/container.service.ts index b57cf934f4..5ac67c92a2 100644 --- a/container/src/services/container.service.ts +++ b/container/src/services/container.service.ts @@ -108,7 +108,7 @@ export class ContainerService { context: targetCnt.context || {}, internal: { thirdPartyCookieCheck: { - disabled: this.handleCookieCheck(targetCnt) + disabled: targetCnt.skipCookieCheck === 'true' } }, authData: targetCnt.authData || {} @@ -196,20 +196,6 @@ export class ContainerService { registerContainer(thisComponent: HTMLElement): void { this.getContainerManager().container.push(thisComponent); } - - private handleCookieCheck(targetCnt): boolean { - let skipCookieCheck = false; - - if (targetCnt.getAttribute) { - if (typeof targetCnt.getAttribute('skip-cookie-check') === 'string') { - skipCookieCheck = targetCnt.getAttribute('skip-cookie-check') === 'true'; - } else if (typeof targetCnt.skipCookieCheck !== 'undefined') { - skipCookieCheck = targetCnt.skipCookieCheck; - } - } - - return skipCookieCheck; - } } export const containerService = new ContainerService(); diff --git a/container/test-app/iframe/iframe-cookies.html b/container/test-app/iframe/iframe-cookies.html new file mode 100644 index 0000000000..5a63d28aff --- /dev/null +++ b/container/test-app/iframe/iframe-cookies.html @@ -0,0 +1,39 @@ + + + + + +

+ This page is used to test Container Luigi CLient API functionalities for iframe + based microfrontend +

+ +
+ + +
+ + + + diff --git a/container/test-app/iframe/microfrontend.html b/container/test-app/iframe/microfrontend.html index 80e1159dc7..b69fc1a4e9 100644 --- a/container/test-app/iframe/microfrontend.html +++ b/container/test-app/iframe/microfrontend.html @@ -4,7 +4,7 @@ - +