sp-dev-docs icon indicating copy to clipboard operation
sp-dev-docs copied to clipboard

SPFx in Teams tab not rendering until browsed to in spo

Open JonoSuave opened this issue 1 year ago • 6 comments

Target SharePoint environment

SharePoint Online

What SharePoint development model, framework, SDK or API is this about?

💥 SharePoint Framework

Developer environment

macOS

What browser(s) / client(s) have you tested

  • [ ] 💥 Internet Explorer
  • [X] 💥 Microsoft Edge
  • [X] 💥 Google Chrome
  • [ ] 💥 FireFox
  • [ ] 💥 Safari
  • [ ] mobile (iOS/iPadOS)
  • [ ] mobile (Android)
  • [ ] not applicable
  • [X] other (enter in the "Additional environment details" area below)

Additional environment details

  • MacOS Teams Desktop Client

Describe the bug / error

I have a custom Teams personal app that has two tabs: one tab surfaces an spfx webpart, while the other a React SPA provisioned and deployed using the Teams Toolkit. After a couple days of not browsing to the custom Teams app, however, the tab that surfaces the SPFx component is stuck on a spinner -- the other tab has no problems. The console gives me the following error: App resource defined in manifest and iframe origin do not match failureCallback @ TeamsLogon.aspx?SPFX...rceLocale=en-us:240

In order to get the spfx tab to render I have to do the following in Chrome and Edge: browse to a SharePoint page with the webpart and then refresh the static tab in Teams

On the Teams Desktop client I added a reference to the SharePoint page in a Teams Channel and after browsing to that Teams channel tab I can then see the spfx component in the custom app's spfx tab

Steps to reproduce

  • Create 3 webparts: a search webpart, a directory webpart, and a faculty webpart that references the two main components of the search and directory webparts. For example, the FacultyWebpart.tsx will render the Faculty.tsx component, whose function is to render the Search.tsx and Faculty.tsx components.
  • Create your own teams manifest.json file in the teams folder of the SharePoint solution, so that when you deploy the sppkg it knows which webpart to make available in Teams
  • Create a Fluent UI Personal app using the Teams Toolkit
  • In the manifest.json inside the appPackage folder, create two static tabs: one that references the spfx webpart to be served and the other tab for the React app provided by Teams Toolkit
  • Provision, Deploy, and send to Production

Expected behavior

I shouldn't have to browse to the SPO page with the spfx webpart to then see it in the custom Teams app Screenshot 2024-09-16 at 12 48 05 PM Screenshot 2024-09-16 at 12 48 25 PM

JonoSuave avatar Sep 16 '24 18:09 JonoSuave

Here's what my Teams Toolkit manifest.json looks like:

