Repository: SAP/luigi (js)

Issue:
Add client getToken function support to Container

Pull request:
Add getToken function support to Luigi Container
<!--   Thank you for your contribution. Before you submit the pull request:
1. Follow the contributing guidelines: https://github.com/SAP/luigi/blob/main/CONTRIBUTING.md
2. Test your changes and attach their results to the pull request.
3. Update any relevant documentation.
4. Sign the Contributor License Agreement.
-->

**Description**

Changes proposed in this pull request:

- add capability to use the [getToken](https://docs.luigi-project.io/docs/luigi-client-api?section=gettoken) function inside the iframe based container 
- Sends a message to the container iframe based microfrontend to set the auth data. This allows LuigiClient to receive the token through the getToken function inside the microfrontend.
- Added Cypress tests
- Added unit tests


- **Potential future merge conflict with [Light DOM Container PR](https://github.com/SAP/luigi/pull/3697) since it affects LuigiContainer.svelte files** _due to the new Prettier for Svelte options recently added_ - merge that one first and resolve Merge Conflicts here afterward, to make reviewing this PR easier.

**Related issue(s)**
<!-- If you refer to a particular issue, provide its number. For example, `Resolves #123`, `Fixes #43`, or `See also #33`. -->
Resolves #3784 

Patch:
diff --git a/container/src/LuigiContainer.svelte b/container/src/LuigiContainer.svelte
index d3807e09..e703f3d5 100644
--- a/container/src/LuigiContainer.svelte
+++ b/container/src/LuigiContainer.svelte
@@ -23,7 +23,8 @@
       hasBack: { type: 'Boolean', reflect: false, attribute: 'has-back' },
       documentTitle: { type: 'String', reflect: false, attribute: 'document-title' },
       allowRules: { type: 'Array', reflect: false, attribute: 'allow-rules' },
-      sandboxRules: { type: 'Array', reflect: false, attribute: 'sandbox-rules' }
+      sandboxRules: { type: 'Array', reflect: false, attribute: 'sandbox-rules' },
+      authData: { type: 'Object', reflect: false, attribute: 'auth-data' }
     },
     extend: customElementConstructor => {
       let notInitFn = name => {
@@ -35,8 +36,13 @@
         updateContext = notInitFn('updateContext');
         closeAlert = notInitFn('closeAlert');
         attributeChangedCallback(name, oldValue, newValue) {
-          if (this.containerInitialized && name === 'context') {
-            this.updateContext(JSON.parse(newValue));
+          if (this.containerInitialized) {
+            if (name === 'context') {
+              this.updateContext(JSON.parse(newValue));
+            }
+            if (name === 'auth-data') {
+              ContainerAPI.updateAuthData(this.iframeHandle, JSON.parse(newValue));
+            }
           }
         };
         getNoShadow(){
@@ -78,6 +84,7 @@
 
   export let userSettings: any;
   export let anchor: string;
+  export let authData: any;
 
   const iframeHandle:
     | {
@@ -218,4 +225,4 @@
       />
     {/if}
   {/if}
-</main>
\ No newline at end of file
+</main>
diff --git a/container/src/api/container-api.ts b/container/src/api/container-api.ts
index 6b07f543..45890774 100644
--- a/container/src/api/container-api.ts
+++ b/container/src/api/container-api.ts
@@ -3,7 +3,7 @@ import { containerService } from '../services/container.service';
 
 export class ContainerAPIFunctions {
   /**
-   * Updates the context of the microfrontend by sending a message to the iframe or webcomponent that sets the context of the microfrontend
+   * Updates the context of the microfrontend by sending a message to the iframe that sets the context of the microfrontend
    * @param contextObj The context data
    * @param internal internal luigi legacy data
    * @param iframeHandle a reference to the iframe that is needed to send a message to it internally
@@ -17,7 +17,7 @@ export class ContainerAPIFunctions {
           context: contextObj,
           internal: internalParameter,
           // set withoutSync to true for the container case to avoid browser history changes from luigi client
-          withoutSync: true
+          withoutSync: true,
         },
         LuigiInternalMessageID.SEND_CONTEXT_OBJECT
       );
@@ -26,6 +26,23 @@ export class ContainerAPIFunctions {
     }
   };
 
+  /**
+   * Updates the auth data of the microfrontend by sending a message to the iframe that sets the authData of the microfrontend
+   * @param iframeHandle a reference to the iframe that is needed to send a message to it internally
+   * @param authData the authData object being sent to the microfrontend
+   */
+  updateAuthData = (iframeHandle: any, authData: any) => {
+    if (iframeHandle && authData) {
+      containerService.sendCustomMessageToIframe(
+        iframeHandle,
+        {authData},
+        LuigiInternalMessageID.AUTH_SET_TOKEN
+      );
+    } else {
+      console.warn('Attempting to update auth data on inexisting iframe or authData');
+    }
+  };
+
   /**
    * Send a custom message to the referenced iframe or web component
    * @param id a string containing the message id
diff --git a/container/src/constants/internal-communication.ts b/container/src/constants/internal-communication.ts
index 5125d9e3..bddedb86 100644
--- a/container/src/constants/internal-communication.ts
+++ b/container/src/constants/internal-communication.ts
@@ -112,4 +112,9 @@ export namespace LuigiInternalMessageID {
    * A message emmitted from the Microfrontend when a request to set the 'dirty status' (ex: avoid closing if usaved changes) is sent
    */
   export const SET_DIRTY_STATUS_REQUEST = 'luigi.set-page-dirty';
+
+  /**
+   * A message emmitted from the Microfrontend when a request to set the 'token auth' is sent
+   */
+    export const AUTH_SET_TOKEN = 'luigi.auth.tokenIssued';
 }
diff --git a/container/src/services/container.service.ts b/container/src/services/container.service.ts
index 081de511..24237a4b 100644
--- a/container/src/services/container.service.ts
+++ b/container/src/services/container.service.ts
@@ -106,7 +106,8 @@ export class ContainerService {
                   {
                     msg: LuigiInternalMessageID.SEND_CONTEXT_HANDSHAKE,
                     context: targetCnt.context || {},
-                    internal: {}
+                    internal: {},
+                    authData: targetCnt.authData || {},
                   },
                   '*'
                 );
diff --git a/container/typings/LuigiContainer.svelte.d.ts b/container/typings/LuigiContainer.svelte.d.ts
index 7a421a35..27b620ba 100644
--- a/container/typings/LuigiContainer.svelte.d.ts
+++ b/container/typings/LuigiContainer.svelte.d.ts
@@ -184,4 +184,10 @@ export default class LuigiContainer extends HTMLElement {
    * @since 1.0.0
    */
   init(): void;
+
+  /**
+   * The authData value to be passed to the iframe-based micro frontend.
+   * @since NEXT_RELEASE
+   */
+    authData: Object;
 }
diff --git a/docs/luigi-container-api.md b/docs/luigi-container-api.md
index 1b902c26..a382e416 100644
--- a/docs/luigi-container-api.md
+++ b/docs/luigi-container-api.md
@@ -323,3 +323,13 @@ Returns **void**
 **Meta**
 
 -   **since**: 1.0.0
+
+### authData
+
+The authData value to be passed to the iframe-based micro frontend.
+
+Type: [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)
+
+**Meta**
+
+-   **since**: NEXT_RELEASE


Test patch:
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 bc5fa13d..74724c4e 100644
--- a/container/cypress/e2e/test-app/iframe/iframe-container.cy.js
+++ b/container/cypress/e2e/test-app/iframe/iframe-container.cy.js
@@ -39,4 +39,29 @@ describe('Iframe Container Test', () => {
           });
       });
   });
+
+  it('set auth token', () => {
+    const stub = cy.stub();
+    cy.on('window:alert', stub);
+
+    cy.visit('http://localhost:8080/iframe/iframeContainer.html');
+
+    cy.get('button[id="update-token"]').click();
+
+    cy.get('[data-test-id="iframe-based-container-test"]')
+      .shadow()
+      .get('iframe')
+      .then(iframe => {
+        const $body = iframe.contents().find('body');
+        cy.wrap($body)
+          .contains('test get token')
+          .click()
+          .then(() => {
+            cy.wrap(stub).should(
+              'have.been.calledWith',
+              'Custom message recieved: {"id":"token.updated","_metaData":{},"data":{"value":"updated token"}}'
+            );
+          });
+      });
+  });
 });
