OfficeDev / office-js

A repo and NPM package for Office.js, corresponding to a copy of what gets published to the official "evergreen" Office.js CDN, at https://appsforoffice.microsoft.com/lib/1/hosted/office.js.
https://learn.microsoft.com/javascript/api/overview
Other
653 stars 93 forks source link

Error using Office.context.ui.messageParent() method in Office.js for Excel Online in Edge #4527

Open stefanraghavan opened 1 month ago

stefanraghavan commented 1 month ago

Problem Overview

I'm getting the following error: Uncaught TypeError: Cannot read properties of undefined (reading 'messageParent') when trying to authenticate the user upon logging into the add-in.

I have a server that authenticates the user and sends a successful sign-in response back to the client (taskpane) using the Office.context.ui.messageParent() method (see attached for relevant server code).

It works fine on the desktop versions of Excel for Mac and Windows, but not for the web version of Excel. It looks like the Office.context.ui.messageParent() method may not be available for the web version of Excel.

Expected Behavior

Upon successful sign-in, the Microsoft authentication popup is expected to close and the user should be redirected to the main app body of the taskpane.

Current Behavior

Upon successful sign-in, the Microsoft authentication popup remains open and the main app body is still hidden. See the live example link below.

Steps to Reproduce, or Live Example

Context

I am unable to use the Office.context.ui.messageParent() method to respond back to the client and initiate a successful login.

Environment

Useful logs

See screenshot for logs and relevant server and client code pasted below. Screenshot 2024-05-28 131919

Releavant Code:


// SERVER CODE - Function to send success message with JWT
function sendSuccessMessage(req, res, subscriptionStatus, jwtToken) {
    const nonce = res.locals.nonce;
    console.log("Sending token to client:", jwtToken);
    res.send(`
        <script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js" nonce="${nonce}"></script>
        <script nonce="${nonce}">
        Office.onReady(() => {
            const message = JSON.stringify({
                status: 'success',
                subscriptionStatus: '${subscriptionStatus}',
                accessToken: '${jwtToken}' // Retain the name accessToken
            });
            const targetOrigin = 'https://formulainsightstorage.z22.web.core.windows.net';
            Office.context.ui.messageParent(message, { targetOrigin: targetOrigin });
        });
        </script>
    `);
}

// CLIENT CODE
Office.onReady((info) => {
    if (info.host === Office.HostType.Excel) {
      checkIfAuthenticated().then(isAuthenticated => {
        if (isAuthenticated) {
          fetchUsageLimits().then(() => {
            fetchExplanationCount().then(() => {
              updateUsageDisplay();
              showAppBody();  // Show main application body if already authenticated
            });
          });
        } else {
          setupLoginButton();  // Setup login button if not authenticated
        }
      }).catch(error => {
        console.error('Error checking authentication status:', error);
        setupLoginButton();  // Fallback to setup login button
      });
    }
  });

  function setupLoginButton() {
    const loginButton = document.getElementById("loginButton");
    loginButton.addEventListener("click", function () {
      // Open a dialog for login
      Office.context.ui.displayDialogAsync(
        'https://formula-insight-client-signin.azurewebsites.net/signin',
        { height: 60, width: 30, displayInIframe: false },
        function (asyncResult) {
          if (asyncResult.status === Office.AsyncResultStatus.Failed) {
            console.error('Failed to open dialog:', asyncResult.error.message);
          } else {
            // This is where your function comes into play
            dialog = asyncResult.value;  // Set the global 'dialog' variable
            console.log("Dialog assigned:", dialog);
            dialog.addEventHandler(Office.EventType.DialogMessageReceived, processMessage);
            dialog.addEventHandler(Office.EventType.DialogEventReceived, processDialogEvent);
          }
        }
      );
    });
  }

  function processMessage(arg) {
    try {
      const messageFromDialog = JSON.parse(arg.message);
      console.log("Received message: ", messageFromDialog);

      if (messageFromDialog.status === 'success') {
        sessionStorage.setItem('isAuthenticated', 'true');
        const accessToken = messageFromDialog.accessToken; // MSAL access token

        if (accessToken) {
          sessionStorage.setItem('accessToken', accessToken);
          fetchUsageLimits().then(() => {
            fetchExplanationCount().then(() => {
              updateUsageDisplay();
            });
          });
        } else {
          console.error("Access token is missing in the response.");
        }

        const previousStatus = sessionStorage.getItem('subscriptionStatus');
        const currentStatus = messageFromDialog.subscriptionStatus;
        sessionStorage.setItem('subscriptionStatus', currentStatus);
        console.log("Subscription Status Set To:", currentStatus);

        if (previousStatus === 'trialing' && currentStatus === 'active') {
          explanationCount = 0; // Reset the explanation count if status changes to active
        }

        showAppBody();
        dialog.close();
      } else {
        sessionStorage.removeItem('isAuthenticated');
        showError("Login failed. Please signup at");
        dialog.close();
      }
    } catch (error) {
      console.error("Error processing message: ", error);
    }
  }