{
  "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.17/MicrosoftTeams.schema.json",
  "version": "1.26.0",
  "manifestVersion": "1.17",
  "id": "bda4dea9-fa92-4f2c-81a1-8b95a8c70e40",
  "name": {
    "short": "Directory",
    "full": "Full name for Directory"
  },
  "developer": {
    "name": "JourneyTEAM, LLC",
    "websiteUrl": "https://www.journeyteam.com",
    "privacyUrl": "https://www.journeyteam.com",
    "termsOfUseUrl": "https://www.journeyteam.com"
  },
  "description": {
    "short": "Short description of DepartmentDirectory",
    "full": "Full description of DepartmentDirectory"
  },
  "icons": {
    "outline": "outline.png",
    "color": "color.png"
  },
  "accentColor": "#FFFFFF",
  "staticTabs": [
    {
      "entityId": "SPFxEmployeeDirectoryTab",
      "name": "Faculty & Staff",
      "contentUrl": "https://(redacted_domain_name).sharepoint.com/_layouts/15/TeamsLogon.aspx?SPFX=true&dest=/_layouts/15/teamshostedapp.aspx%3Fteams%26personal%26componentId=386dd1b8-71f5-4d5d-a568-b44b211067f0%26forceLocale=en-us",
      "scopes": [
        "personal"
      ],
      "context": [
        "personalTab"
      ]
    },
    {
      "entityId": "DepartmentDirectoryTab",
      "name": "Departments",
      "contentUrl": "https://ashy-hill-0f884b110.5.azurestaticapps.net/index.html#/department-directory",
      "websiteUrl": "https://ashy-hill-0f884b110.5.azurestaticapps.net/index.html#/department-directory",
      "scopes": [
        "personal"
      ]
    },
    {
      "entityId": "about",
      "scopes": [
        "personal"
      ]
    }
  ],
  "validDomains": [
    "(redacted_domain_name).sharepoint.com",
    "ashy-hill-0f884b110.5.azurestaticapps.net",
    "*.login.microsoftonline.com",
    "*.sharepoint.com",
    "*.sharepoint-df.com",
    "spoppe-a.akamaihd.net",
    "spoprod-a.akamaihd.net",
    "ashy-hill-0f884b110.5.azurestaticapps.net*"
  ],
  "webApplicationInfo": {
    "id": "2db8265f-5a18-4d83-a39c-335603046850",
    "resource": "api://ashy-hill-0f884b110.5.azurestaticapps.net/2db8265f-5a18-4d83-a39c-335603046850"
  },
  "authorization": {
    "permissions": {
      "resourceSpecific": []
    }
  }
}

JonoSuave avatar Sep 18 '24 00:09 JonoSuave

Here's what my spfx teams manifest.json looks like:

{
	"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.17/MicrosoftTeams.schema.json",
	"version": "1.2",
	"manifestVersion": "1.17",
	"id": "aa1185ab-38c1-4621-b54d-90e94523e602",
	"developer": {
		"name": "JourneyTEAM, LLC",
		"mpnId": "",
		"websiteUrl": "https://www.journeyteam.com",
		"privacyUrl": "https://www.journeyteam.com",
		"termsOfUseUrl": "https://www.journeyteam.com"
	},
	"name": {
		"short": "Teams Faculty Directory",
		"full": "Teams Faculty Directory"
	},
	"description": {
		"short": "Teams Employee Directory description",
		"full": "Teams Employee Directory description"
	},
	"icons": {
		"outline": "aa1185ab-38c1-4621-b54d-90e94523e602_outline.png",
		"color": "aa1185ab-38c1-4621-b54d-90e94523e602_color.png"
	},
	"accentColor": "#FFFFFF",
	"staticTabs": [
		{
			"entityId": "Belmont Faculty Directory",
			"name": "Faculty Directory",
			"contentUrl": "https://(redacted_domain_name).sharepoint.com/_layouts/15/TeamsLogon.aspx?SPFX=true&dest=/_layouts/15/teamshostedapp.aspx%3Fteams%26personal%26componentId=386dd1b8-71f5-4d5d-a568-b44b211067f0%26forceLocale=en-us",
			"scopes": ["personal"],
			"context": ["personalTab"]
		},
		{
			"entityId": "about",
			"scopes": ["personal"]
		}
	],
	"validDomains": [
		"*.login.microsoftonline.com",
		"*.sharepoint.com",
		"*.sharepoint-df.com",
		"spoppe-a.akamaihd.net",
		"spoprod-a.akamaihd.net",
		"resourceseng.blob.core.windows.net",
		"msft.spoppe.com"
	],
	"webApplicationInfo": {
		"id": "00000003-0000-0ff1-ce00-000000000000",
		"resource": ""
	}
}

JonoSuave avatar Sep 18 '24 00:09 JonoSuave

Here's an example of how I call the two components from two separate webparts:

import * as React from "react";
import { useMemo, useState } from "react";
import styles from "./FacultyStudentTeamsDirectory.module.scss";
import type { IFacultyStudentTeamsDirectoryProps } from "./IFacultyStudentTeamsDirectoryProps";
import EmployeeDirectory from "../../employeeDirectory/components/EmployeeDirectory";
import { IReactTemplateProps } from "@journeyteam/directory-extensions";
import SearchBox from "../../searchBox/components/SearchBox";
import { ISelectedRefiner, ISelectedRefinerValues } from "../../../models/refiner";
import * as SPSearch from "@pnp/sp/search/types";