diff --git a/container/test-app/iframe/iframeContainer.html b/container/test-app/iframe/iframeContainer.html
index 60c77ff9..f3e252cd 100644
--- a/container/test-app/iframe/iframeContainer.html
+++ b/container/test-app/iframe/iframeContainer.html
@@ -11,12 +11,14 @@
     </h3>
     <button id="btn-1">Send Custom Message to iFrame Container</button>
     <button id="update-ctx">Update Ctx</button>
+    <button id="update-token">Update Access Token</button>
 
     <div style="border:solid 1px blue">
       <luigi-container
         data-test-id="iframe-based-container-test"
         viewURL="./microfrontend.html"
         context='{"title": "Projects", "content":" "}'
+        auth-data='{ "accessToken": "my-token" }'
       >
       </luigi-container>
     </div>
@@ -38,6 +40,7 @@
       // Some glue code
       const sendCustomMessageBtn = document.getElementById('btn-1');
       const updateContextButton = document.getElementById('update-ctx');
+      const updateAccessTokenButton = document.getElementById('update-token');
 
       // for testing send Custom message functionality
       sendCustomMessageBtn.addEventListener('click', () => {
@@ -50,6 +53,10 @@
         luigiContainer.updateContext({ myContext: 'some context data' });
       });
 
+      updateAccessTokenButton.addEventListener('click', () => {
+        luigiContainer.setAttribute('auth-data', '{"accessToken": "updated token"}');
+      });
+
       luigiContainer.addEventListener(Events.NAVIGATION_REQUEST, event => {
         if (confirm('Do you want to leave this page?')) {
           window.location = event.detail.link;
diff --git a/container/test-app/iframe/microfrontend.html b/container/test-app/iframe/microfrontend.html
index dbe180c4..bc88bc58 100644
--- a/container/test-app/iframe/microfrontend.html
+++ b/container/test-app/iframe/microfrontend.html
@@ -63,6 +63,7 @@
       <button onclick="testCheckPathExists()">test check path exists</button>
       <button onclick="testSetDirtyStatus()">test set dirty status</button>
       <button onclick="testShowAlert()">test show alert</button>
+      <button onclick="testGetToken()">test get token</button>
     </div>
     <script>
       // List of onclick handlers for client calls
@@ -153,6 +154,14 @@
         LuigiClient.uxManager().setDirtyStatus(true);
       }
 
+      function testGetToken() {
+        // send back message with get Token value
+        LuigiClient.sendCustomMessage({
+          id: 'token.updated',
+          value: LuigiClient.getToken()
+        });
+      }
+
       function testShowAlert() {
         let dt = new Date();
         let time = dt.getMilliseconds() + '';
@@ -213,6 +222,12 @@
           'Received Custom Message: ' + data.milliseconds;
       });
 
+      LuigiClient.addCustomMessageListener('update', data => {
+        console.log('Custom Message Received inside iframe Container', data);
+        document.getElementById('content').innerHTML =
+          'Received Custom Message: ' + data.milliseconds;
+      });
+
       // fallback visibility if no initlistener called for 3 seconds
       setTimeout(function() {
         document.getElementById('textCnt').classList.add('visible');
diff --git a/container/test/api/container-api.spec.ts b/container/test/api/container-api.spec.ts
index 0fd2d85f..5bbdfb0c 100644
--- a/container/test/api/container-api.spec.ts
+++ b/container/test/api/container-api.spec.ts
@@ -61,6 +61,75 @@ describe('Container Service', () => {
         });
     });
 
+    describe('updateAuthData', () => {
+        let containerAPI = new ContainerAPIFunctions();
+
+        it('iframeHandle exists, authData exists', () => {
+            // mock and spy
+            const authData = { someData: 'mytoken'};
+            const iframeHandle = {
+                data: 'test'
+            };
+            containerService.sendCustomMessageToIframe = jest.fn();
+            const spy = jest.spyOn(containerService, 'sendCustomMessageToIframe');
+
+            // act
+            containerAPI.updateAuthData(iframeHandle, authData);
+
+            // assert
+            expect(spy).toHaveBeenCalledWith(iframeHandle, {authData}, LuigiInternalMessageID.AUTH_SET_TOKEN)
+        });
+
+        it('iframeHandle undefined, authData exists', () => {
+             // mock and spy
+             const authData = undefined;
+             const iframeHandle = undefined;
+             containerService.sendCustomMessageToIframe = jest.fn();
+             const sendCustomMSGSpy = jest.spyOn(containerService, 'sendCustomMessageToIframe');
+             const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(jest.fn());
+ 
+             // act
+             containerAPI.updateAuthData(iframeHandle, authData);
+ 
+             // assert
+             expect(sendCustomMSGSpy).not.toHaveBeenCalled();
+             expect(consoleWarnSpy).toHaveBeenCalledWith('Attempting to update auth data on inexisting iframe or authData')
+        });
+
+
+        it('iframeHandle exists, authData undefined', () => {
+             // mock and spy
+             const authData = undefined;
+             const iframeHandle = undefined;
+             containerService.sendCustomMessageToIframe = jest.fn();
+             const sendCustomMSGSpy = jest.spyOn(containerService, 'sendCustomMessageToIframe');
+             const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(jest.fn());
+ 
+             // act
+             containerAPI.updateAuthData(iframeHandle, authData);
+ 
+             // assert
+             expect(sendCustomMSGSpy).not.toHaveBeenCalled();
+             expect(consoleWarnSpy).toHaveBeenCalledWith('Attempting to update auth data on inexisting iframe or authData')
+        });
+
+        it('iframeHandle undefined, authData undefined', () => {
+            // mock and spy
+            const authData = undefined;
+            const iframeHandle = undefined;
+            containerService.sendCustomMessageToIframe = jest.fn();
+            const sendCustomMSGSpy = jest.spyOn(containerService, 'sendCustomMessageToIframe');
+            const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(jest.fn());
+
+            // act
+            containerAPI.updateAuthData(iframeHandle, authData);
+
+            // assert
+            expect(sendCustomMSGSpy).not.toHaveBeenCalled();
+            expect(consoleWarnSpy).toHaveBeenCalledWith('Attempting to update auth data on inexisting iframe or authData')
+        });
+    });
+
     describe('closeAlert', () => {
         let containerAPI = new ContainerAPIFunctions();
 
@@ -80,7 +149,6 @@ describe('Container Service', () => {
         });
     });
 
-
     describe('sendCustomMessage', () => {
         let containerAPI = new ContainerAPIFunctions();
         beforeEach(() => {
diff --git a/container/test/services/container.service.spec.ts b/container/test/services/container.service.spec.ts
index 0cc0bf4d..5c146767 100644
--- a/container/test/services/container.service.spec.ts
+++ b/container/test/services/container.service.spec.ts
@@ -87,7 +87,7 @@ describe('getContainerManager messageListener', () => {
      cw.postMessage = postMessageMock;
 
     // Define the message to send and target Origin
-    const message = {"context": {}, "internal": {}, "msg": "luigi.init"};
+    const message = {"context": {}, "internal": {}, "msg": "luigi.init", "authData":{}};
     const targetOrigin = "*";
 
     // Call the method that should trigger postMessage