shanshanzheng-dev commented 1 month ago

Hi @stefanraghavan We'll be looking into this problem, thanks for reporting! we will report back here if we have a suggestion for you. Meanwhile, could you share us manifest if possible? Thanks.

stefanraghavan commented 1 month ago

I have attached the manifest below:


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0" xmlns:ov="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="TaskPaneApp">
  <Id>7510063f-4104-487f-8a27-55f6f6bd8cd3</Id>
  <Version>1.0.0.1</Version>
  <ProviderName>Formula Insight AI</ProviderName>
  <DefaultLocale>en-US</DefaultLocale>
  <DisplayName DefaultValue="Formula Insight"/>
  <Description DefaultValue="A template to get started."/>
  <IconUrl DefaultValue="https://formulainsightstorage.z22.web.core.windows.net/assets/manifest_logo_32x32.png"/>
  <HighResolutionIconUrl DefaultValue="https://formulainsightstorage.z22.web.core.windows.net/assets/manifest_logo_64x64.png"/>
  <SupportUrl DefaultValue="https://www.formulainsight.ai/support"/>
  <AppDomains>
    <AppDomain>https://formulainsight.azurewebsites.net</AppDomain>
    <AppDomain>https://formula-insight-client-signin.azurewebsites.net</AppDomain>
    <AppDomain>https://formulainsightstorage.z22.web.core.windows.net</AppDomain>
  </AppDomains>
  <Hosts>
    <Host Name="Workbook"/>
  </Hosts>
  <DefaultSettings>
    <SourceLocation DefaultValue="https://formulainsightstorage.z22.web.core.windows.net/taskpane.html"/>
  </DefaultSettings>
  <Permissions>ReadWriteDocument</Permissions>
  <VersionOverrides xmlns="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="VersionOverridesV1_0">
    <Hosts>
      <Host xsi:type="Workbook">
        <DesktopFormFactor>
          <GetStarted>
            <Title resid="GetStarted.Title"/>
            <Description resid="GetStarted.Description"/>
            <LearnMoreUrl resid="GetStarted.LearnMoreUrl"/>
          </GetStarted>
          <FunctionFile resid="Commands.Url"/>
          <ExtensionPoint xsi:type="PrimaryCommandSurface">
            <OfficeTab id="TabHome">
              <Group id="CommandsGroup">
                <Label resid="CommandsGroup.Label"/>
                <Icon>
                  <bt:Image size="16" resid="Icon.16x16"/>
                  <bt:Image size="32" resid="Icon.32x32"/>
                  <bt:Image size="64" resid="Icon.64x64"/>
                  <bt:Image size="80" resid="Icon.80x80"/>
                </Icon>
                <Control xsi:type="Button" id="TaskpaneButton">
                  <Label resid="TaskpaneButton.Label"/>
                  <Supertip>
                    <Title resid="TaskpaneButton.Label"/>
                    <Description resid="TaskpaneButton.Tooltip"/>
                  </Supertip>
                  <Icon>
                    <bt:Image size="16" resid="Icon.16x16"/>
                    <bt:Image size="32" resid="Icon.32x32"/>
                    <bt:Image size="64" resid="Icon.64x64"/>
                    <bt:Image size="80" resid="Icon.80x80"/>
                  </Icon>
                  <Action xsi:type="ShowTaskpane">
                    <TaskpaneId>ButtonId1</TaskpaneId>
                    <SourceLocation resid="Taskpane.Url"/>
                  </Action>
                </Control>
              </Group>
            </OfficeTab>
          </ExtensionPoint>
        </DesktopFormFactor>
      </Host>
    </Hosts>
    <Resources>
      <bt:Images>
        <bt:Image id="Icon.16x16" DefaultValue="https://formulainsightstorage.z22.web.core.windows.net/assets/icon-formula-insight.png"/>
        <bt:Image id="Icon.32x32" DefaultValue="https://formulainsightstorage.z22.web.core.windows.net/assets/manifest_logo_32x32.png"/>
        <bt:Image id="Icon.64x64" DefaultValue="https://formulainsightstorage.z22.web.core.windows.net/assets/manifest_logo_64x64.png"/>
        <bt:Image id="Icon.80x80" DefaultValue="https://formulainsightstorage.z22.web.core.windows.net/assets/icon-formula-insight.png"/>
      </bt:Images>
      <bt:Urls>
        <bt:Url id="GetStarted.LearnMoreUrl" DefaultValue="https://go.microsoft.com/fwlink/?LinkId=276812"/>
        <bt:Url id="Commands.Url" DefaultValue="https://formulainsightstorage.z22.web.core.windows.net/commands.html"/>
        <bt:Url id="Taskpane.Url" DefaultValue="https://formulainsightstorage.z22.web.core.windows.net/taskpane.html"/>
      </bt:Urls>
      <bt:ShortStrings>
        <bt:String id="GetStarted.Title" DefaultValue="Get started with your sample add-in!"/>
        <bt:String id="CommandsGroup.Label" DefaultValue="Commands Group"/>
        <bt:String id="TaskpaneButton.Label" DefaultValue="Formula Insight"/>
      </bt:ShortStrings>
      <bt:LongStrings>
        <bt:String id="GetStarted.Description" DefaultValue="Your sample add-in loaded succesfully. Go to the HOME tab and click the 'Show Taskpane' button to get started."/>
        <bt:String id="TaskpaneButton.Tooltip" DefaultValue="Click to Show a Taskpane"/>
      </bt:LongStrings>
    </Resources>
  </VersionOverrides>