export declare enum TemplateType {
	adaptiveCard = "adaptive-card",
	react = "react",
	handlebars = "handelbars",
}
export default function FacultyStudentTeamsDirectory(props: IFacultyStudentTeamsDirectoryProps) {
	const {
		isDarkTheme,
		hasTeamsContext,
		initialSearchText,
		employeeCardTemplate,
		studentView,
		propertyPanelOpen,
		focusSections,
		directoryRefiners,
		refiners,
	} = props;

	const [selectedRefiners, setSelectedRefiners] =
		useState<ISelectedRefinerValues[]>(directoryRefiners);
	const [searchBoxRefinersSourceData, setSearchBoxRefinersSourceData] = useState<
		SPSearch.IRefiner[]
	>([]);
	const [searchText, setSearchText] = useState(initialSearchText ? initialSearchText : "");
	const [cardWidth, setCardWidth] = useState(275);

	useMemo(() => {
		const handleResize = () => {
			if (window.innerWidth < 500) {
				setCardWidth(150);
			} else if (window.innerWidth < 800) {
				setCardWidth(200);
			} else {
				setCardWidth(275);
			}
		};

		// Call the handleResize function initially to set the width based on the current viewport size
		handleResize();

		// Set up the event listener for the resize event
		window.addEventListener("resize", handleResize);

		// Clean up the event listener when the component is unmounted
		return () => {
			window.removeEventListener("resize", handleResize);
		};
	}, []);

	const searchBoxRefinersChanged = (searchBoxRefiners: ISelectedRefiner[]) => {
		const mappedSelectedRefiners = refiners.map<ISelectedRefinerValues>((refiner) => {
			const matchedRefiners = searchBoxRefiners.filter(
				(selectedRefiner) => selectedRefiner.Name === refiner.PropertyName
			);
			return {
				PropertyName: refiner.PropertyName,
				SelectedRefiners: matchedRefiners,
			};
		});
		setSelectedRefiners(mappedSelectedRefiners);
	};

	const onSearchChange = (val: string) => {
		setSearchText(val);
	};

	const _refinersChanged = (refiners: SPSearch.IRefiner[]) => {
		if (searchBoxRefinersSourceData.length === 0) {
			setSearchBoxRefinersSourceData(refiners);
		}
		if (searchBoxRefinersSourceData[0]?.Entries.length < 20) {
			setSearchBoxRefinersSourceData(refiners);
		}
	};

	return (
		<section
			className={`${styles.facultyStudentTeamsDirectory} ${hasTeamsContext ? styles.teams : ""}`}>
			<SearchBox
				onSearchChanged={onSearchChange}
				initialSearchText={initialSearchText}
				refinerSourceData={searchBoxRefinersSourceData}
				refiners={props.refiners}
				onSelectedRefinersChanged={searchBoxRefinersChanged}
				searchByFilters={props.searchByFilters}
				searchByLabel={undefined}
				searchByPlaceholder="Search By"
				refinersLabel=""
				refinersPlaceholder="Search Refiners"
				searchLabel={""}
				searchPlaceholder={""}
			/>
			<br />
			<br />

			<EmployeeDirectory
				 searchText={`${
					searchText}*`}
				itemsPerPage={20}
				preFilter={`-"SPS-HideFromAddressLists":1`}
				refiners={selectedRefiners}
				onRefinersChanged={_refinersChanged}
				useLibraryTemplate={false}
				showProfileLink={undefined}
				customStyles={undefined}
				employeeCardTemplate={employeeCardTemplate}
				focusSections={focusSections}
				headerSections={props.headerSections}
				otherSections={[]}
				sourceId={undefined}
				sort={[
					{
						Property: "firstName",
						Direction: 0,
					},
				]}
				hiddenRefiner={[]}
				serviceScope={props.serviceScope}
				context={props.context}
				templates={props.templates}
				editMode={true}
				employeeCardWidth={cardWidth}
				propertyPanelOpen={propertyPanelOpen}
				paginationLocation={1}
				toggleDirectReportsExport={false}
				directReportsExportUrl=""
				studentView={studentView}
				isDarkTheme={isDarkTheme}
			/>
		</section>
	);
}

JonoSuave avatar Sep 18 '24 00:09 JonoSuave

Further update...just deploying my spfx solution and syncing to Teams works as a standalone personal app. The personal app that has multiple personal tabs (one being the spfx component and the other being a React app provisioned and deployed in Entra) doesn't work for the spfx tab until I first navigate to the standalone spfx personal app

Screenshot 2024-09-18 at 9 09 25 AM

JonoSuave avatar Sep 18 '24 15:09 JonoSuave

@VesaJuvonen any update on your guys end by chance?

JonoSuave avatar Oct 01 '24 01:10 JonoSuave

From what I'm seeing, seems the issue may lie in that the teams app manifest only allows for one set of id and resource to be referenced in the webApplicationInfo. In my case, one tab is leveraging an spfx component (resource would be the teamsitedomain and id the multi-tentant Microsoft Graph app id) and the other a teams app that was provisioned in entra using the Teams Toolkit.

JonoSuave avatar Oct 01 '24 18:10 JonoSuave

Hello @JonoSuave, Thank you for bringing this issue to our attention. We will look into it and get back to you shortly. Could you please confirm if the issue still persists for you?

Amey-MSFT avatar Apr 30 '25 11:04 Amey-MSFT

I believe this issue occurs when you have two tabs on a team’s static app with one being a teams-first app and the other being an spfx-first app.

On Wed, Apr 30, 2025 at 5:25 AM Amey-MSFT @.***> wrote:

Amey-MSFT left a comment (SharePoint/sp-dev-docs#9920) https://github.com/SharePoint/sp-dev-docs/issues/9920#issuecomment-2841672623

Hello @JonoSuave https://github.com/JonoSuave, Thank you for bringing this issue to our attention. We will look into it and get back to you shortly. Could you please confirm if the issue still persists for you?

— Reply to this email directly, view it on GitHub https://github.com/SharePoint/sp-dev-docs/issues/9920#issuecomment-2841672623, or unsubscribe https://github.com/notifications/unsubscribe-auth/AFPH445NYE2NZ6I32O7EIBT24CXMHAVCNFSM6AAAAABOJ5GQJCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDQNBRGY3TENRSGM . You are receiving this because you were mentioned.Message ID: @.***>

JonoSuave avatar Apr 30 '25 14:04 JonoSuave

Hello @JonoSuave, We were able to reproduce the issue, and we are investigating it. We have logged this as a bug, and our engineering team will look into it. Thank you!

Amey-MSFT avatar May 05 '25 04:05 Amey-MSFT

Awesome, thanks for the update!

On Sun, May 4, 2025 at 10:12 PM Amey-MSFT @.***> wrote:

Amey-MSFT left a comment (SharePoint/sp-dev-docs#9920) https://github.com/SharePoint/sp-dev-docs/issues/9920#issuecomment-2849864583

Hello @JonoSuave https://github.com/JonoSuave, We were able to reproduce the issue, and we are investigating it. We have logged this as a bug, and our engineering team will look into it. Thank you!

— Reply to this email directly, view it on GitHub https://github.com/SharePoint/sp-dev-docs/issues/9920#issuecomment-2849864583, or unsubscribe https://github.com/notifications/unsubscribe-auth/AFPH446CJ3N7YZC52BBOWID243QJ7AVCNFSM6AAAAABOJ5GQJCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDQNBZHA3DINJYGM . You are receiving this because you were mentioned.Message ID: @.***>

JonoSuave avatar May 05 '25 04:05 JonoSuave