</OfficeApp>
stefanraghavan commented 1 month ago

@shanshanzheng-dev I forgot to mention that in order to test the sign in you will need to signup for an account. Please follow the steps below:

  1. Visit https://www.formulainsight.ai/signup.
  2. Sign in with Microsoft credentials.
  3. After login, you will be redirected to the Free Trial plan page. Select Start Free Trial.
  4. On the Stripe checkout page (in test mode, no charges):
    • Use test card number: 4242 4242 4242 4242
    • Use any future expiration date: 12/34
    • Use any three-digit CVC: 123
    • Use any name/address. For convenience, you can use the following:
    • Name: Joel Morgan
    • Address: 2808 Adams Avenue, Frederick, MD 21701
    • Select Start trial and wait until payment is successful and are redirected to the download instructions page.
shanshanzheng-dev commented 1 month ago

Hi @stefanraghavan Thanks for these detailed information. Hi @m-hellesen, could you help to take a look at this? Thanks.

stefanraghavan commented 4 weeks ago

Hey @shanshanzheng-dev and @m-hellesen Do you have any update on this?

This is the final issue that's holding me back from publishing the Addin to the store, so I'd like to resolve this soon.

Thank you.

shanshanzheng-dev commented 4 weeks ago

Hi @stefanraghavan, sorry for slow response. we're looking into the root cause.

mayurandhare13 commented 2 weeks ago

@stefanraghavan @shanshanzheng-dev

Since, window.external.GetContext is undefined it throws Warning: Office.js is loaded outside of Office client and context never gets initialized.

this happens when it calls getAppContextOSF.InitializationHelper.prototype.getAppContext So, for getContext method returns {} that's why Office.context.ui.messageParent throws exception.

I see the server side snippet. when Office is getting loaded outside of Office Client, it doesn't initialize everything. Here, I want to understand how server side is loading the Office. I see you are already using displayDialogAsync. Are you loading the office in your dialog.html ? is it possible that office is getting loaded twice because you are returning script from the sendSuccessMessage. Since, I don't know the dialog.html, I am not able to figure out why its showing that office is loaded outside of office client.

pkkj commented 2 weeks ago

Hi,

When your page is being redirect to the login.microsoftonline.com, try to include the search string in the redirect_uri. For example, when display dialog API is called in Excel Web, the API will launch a page with https://formula-insight-client-signin.azurewebsites.net/signin?_host_Info=excel$web$16.00$en-us$eae06490-8ae3-3ecb-d9c8-ab376278bff3$isDialog$$0. So, the redirect_uri for https://login.microsoftonline.com/ should be https%3A%2F%2Fformula-insight-client-signin.azurewebsites.net%2Fsignin-success%3F_host_Info%3Dexcel%24web%2416.00%24en-us%24eae06490-8ae3-3ecb-d9c8-ab376278bff3%24isDialog%24%240.

And the server side code should handle the redirect URL correctly, since it will contain both _hostinfo and the token.

The reason is, based on the document, your app should land on a real HTML page in the same domain first, and this page should include office.js. And then redirect to the login page in Office.onReady. When office.js is initializing, it will store the necessary environment information (like the host info) in sessionstorage. However it seems that your current implementation is special - the server side issues a 302 status, causing the redirection to login page directly without "landing on" a real html page in your server. As a result, when redirecting to "signin-success" page, office.js is not able to get any environment info and therefore the API is not working.

Thanks,