├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── policies │ └── resourceManagement.yml ├── pull_request_template.md └── workflows │ ├── codeql.yml │ ├── main.yml │ ├── postrelease.yml │ ├── prerelease.yml │ ├── runtimeCheck.yml │ └── size-limit.yml ├── .gitignore ├── .hintrc ├── .npmrc ├── .prettierignore ├── .prettierrc.js ├── .sdl ├── .gdnbaselines ├── .gdnsuppress └── CredScanSuppressions.json ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RUNTIME.md ├── SECURITY.md ├── apps ├── .eslintrc.js ├── blazor-test-app │ ├── .gitignore │ ├── App.razor │ ├── Blazor-Test-App.csproj │ ├── Components │ │ ├── Loading.razor │ │ ├── Welcome.razor │ │ └── Welcome.razor.css │ ├── Config.cs │ ├── FakesAssemblies │ │ ├── Microsoft.AspNetCore.Components.Server.6.0.0.0.Fakes.dll │ │ └── Microsoft.AspNetCore.Components.Server.6.0.0.0.Fakes.fakesconfig │ ├── Interop │ │ ├── InteropModuleBase.cs │ │ └── TeamsSDK │ │ │ ├── EnumDescriptionConverter.cs │ │ │ ├── MicrosoftTeams.cs │ │ │ ├── TeamsContext.cs │ │ │ └── TeamsInstanceSettings.cs │ ├── Pages │ │ ├── Error.cshtml │ │ ├── Error.cshtml.cs │ │ ├── Tab.razor │ │ ├── Tab.razor.css │ │ ├── TabConfig.razor │ │ └── _Host.cshtml │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Shared │ │ └── MainLayout.razor │ ├── _Imports.razor │ ├── appsettings.json │ ├── env │ │ └── .env.dev │ ├── global.json │ ├── package.json │ └── wwwroot │ │ ├── css │ │ ├── open-iconic │ │ │ ├── FONT-LICENSE │ │ │ ├── ICON-LICENSE │ │ │ ├── README.md │ │ │ └── font │ │ │ │ ├── css │ │ │ │ └── open-iconic-bootstrap.min.css │ │ │ │ └── fonts │ │ │ │ ├── open-iconic.eot │ │ │ │ ├── open-iconic.otf │ │ │ │ ├── open-iconic.svg │ │ │ │ ├── open-iconic.ttf │ │ │ │ └── open-iconic.woff │ │ └── site.css │ │ ├── favicon.ico │ │ ├── hello.png │ │ └── js │ │ └── TeamsJsBlazorInterop.js ├── ssr-test-app │ ├── .eslintrc.js │ ├── .gitignore │ ├── README.md │ ├── manifest.json │ ├── package.json │ ├── pages │ │ └── index.tsx │ └── tsconfig.json ├── teams-perf-test-app │ ├── .babelrc │ ├── .eslintrc.js │ ├── README.md │ ├── index.html │ ├── package.json │ ├── public │ │ └── manifest.json │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── components │ │ │ ├── AppInitialization.tsx │ │ │ └── BoxAndButton.tsx │ │ ├── index.css │ │ └── index.tsx │ ├── tsconfig.json │ └── webpack.config.js ├── teams-test-app │ ├── .babelrc │ ├── .eslintrc.js │ ├── README.md │ ├── capabilities.schema.json │ ├── e2e-test-data │ │ ├── app.json │ │ ├── appEntity.json │ │ ├── appInstallDialog.json │ │ ├── authentication.json │ │ ├── barCode.json │ │ ├── calendar.json │ │ ├── call.json │ │ ├── chat.json │ │ ├── clipboard.json │ │ ├── conversation.json │ │ ├── dialog.card.json │ │ ├── dialog.json │ │ ├── dialog.update.json │ │ ├── dialog.url.json │ │ ├── dialog.url.parentCommunication.json │ │ ├── externalAppAuthentication.json │ │ ├── externalAppAuthenticationForCEA.json │ │ ├── externalAppCardActions.json │ │ ├── externalAppCardActionsForCEA.json │ │ ├── externalAppCardActionsForDA.json │ │ ├── externalAppCommands.json │ │ ├── files.json │ │ ├── geoLocation.json │ │ ├── geoLocation.map.json │ │ ├── hostEntity.tab.json │ │ ├── interactive.json │ │ ├── location.json │ │ ├── log.json │ │ ├── mail.handoff.json │ │ ├── mail.json │ │ ├── marketplace.json │ │ ├── media.json │ │ ├── meeting.appShareButton.json │ │ ├── meeting.json │ │ ├── meetingRoom.json │ │ ├── messageChannels.json │ │ ├── monetization.json │ │ ├── nestedAppAuth.json │ │ ├── notifications.json │ │ ├── otherAppStateChange.json │ │ ├── pages.appButton.json │ │ ├── pages.backStack.json │ │ ├── pages.config.json │ │ ├── pages.currentApp.json │ │ ├── pages.fullTrust.json │ │ ├── pages.json │ │ ├── pages.tabs.json │ │ ├── people.json │ │ ├── profile.json │ │ ├── remoteCamera.json │ │ ├── search.json │ │ ├── secondaryBrowser.json │ │ ├── sharing.json │ │ ├── stageView.json │ │ ├── store.json │ │ ├── teams.fullTrust.joinedTeams.json │ │ ├── teams.fullTrust.json │ │ ├── teams.json │ │ ├── thirdPartyCloudStorage.json │ │ ├── video.json │ │ ├── video.mediaStream.json │ │ ├── video.sharedFrame.json │ │ ├── visualMedia.image.json │ │ └── webStorage.json │ ├── index_bundle.html │ ├── index_cdn.html │ ├── index_local.html │ ├── package.json │ ├── public │ │ ├── README.md │ │ ├── color.png │ │ ├── manifest.json │ │ └── outline.png │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── components │ │ │ ├── AppAPIs.tsx │ │ │ ├── AppEntityAPIs.tsx │ │ │ ├── AppInitialization.tsx │ │ │ ├── AppInstallDialog.tsx │ │ │ ├── AuthenticationAPIs.tsx │ │ │ ├── BarCodeAPIs.tsx │ │ │ ├── CalendarAPIs.tsx │ │ │ ├── CallAPIs.tsx │ │ │ ├── Clipboard.tsx │ │ │ ├── Custom.tsx │ │ │ ├── DialogAPIs.tsx │ │ │ ├── DialogCardAPIs.tsx │ │ │ ├── DialogCardBotAPIs.tsx │ │ │ ├── DialogUpdateAPIs.tsx │ │ │ ├── DialogUrlAPIs.tsx │ │ │ ├── DialogUrlBotAPIs.tsx │ │ │ ├── DialogUrlParentCommunicationAPIs.tsx │ │ │ ├── GeoLocationAPIs.tsx │ │ │ ├── HostEntityTabAPIs.tsx │ │ │ ├── Links.tsx │ │ │ ├── LocationAPIs.tsx │ │ │ ├── LogsAPIs.tsx │ │ │ ├── MailAPIs.tsx │ │ │ ├── MarketplaceAPIs.tsx │ │ │ ├── MediaAPIs.tsx │ │ │ ├── MeetingAPIs.tsx │ │ │ ├── MenusAPIs.tsx │ │ │ ├── NestedAppAuthAPIs.tsx │ │ │ ├── OtherAppStateChangeAPIs.tsx │ │ │ ├── PagesAPIs.tsx │ │ │ ├── PagesAppButtonAPIs.tsx │ │ │ ├── PagesBackStackAPIs.tsx │ │ │ ├── PagesConfigAPIs.tsx │ │ │ ├── PagesCurrentAppAPIs.tsx │ │ │ ├── PagesTabsAPIs.tsx │ │ │ ├── PeopleAPIs.tsx │ │ │ ├── ProfileAPIs.tsx │ │ │ ├── RemoteCameraAPIs.tsx │ │ │ ├── SearchAPIs.tsx │ │ │ ├── SecondaryBrowserAPIs.tsx │ │ │ ├── SharingAPIs.tsx │ │ │ ├── StageViewAPIs.tsx │ │ │ ├── StageViewSelfAPIs.tsx │ │ │ ├── StoreApis.tsx │ │ │ ├── TeamsCoreAPIs.tsx │ │ │ ├── ThirdPartyCloudStorageAPIs.tsx │ │ │ ├── ThirdPatryCookies.tsx │ │ │ ├── Version.tsx │ │ │ ├── VideoEffectsApis.tsx │ │ │ ├── VisualMediaAPIs.tsx │ │ │ ├── WebStorageAPIs.tsx │ │ │ ├── privateApis │ │ │ │ ├── ChatAPIs.tsx │ │ │ │ ├── CopilotAPIs.tsx │ │ │ │ ├── ExternalAppAuthenticationAPIs.tsx │ │ │ │ ├── ExternalAppAuthenticationForCEAAPIs.tsx │ │ │ │ ├── ExternalAppCardActionsAPIs.tsx │ │ │ │ ├── ExternalAppCardActionsForCEAAPIs.tsx │ │ │ │ ├── ExternalAppCardActionsForDAAPIs.tsx │ │ │ │ ├── ExternalAppCommandsAPIs.tsx │ │ │ │ ├── FilesAPIs.tsx │ │ │ │ ├── FullTrustAPIs.tsx │ │ │ │ ├── MeetingRoomAPIs.tsx │ │ │ │ ├── MessageChannelAPIs.tsx │ │ │ │ ├── MonetizationAPIs.tsx │ │ │ │ ├── NotificationAPIs.tsx │ │ │ │ ├── PrivateAPIs.tsx │ │ │ │ ├── TeamsAPIs.tsx │ │ │ │ └── VideoEffectsExAPIs.tsx │ │ │ └── utils │ │ │ │ ├── ApiContainer.tsx │ │ │ │ ├── ApiWithCheckboxInput.tsx │ │ │ │ ├── ApiWithTextInput.tsx │ │ │ │ ├── ApiWithoutInput.tsx │ │ │ │ ├── ModuleWrapper.tsx │ │ │ │ ├── PrettyPrintJson.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── isTestBackCompat.ts │ │ │ │ └── utils.css │ │ ├── index.css │ │ ├── index.tsx │ │ ├── pages │ │ │ ├── SecondRoute.tsx │ │ │ └── TestApp.tsx │ │ └── public │ │ │ ├── auth_end.html │ │ │ ├── auth_start.html │ │ │ ├── externalOauth_end.html │ │ │ └── naa_childIframe.html │ ├── tsconfig.json │ ├── webpack.cdn.config.js │ ├── webpack.common.js │ ├── webpack.config.js │ └── webpack.local.config.js ├── tree-shaking-test-app │ ├── package.json │ ├── rollup.config.mjs │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── webpack.config.cjs └── typed-dependency-tester │ ├── .eslintrc.js │ ├── MicrosoftTeams.min.js │ ├── README.md │ ├── package.json │ ├── tsconfig.json │ └── tsconfig.strictNullChecks.json ├── azure-pipelines.yml ├── beachball.config.js ├── change ├── @microsoft-teams-js-06b44e75-5613-4594-a024-dfb68a5d7a05.json ├── @microsoft-teams-js-2d20cf0b-3ef5-4d6a-a6b0-b3110792b83f.json ├── @microsoft-teams-js-3a3fbb36-90e2-4d00-bb1e-7633a46f3f8c.json ├── @microsoft-teams-js-96be3430-7bd4-4a40-8554-4be120a97747.json └── @microsoft-teams-js-ade231c1-27a2-4670-b268-bdfc0daa5b0a.json ├── enforceBeachball.js ├── jest.config.common.js ├── lerna.json ├── package.json ├── packages └── teams-js │ ├── .eslintrc.cjs │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── eslint-rules │ └── eslint-plugin-recommend-no-namespaces │ │ ├── index.js │ │ ├── package.json │ │ └── recommendNoNamespaces.js │ ├── jest-setup.cjs │ ├── jest.config.cjs │ ├── package.json │ ├── prepBetaRelease.cjs │ ├── rollup.config.mjs │ ├── scripts │ ├── add-npmrc-npmtoken.ps1 │ └── view-published-packages-urls.ps1 │ ├── src │ ├── artifactsForCDN │ │ └── validDomains.json │ ├── index.ts │ ├── internal │ │ ├── VideoFrameTypes.ts │ │ ├── appHelpers.ts │ │ ├── childCommunication.ts │ │ ├── communication.ts │ │ ├── communicationUtils.ts │ │ ├── constants.ts │ │ ├── deepLinkConstants.ts │ │ ├── deepLinkUtilities.ts │ │ ├── dialogHelpers.ts │ │ ├── emailAddressValidation.ts │ │ ├── globalVars.ts │ │ ├── handlers.ts │ │ ├── hostToAppTelemetry.ts │ │ ├── idValidation.ts │ │ ├── interfaces.ts │ │ ├── internalAPIs.ts │ │ ├── marketplaceUtils.ts │ │ ├── mediaUtil.ts │ │ ├── messageObjects.ts │ │ ├── nestedAppAuthUtils.ts │ │ ├── pagesHelpers.ts │ │ ├── profileUtil.ts │ │ ├── responseHandler.ts │ │ ├── telemetry.ts │ │ ├── typeCheckUtilities.ts │ │ ├── utils.ts │ │ ├── validOrigins.ts │ │ ├── videoEffectsUtils.ts │ │ ├── videoFrameTick.ts │ │ ├── videoPerformanceMonitor.ts │ │ ├── videoPerformanceStatistics.ts │ │ ├── visualMediaHelpers.ts │ │ └── webStorageHelpers.ts │ ├── private │ │ ├── appEntity.ts │ │ ├── constants.ts │ │ ├── conversations.ts │ │ ├── copilot │ │ │ ├── copilot.ts │ │ │ ├── customTelemetry.ts │ │ │ └── eligibility.ts │ │ ├── externalAppAuthentication.ts │ │ ├── externalAppAuthenticationForCEA.ts │ │ ├── externalAppCardActions.ts │ │ ├── externalAppCardActionsForCEA.ts │ │ ├── externalAppCardActionsForDA.ts │ │ ├── externalAppCommands.ts │ │ ├── externalAppErrorHandling.ts │ │ ├── files.ts │ │ ├── hostEntity │ │ │ ├── hostEntity.ts │ │ │ └── tab.ts │ │ ├── index.ts │ │ ├── interfaces.ts │ │ ├── logs.ts │ │ ├── meetingRoom.ts │ │ ├── messageChannels │ │ │ ├── dataLayer.ts │ │ │ ├── messageChannels.ts │ │ │ └── telemetry.ts │ │ ├── nestedAppAuth │ │ │ └── nestedAppAuthBridge.ts │ │ ├── notifications.ts │ │ ├── otherAppStateChange.ts │ │ ├── privateAPIs.ts │ │ ├── remoteCamera.ts │ │ ├── store.ts │ │ ├── teams │ │ │ ├── fullTrust │ │ │ │ ├── fullTrust.ts │ │ │ │ └── joinedTeams.ts │ │ │ └── teams.ts │ │ └── videoEffectsEx.ts │ └── public │ │ ├── adaptiveCards.ts │ │ ├── app │ │ ├── app.ts │ │ └── lifecycle.ts │ │ ├── appId.ts │ │ ├── appInitialization.ts │ │ ├── appInstallDialog.ts │ │ ├── appWindow.ts │ │ ├── authentication.ts │ │ ├── barCode.ts │ │ ├── calendar.ts │ │ ├── call.ts │ │ ├── chat.ts │ │ ├── clipboard.ts │ │ ├── constants.ts │ │ ├── dialog │ │ ├── adaptiveCard │ │ │ ├── adaptiveCard.ts │ │ │ └── bot.ts │ │ ├── dialog.ts │ │ ├── update.ts │ │ └── url │ │ │ ├── bot.ts │ │ │ ├── parentCommunication.ts │ │ │ └── url.ts │ │ ├── emailAddress.ts │ │ ├── featureFlags.ts │ │ ├── geoLocation │ │ ├── geoLocation.ts │ │ └── map.ts │ │ ├── index.ts │ │ ├── interfaces.ts │ │ ├── liveShareHost.ts │ │ ├── location.ts │ │ ├── mail │ │ ├── handoff.ts │ │ └── mail.ts │ │ ├── marketplace.ts │ │ ├── media.ts │ │ ├── meeting │ │ ├── appShareButton.ts │ │ └── meeting.ts │ │ ├── menus.ts │ │ ├── monetization.ts │ │ ├── navigation.ts │ │ ├── nestedAppAuth.ts │ │ ├── pages │ │ ├── appButton.ts │ │ ├── backStack.ts │ │ ├── config.ts │ │ ├── currentApp.ts │ │ ├── fullTrust.ts │ │ ├── pages.ts │ │ └── tabs.ts │ │ ├── people.ts │ │ ├── profile.ts │ │ ├── publicAPIs.ts │ │ ├── runtime.ts │ │ ├── search.ts │ │ ├── secondaryBrowser.ts │ │ ├── serializable.interface.ts │ │ ├── settings.ts │ │ ├── sharing │ │ ├── history.ts │ │ └── sharing.ts │ │ ├── stageView │ │ ├── self.ts │ │ └── stageView.ts │ │ ├── tasks.ts │ │ ├── teamsAPIs.ts │ │ ├── thirdPartyCloudStorage.ts │ │ ├── uuidObject.ts │ │ ├── validatedSafeString.ts │ │ ├── version.ts │ │ ├── videoEffects.ts │ │ ├── visualMedia │ │ ├── image.ts │ │ └── visualMedia.ts │ │ └── webStorage.ts │ ├── test │ ├── internal │ │ ├── childCommunication.spec.ts │ │ ├── communication.spec.ts │ │ ├── communicationUtils.spec.ts │ │ ├── deepLinkUtilities.spec.ts │ │ ├── emailAddressValidation.spec.ts │ │ ├── idValidation.spec.ts │ │ ├── internalAPI.spec.ts │ │ ├── marketplaceUtils.spec.ts │ │ ├── mediaUtil.spec.ts │ │ ├── profileUtil.spec.ts │ │ ├── responseHandler.spec.ts │ │ ├── utils.spec.ts │ │ ├── validOrigins.spec.ts │ │ ├── videoFrameTick.spec.ts │ │ ├── videoPerformanceMonitor.spec.ts │ │ └── videoPerformanceStatistics.spec.ts │ ├── private │ │ ├── appEntity.spec.ts │ │ ├── conversations.spec.ts │ │ ├── copilot.spec.ts │ │ ├── externalAppAuthentication.spec.ts │ │ ├── externalAppAuthenticationForCEA.spec.ts │ │ ├── externalAppCardActions.spec.ts │ │ ├── externalAppCardActionsForCEA.spec.ts │ │ ├── externalAppCardActionsForDA.spec.ts │ │ ├── externalAppCommands.spec.ts │ │ ├── files.spec.ts │ │ ├── hostEntity.spec.ts │ │ ├── logs.spec.ts │ │ ├── meetingRoom.spec.ts │ │ ├── messageChannels.spec.ts │ │ ├── nestedAppAuthBridge.spec.ts │ │ ├── notifications.spec.ts │ │ ├── otherAppStateChange.spec.ts │ │ ├── privateAPIs.spec.ts │ │ ├── remoteCamera.spec.ts │ │ ├── store.spec.ts │ │ ├── teams.spec.ts │ │ └── videoEffectsEx.spec.ts │ ├── promiseTester.ts │ ├── public │ │ ├── adaptiveCards.spec.ts │ │ ├── app.spec.ts │ │ ├── appId.spec.ts │ │ ├── appInitialization.spec.ts │ │ ├── appInstallDialog.spec.ts │ │ ├── appWindow.spec.ts │ │ ├── authentication.spec.ts │ │ ├── barCode.spec.ts │ │ ├── calendar.spec.ts │ │ ├── call.spec.ts │ │ ├── chat.spec.ts │ │ ├── clipboard.spec.ts │ │ ├── dialog.spec.ts │ │ ├── emailAddress.spec.ts │ │ ├── geoLocation.spec.ts │ │ ├── liveShareHost.spec.ts │ │ ├── location.spec.ts │ │ ├── mail.spec.ts │ │ ├── marketplace.spec.ts │ │ ├── media.spec.ts │ │ ├── meeting.spec.ts │ │ ├── menus.spec.ts │ │ ├── monetization.spec.ts │ │ ├── navigation.spec.ts │ │ ├── nestedAppAuth.spec.ts │ │ ├── pages.spec.ts │ │ ├── people.spec.ts │ │ ├── profile.spec.ts │ │ ├── publicAPIs.spec.ts │ │ ├── runtime.spec.ts │ │ ├── search.spec.ts │ │ ├── secondaryBrowser.spec.ts │ │ ├── serializable.spec.ts │ │ ├── settings.spec.ts │ │ ├── sharing.spec.ts │ │ ├── stageView.spec.ts │ │ ├── tasks.spec.ts │ │ ├── teamsAPIs.spec.ts │ │ ├── thirdPartyCloudStorage.spec.ts │ │ ├── version.spec.ts │ │ ├── videoEffects.spec.ts │ │ ├── visualMedia.spec.ts │ │ └── webStorage.spec.ts │ ├── resultValidation.ts │ ├── setupTest.ts │ └── utils.ts │ ├── tsconfig.eslint.json │ ├── tsconfig.json │ ├── typedoc.json │ └── webpack.config.cjs ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── skeleton-buffer ├── index.d.ts ├── index.js └── package.json ├── tools ├── bundle-size-tools │ ├── .eslintrc.js │ ├── README.md │ ├── bundle-analysis-app │ │ ├── .eslintrc.js │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── webpack.config.js │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── ADO │ │ │ ├── AdoArtifactFileProvider.ts │ │ │ ├── AdoSizeComparator.ts │ │ │ ├── Constants.ts │ │ │ ├── DefaultStatsProcessors.ts │ │ │ ├── FileSystemBundleFileProvider.ts │ │ │ ├── PrCommentsUtils.ts │ │ │ ├── getAzureDevopsApi.ts │ │ │ ├── getBuildTagForCommit.ts │ │ │ ├── getBundleBuddyConfigMap.ts │ │ │ ├── getBundleFilePathsFromFolder.ts │ │ │ ├── getBundleSummaries.ts │ │ │ ├── getCommentForBundleDiff.ts │ │ │ └── index.ts │ │ ├── BundleBuddyTypes.ts │ │ ├── compareBundles.ts │ │ ├── index.ts │ │ ├── statsProcessors │ │ │ ├── bundleBuddyConfigProcessor.ts │ │ │ ├── entryStatsProcessor.ts │ │ │ ├── index.ts │ │ │ └── totalSizeStatsProcessor.ts │ │ └── utilities │ │ │ ├── decompressStatsFile.ts │ │ │ ├── getAllFilesInDirectory.ts │ │ │ ├── getBuilds.ts │ │ │ ├── getChunkAndDependenciesSizes.ts │ │ │ ├── getChunkParsedSize.ts │ │ │ ├── getLastCommitHashFromPR.ts │ │ │ ├── gitCommands.ts │ │ │ ├── index.ts │ │ │ ├── runProcessorOnStatsFile.ts │ │ │ └── unzipStream.ts │ ├── test │ │ └── gitCommands.spec.ts │ ├── tsconfig.json │ └── tsconfig.strictNullChecks.json ├── cli │ ├── collectBundleAnalysis.js │ ├── compareBundleAnalysis.js │ ├── preRelease.js │ └── readChangelog.js ├── releases │ ├── build-release.yml │ ├── ddl-release.yml │ ├── prod-release.yml │ └── sdf-release.yml ├── validateTestSchema.ts └── yaml-templates │ ├── android-test.yml │ ├── build-app-host.yml │ ├── build-test-publish.yml │ ├── ios-test.yml │ ├── security.yml │ ├── web-e2e-tests-job.yml │ └── web-e2e-versions.yml ├── tsconfig.common.json └── tsconfig.strictNullChecks.json /.eslintignore: -------------------------------------------------------------------------------- 1 | .sdl 2 | .vscode 3 | dist 4 | node_modules -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@microsoft/sdl/recommended', 6 | 'plugin:@typescript-eslint/eslint-recommended', 7 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 8 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 9 | ], 10 | parserOptions: { 11 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 12 | sourceType: 'module', // Allows for the use of imports 13 | }, 14 | plugins: ['@typescript-eslint', 'only-error', 'simple-import-sort'], 15 | rules: { 16 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 17 | '@typescript-eslint/no-use-before-define': 'off', 18 | '@typescript-eslint/explicit-member-accessibility': 'warn', 19 | '@typescript-eslint/explicit-function-return-type': [ 20 | 'error', 21 | { 22 | allowExpressions: true, 23 | }, 24 | ], 25 | curly: 'error', 26 | 'simple-import-sort/imports': 'error', 27 | quotes: ['error', 'single', { avoidEscape: true }], 28 | }, 29 | env: { 30 | node: true, 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto eol=lf 5 | -------------------------------------------------------------------------------- /.github/workflows/postrelease.yml: -------------------------------------------------------------------------------- 1 | name: Post Release 2 | on: 3 | pull_request: 4 | branches: [release/*] 5 | types: [closed] 6 | 7 | jobs: 8 | PostMerge: 9 | # this job will only run if the PR has been merged 10 | if: github.event.pull_request.merged == true 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Setup Node 14 | uses: actions/setup-node@v4.0.2 15 | with: 16 | node-version: '18.x' 17 | 18 | - name: Checkout 19 | uses: actions/checkout@v4.1.1 20 | 21 | - name: Extract branch name 22 | id: extract_branch 23 | run: echo "::set-output name=branch::${GITHUB_REF#refs/heads/}" 24 | 25 | - name: Extract version 26 | id: extract_version 27 | run: | 28 | version=$(echo ${{ steps.extract_branch.outputs.branch }} | cut --complement -d "/" -f 1) 29 | echo "::set-output name=version::$version" 30 | 31 | - name: Extract changelog for version 32 | id: extract_changelog 33 | run: | 34 | changelog=$(node tools/cli/readChangelog.js ${{steps.extract_version.outputs.version}}) 35 | changelog="${changelog//'%'/'%25'}" 36 | changelog="${changelog//$'\n'/'%0A'}" 37 | changelog="${changelog//$'\r'/'%0D'}" 38 | echo -e "::set-output name=changelog::$changelog" 39 | 40 | - name: Notify TeamsFx Repo 41 | uses: peter-evans/repository-dispatch@v2 42 | with: 43 | token: ${{ secrets.TEAMSFX_REPO_ACCESS_TOKEN }} 44 | repository: OfficeDev/TeamsFx 45 | event-type: teamsjs-released 46 | client-payload: '{"version": "${{ steps.extract_version.outputs.version }}"}' 47 | -------------------------------------------------------------------------------- /.github/workflows/runtimeCheck.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Check for runtime version changes 3 | 4 | on: 5 | pull_request: 6 | branches: [main] 7 | paths: [packages/teams-js/src/public/runtime.ts] 8 | 9 | permissions: 10 | contents: read 11 | pull-requests: write 12 | 13 | jobs: 14 | Check-For-Changes-To-Runtime-File: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: 'Comment on PR' 18 | if: github.event.pull_request.head.repo.fork != true 19 | uses: actions/github-script@v6 20 | with: 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | script: | 23 | commentBody = 'This pull request contains changes to the runtime.ts file. If you, as the author of this PR, have made changes to the Runtime interface please review RUNTIME.md to determine if a new runtime version is required. Please reply to this comment stating what changes, if any, were made to the runtime object and whether a new runtime version was required.'; 24 | comments = await github.rest.issues.listComments({ 25 | owner: context.repo.owner, 26 | repo: context.repo.repo, 27 | issue_number: context.issue.number 28 | }); 29 | if (comments.data.some(comment => comment.body.includes(commentBody))) { 30 | return; 31 | } 32 | await github.rest.issues.createComment({ 33 | owner: context.repo.owner, 34 | repo: context.repo.repo, 35 | issue_number: context.issue.number, 36 | body: commentBody 37 | }); -------------------------------------------------------------------------------- /.github/workflows/size-limit.yml: -------------------------------------------------------------------------------- 1 | name: 'Size Change' 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | permissions: 7 | pull-requests: write 8 | jobs: 9 | size: 10 | runs-on: ubuntu-latest 11 | env: 12 | CI_JOB_NUMBER: 1 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Setup pnpm 16 | uses: pnpm/action-setup@v4 17 | with: 18 | version: 10 19 | - uses: andresz1/size-limit-action@v1 20 | with: 21 | github_token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 3 | node_modules/ 4 | npm-debug 5 | npm-error 6 | 7 | # build output 8 | build/ 9 | coverage/ 10 | dist/ 11 | dts/ 12 | 13 | # Visual Studio cache/options directory 14 | .DS_Store 15 | .vs/ 16 | 17 | # Test App gitignore areas 18 | yarn-error.log 19 | *.log 20 | 21 | # bundle analysis folders 22 | /common/* 23 | /tools/bundle-size-tools/bundle-analysis-app/bundleAnalysis 24 | 25 | # UT results 26 | /packages/*/test-results 27 | /test-results 28 | /tools/*/test-results 29 | 30 | # locally generated docs 31 | /packages/teams-js/docs 32 | 33 | #lerna logs 34 | lerna-debug.log 35 | 36 | # Blazor Test App 37 | microsoft-teams-library-js.sln 38 | *.binlog 39 | *.user 40 | .dotnet/ 41 | .vs/ 42 | .vscode/ 43 | artifacts/ 44 | bin/ 45 | obj/ 46 | 47 | #VS Code Local Files 48 | *.sln 49 | 50 | # TypeScript output 51 | out 52 | 53 | # Dependency directories 54 | node_modules 55 | 56 | # Azure Functions artifacts 57 | bin 58 | obj 59 | appsettings.json 60 | 61 | 62 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 63 | # TeamsFx files 64 | env/.env.*.user 65 | .env.local 66 | build 67 | appPackage/build 68 | .deployment 69 | .localConfigs 70 | 71 | # dependencies 72 | /node_modules 73 | 74 | # testing 75 | /coverage 76 | 77 | # Dev tool directories 78 | devTools/ 79 | 80 | # Ignore Jest JUnit test results 81 | apps/teamsjs-diagnostic-app/test-results/unit/unit-tests-report.xml 82 | 83 | #Ignore package locks 84 | apps/teamsjs-diagnostic-app/package-lock.json 85 | apps/teamsjs-diagnostic-app/api/package-lock.json -------------------------------------------------------------------------------- /.hintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "development" 4 | ], 5 | "browserslist": [ 6 | "defaults", 7 | "not ie 11", 8 | "not and_ff <= 126", 9 | "not firefox <= 126" 10 | ] 11 | } -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers=true 2 | # Failing on missing/conflicting peer dependencies is a good thing, don't turn this off even though pnpm suggests it as an option whenever displaying a peer dependency error 3 | strict-peer-dependencies=true 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | pnpm-lock.yaml -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | endOfLine: 'lf', 3 | printWidth: 120, 4 | semi: true, 5 | singleQuote: true, 6 | tabWidth: 2, 7 | trailingComma: 'all', 8 | useTabs: false, 9 | }; 10 | -------------------------------------------------------------------------------- /.sdl/.gdnbaselines: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "baselines": { 4 | "Baselines": { 5 | "name": "Baselines", 6 | "createdDate": "", 7 | "lastUpdatedDate": "" 8 | } 9 | }, 10 | "results": { 11 | } 12 | } -------------------------------------------------------------------------------- /.sdl/.gdnsuppress: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "suppressionSets": { 4 | "Suppressions": { 5 | "name": "Suppressions", 6 | "createdDate": "", 7 | "lastUpdatedDate": "" 8 | } 9 | }, 10 | "results": { 11 | } 12 | } -------------------------------------------------------------------------------- /.sdl/CredScanSuppressions.json: -------------------------------------------------------------------------------- 1 | { 2 | "tool": "Credential Scanner", 3 | "suppressions": [ 4 | { 5 | "file": ["\\test\\ssl-cert-snakeoil.key", "\\test\\fixtures\\server.key"], 6 | "_justification": "Self-signed certificates used by dependent packages for testing" 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": [ 6 | "dbaeumer.vscode-eslint", 7 | "esbenp.prettier-vscode", 8 | "orta.vscode-jest", 9 | "streetsidesoftware.code-spell-checker" 10 | ], 11 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 12 | "unwantedRecommendations": [] 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "pwa-node", 6 | "request": "launch", 7 | "name": "Debug compare bundle analysis", 8 | "skipFiles": ["/**"], 9 | "program": "${workspaceFolder}\\tools\\cli\\compareBundleAnalysis.js", 10 | "outFiles": ["${workspaceFolder}/tools/**/*.js"] 11 | }, 12 | { 13 | "type": "node", 14 | "request": "launch", 15 | "name": "Run current file", 16 | "program": "${file}", 17 | "cwd": "${fileDirname}" 18 | }, 19 | { 20 | "type": "node", 21 | "request": "launch", 22 | "name": "Launch pnpm command", 23 | "runtimeExecutable": "pnpm", 24 | "cwd": "${workspaceFolder}", 25 | "runtimeArgs": ["build-sdk"] 26 | }, 27 | { 28 | "type": "node", 29 | "name": "vscode-jest-tests", 30 | "request": "launch", 31 | "console": "integratedTerminal", 32 | "internalConsoleOptions": "neverOpen", 33 | "disableOptimisticBPs": true, 34 | "cwd": "${workspaceFolder}", 35 | "runtimeExecutable": "pnpm", 36 | "args": ["test", "--", "--", "--runInBand", "--watchAll=false"] 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "adal", 4 | "frameless", 5 | "ipados", 6 | "parentless", 7 | "Subcapability", 8 | "teamsjs", 9 | "teamspace", 10 | "uninitialize", 11 | "xvfb" 12 | ], 13 | "editor.defaultFormatter": "esbenp.prettier-vscode", 14 | "eslint.workingDirectories": [ 15 | "./apps/ssr-test-app/", 16 | "./apps/teams-perf-test-app", 17 | "./apps/teams-test-app/", 18 | "./apps/typed-dependency-tester/", 19 | "./packages/teams-js/" 20 | ], 21 | "editor.formatOnSave": true, 22 | "editor.insertSpaces": true, 23 | "editor.tabSize": 2, 24 | "javascript.preferences.quoteStyle": "single", 25 | "jest.jestCommandLine": "pnpm test -- --", 26 | "jest.runMode": { "runAllTestsOnStartup": true, "type": "watch" }, 27 | "search.exclude": { 28 | "**/dist": true 29 | }, 30 | "typescript.preferences.quoteStyle": "single", 31 | "typescript.tsdk": "./node_modules/typescript/lib" 32 | } 33 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @OfficeDev/teams-client-sdk-approvers 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | - Employees can reach out at [aka.ms/opensource/moderation-support](https://aka.ms/opensource/moderation-support) 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Microsoft Teams JS Library 2 | 3 | Copyright (c) Microsoft Corporation 4 | All rights reserved. 5 | 6 | MIT License 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /apps/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['plugin:react/recommended'], 3 | plugins: ['react-hooks'], 4 | rules: { 5 | 'react-hooks/rules-of-hooks': 'error', 6 | 'react-hooks/exhaustive-deps': 'warn', 7 | 'react/prop-types': 'off', 8 | }, 9 | settings: { 10 | react: { 11 | pragma: 'React', 12 | version: 'detect', 13 | }, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /apps/blazor-test-app/.gitignore: -------------------------------------------------------------------------------- 1 | # TeamsFx files 2 | node_modules 3 | .DS_Store 4 | .env.teamsfx.local 5 | .fx/configs/config.local.json 6 | .fx/configs/localSettings.json 7 | .fx/states/*.userdata 8 | .fx/states/state.local.json 9 | appsettings.Development.json 10 | build 11 | FakesAssemblies/Microsoft.AspNetCore.Components.Server.6.0.0.0.Fakes.messages 12 | FakesAssemblies/Microsoft.AspNetCore.Components.Server.6.0.0.0.Fakes.xml 13 | subscriptionInfo.json 14 | wwwroot/js/MicrosoftTeams.min.js 15 | 16 | env/.env.*.user 17 | env/.env.local 18 | .backup/* 19 | /devTools/ 20 | -------------------------------------------------------------------------------- /apps/blazor-test-app/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Sorry, there's nothing at this address.

9 |
10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Blazor-Test-App.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Components/Loading.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.Extensions.Configuration 2 | @using System.IO 3 | @inject NavigationManager MyNavigationManager 4 | @inject IConfiguration Configuration 5 | @inject IJSRuntime jsRuntime 6 | @inject MicrosoftTeams MicrosoftTeams 7 | 8 | @if (isLoaded) 9 | { 10 | @ChildContent 11 | } 12 | else 13 | { 14 |
15 | 16 |
17 | } 18 | 19 | @code { 20 | bool isLoaded; 21 | 22 | [Parameter] 23 | public RenderFragment ChildContent { get; set; } 24 | 25 | protected override async Task OnAfterRenderAsync(bool firstRender) 26 | { 27 | await base.OnAfterRenderAsync(firstRender); 28 | 29 | try 30 | { 31 | if (firstRender) 32 | { 33 | isLoaded = true; 34 | StateHasChanged(); 35 | } 36 | } 37 | catch (Exception) { } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Components/Welcome.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.Extensions.Configuration 2 | @using Microsoft.AspNetCore.Hosting 3 | @inject MicrosoftTeams MicrosoftTeams 4 | @inject IWebHostEnvironment HostEnvironment 5 | @inject IConfiguration Configuration 6 | @inject NavigationManager MyNavigationManager 7 | 8 | @if(isLoading) 9 | { 10 |
11 | 12 |
13 | } 14 | else 15 | { 16 |
17 |
18 | 19 |

Congratulations!

20 |

If you can read this, it means the SDK is not breaking Blazor app support capabilities!

21 |
22 |
23 | } 24 | 25 | @code { 26 | bool isLoading = true; 27 | 28 | bool IsHostedInM365 = false; 29 | 30 | protected override async Task OnAfterRenderAsync(bool firstRender) 31 | { 32 | await base.OnAfterRenderAsync(firstRender); 33 | 34 | if(firstRender) 35 | { 36 | isLoading = false; 37 | StateHasChanged(); 38 | 39 | IsHostedInM365 = await MicrosoftTeams.IsHostedInM365(); 40 | 41 | if (IsHostedInM365) { 42 | await MicrosoftTeams.InitializeAsync(); 43 | await MicrosoftTeams.notifySuccess(); 44 | } 45 | 46 | } 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Components/Welcome.razor.css: -------------------------------------------------------------------------------- 1 | .narrow { 2 | max-width: 900px; 3 | margin: 0 auto; 4 | } 5 | 6 | .page-padding { 7 | padding: 4rem; 8 | } 9 | 10 | .welcome.page > .narrow > img { 11 | margin: 0 auto; 12 | display: block; 13 | width: 200px; 14 | } 15 | 16 | .welcome.page > .narrow > .menu { 17 | width: 80%; 18 | justify-content: space-between; 19 | margin: 4rem auto; 20 | border-bottom: none; 21 | } 22 | 23 | .welcome.page > .narrow > .menu > .menu-item { 24 | background-color: inherit; 25 | margin: auto; 26 | font-size: 14px; 27 | min-height: 32px; 28 | border-bottom-color: rgb(98, 100, 167); 29 | } 30 | 31 | .center { 32 | text-align: center; 33 | } 34 | 35 | .sections > * { 36 | margin: 4rem auto; 37 | } 38 | 39 | .error { 40 | color: red; 41 | } 42 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Config.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.TeamsFx.Configuration; 2 | 3 | namespace Blazor_Test_App 4 | { 5 | public class ConfigOptions 6 | { 7 | public TeamsFxOptions TeamsFx { get; set; } 8 | } 9 | public class TeamsFxOptions 10 | { 11 | public AuthenticationOptions Authentication { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/blazor-test-app/FakesAssemblies/Microsoft.AspNetCore.Components.Server.6.0.0.0.Fakes.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-library-js/b41cf307375e5469df05da3db6cb8b9d4b70c824/apps/blazor-test-app/FakesAssemblies/Microsoft.AspNetCore.Components.Server.6.0.0.0.Fakes.dll -------------------------------------------------------------------------------- /apps/blazor-test-app/FakesAssemblies/Microsoft.AspNetCore.Components.Server.6.0.0.0.Fakes.fakesconfig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-library-js/b41cf307375e5469df05da3db6cb8b9d4b70c824/apps/blazor-test-app/FakesAssemblies/Microsoft.AspNetCore.Components.Server.6.0.0.0.Fakes.fakesconfig -------------------------------------------------------------------------------- /apps/blazor-test-app/Interop/InteropModuleBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | 3 | namespace Blazor_Test_App.Interop; 4 | 5 | public abstract class InteropModuleBase 6 | { 7 | private readonly IJSRuntime _jsRuntime; 8 | private IJSObjectReference _module; 9 | 10 | protected IJSObjectReference Interop => _module; 11 | protected virtual string ModulePath { get; set; } 12 | 13 | public InteropModuleBase(IJSRuntime jsRuntime) 14 | { 15 | _jsRuntime = jsRuntime; 16 | } 17 | 18 | protected async Task InvokeVoidAsync(string functionName, params object[] args) 19 | { 20 | await ImportModuleAsync(); 21 | 22 | await Interop.InvokeVoidAsync(functionName, args).AsTask(); 23 | } 24 | 25 | protected async Task InvokeAsync(string functionName, params object[] args) 26 | { 27 | await ImportModuleAsync(); 28 | 29 | return await Interop.InvokeAsync(functionName, args).AsTask(); 30 | } 31 | 32 | protected virtual async Task ImportModuleAsync() 33 | { 34 | if (_module == null) 35 | { 36 | _ = await ImportPrerequisiteModuleAsync("./js/MicrosoftTeams.min.js"); 37 | _module = await _jsRuntime.InvokeAsync("import", ModulePath).AsTask(); 38 | } 39 | 40 | return _module; 41 | } 42 | 43 | private Task ImportPrerequisiteModuleAsync(string url) 44 | { 45 | return _jsRuntime.InvokeAsync("import", url).AsTask(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Interop/TeamsSDK/EnumDescriptionConverter.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Reflection; 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace Blazor_Test_App.Interop.TeamsSDK; 7 | 8 | internal class EnumDescriptionConverter : JsonConverter where T : struct, Enum 9 | { 10 | public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 11 | { 12 | string value = reader.GetString(); 13 | 14 | if (string.IsNullOrEmpty(value)) return default; 15 | 16 | var type = typeof(T); 17 | 18 | if (!Enum.TryParse(type, value, ignoreCase: false, out object result) && 19 | !Enum.TryParse(type, value, ignoreCase: true, out result)) 20 | { 21 | throw new JsonException( 22 | $"Unable to convert \"{value}\" to Enum \"{type}\"."); 23 | } 24 | 25 | return (T)result; 26 | } 27 | 28 | public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) 29 | { 30 | FieldInfo fi = value.GetType().GetField(value.ToString()); 31 | 32 | var description = (DescriptionAttribute)fi.GetCustomAttribute(typeof(DescriptionAttribute), false); 33 | 34 | writer.WriteStringValue(description.Description); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Interop/TeamsSDK/MicrosoftTeams.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | 3 | namespace Blazor_Test_App.Interop.TeamsSDK; 4 | 5 | public class MicrosoftTeams : InteropModuleBase 6 | { 7 | protected override string ModulePath => "./js/TeamsJsBlazorInterop.js"; 8 | 9 | public MicrosoftTeams(IJSRuntime jsRuntime) : base(jsRuntime) { } 10 | 11 | public Task InitializeAsync() 12 | { 13 | return InvokeVoidAsync("initializeAsync"); 14 | } 15 | 16 | public Task GetTeamsContextAsync() 17 | { 18 | return InvokeAsync("getContextAsync"); 19 | } 20 | 21 | public Task RegisterOnSaveHandlerAsync(TeamsInstanceSettings settings) 22 | { 23 | return InvokeVoidAsync("registerOnSaveHandler", settings); 24 | } 25 | 26 | public Task IsHostedInM365() 27 | { 28 | try 29 | { 30 | return InvokeAsync("isHostedInM365"); 31 | } 32 | catch (JSException) 33 | { 34 | return Task.FromResult(false); 35 | } 36 | } 37 | 38 | public Task notifySuccess() 39 | { 40 | return InvokeVoidAsync("notifySuccess"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Interop/TeamsSDK/TeamsInstanceSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor_Test_App.Interop.TeamsSDK; 2 | 3 | public class TeamsInstanceSettings 4 | { 5 | public string ContentUrl { get; set; } 6 | public string EntityId { get; set; } 7 | public string RemoveUrl { get; set; } 8 | public string SuggestedDisplayName { get; set; } 9 | public string WebsiteUrl { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model Blazor_Test_App.Pages.ErrorModel 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Error 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Error.

19 |

An error occurred while processing your request.

20 | 21 | @if (Model.ShowRequestId) 22 | { 23 |

24 | Request ID: @Model.RequestId 25 |

26 | } 27 | 28 |

Development Mode

29 |

30 | Swapping to the Development environment displays detailed information about the error that occurred. 31 |

32 |

33 | The Development environment shouldn't be enabled for deployed applications. 34 | It can result in displaying sensitive information from exceptions to end users. 35 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 36 | and restarting the app. 37 |

38 |
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.AspNetCore.Mvc.RazorPages; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Blazor_Test_App.Pages; 8 | 9 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 10 | [IgnoreAntiforgeryToken] 11 | public class ErrorModel : PageModel 12 | { 13 | public string RequestId { get; set; } 14 | 15 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 16 | 17 | private readonly ILogger _logger; 18 | 19 | public ErrorModel(ILogger logger) 20 | { 21 | _logger = logger; 22 | } 23 | 24 | public void OnGet() 25 | { 26 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Pages/Tab.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @page "/tab" 3 | @using Blazor_Test_App.Components; 4 | 5 |
6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Pages/Tab.razor.css: -------------------------------------------------------------------------------- 1 | .narrow { 2 | max-width: 900px; 3 | margin: 0 auto; 4 | } 5 | 6 | .page-padding { 7 | padding: 4rem; 8 | } 9 | 10 | .welcome.page > .narrow > img { 11 | margin: 0 auto; 12 | display: block; 13 | width: 200px; 14 | } 15 | 16 | .welcome.page > .narrow > ul { 17 | width: 75%; 18 | justify-content: space-between; 19 | margin: 4rem auto; 20 | } 21 | 22 | .welcome.page > .narrow > ul > li { 23 | background-color: inherit; 24 | margin: auto; 25 | } 26 | 27 | .welcome.page > .narrow > ul > li > a { 28 | font-size: 16px; 29 | height: 32px; 30 | border-bottom-color: rgb(98, 100, 167); 31 | } 32 | 33 | .center { 34 | text-align: center; 35 | } 36 | 37 | pre { 38 | overflow-x: scroll; 39 | } 40 | 41 | pre, 42 | div.error { 43 | background-color: #e5e5e5; 44 | padding: 1rem; 45 | box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2); 46 | border-radius: 3px; 47 | margin: 1rem 0; 48 | } 49 | 50 | code { 51 | background-color: #e5e5e5; 52 | display: inline-block; 53 | padding: 0px 6px; 54 | border-radius: 3px; 55 | box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2); 56 | } 57 | 58 | .error { 59 | color: red; 60 | } 61 | 62 | .profile { 63 | display: flex; 64 | margin: 1em 0; 65 | background-color: white; 66 | width: fit-content; 67 | box-shadow: 0px 8px 10px rgba(0, 0, 0, 0.1); 68 | border-radius: 3px; 69 | } 70 | 71 | .profile > .avatar { 72 | margin: 2em 1em; 73 | height: 72px; 74 | width: 72px; 75 | } 76 | 77 | .profile > .info { 78 | margin: 2em 2em 0 0; 79 | } 80 | 81 | .profile > .info > h3 { 82 | margin: 0; 83 | } 84 | 85 | .profile > .info > p { 86 | margin: 0; 87 | } 88 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Pages/TabConfig.razor: -------------------------------------------------------------------------------- 1 | @page "/config" 2 | @inject MicrosoftTeams MicrosoftTeams; 3 | @inject NavigationManager NavigationManager; 4 | 5 |
6 |

Tab Configuration

7 |

8 | This is where you will add your tab configuration options the user 9 | can choose when the tab is added to your team/group chat. 10 |

11 |
12 | 13 | @code { 14 | 15 | private Guid _entityId = Guid.NewGuid(); 16 | 17 | protected override async Task OnAfterRenderAsync(bool firstRender) 18 | { 19 | if(firstRender) 20 | { 21 | var settings = new TeamsInstanceSettings 22 | { 23 | SuggestedDisplayName = "My Tab", 24 | EntityId = _entityId.ToString(), 25 | ContentUrl = $"{NavigationManager.BaseUri}/tab", 26 | WebsiteUrl = $"{NavigationManager.BaseUri}/tab" 27 | }; 28 | 29 | await MicrosoftTeams.InitializeAsync(); 30 | await MicrosoftTeams.RegisterOnSaveHandlerAsync(settings); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Pages/_Host.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @namespace Blazor_Test_App.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | @{ 5 | Layout = null; 6 | } 7 | 8 | 9 | 10 | 11 | 12 | 13 | Blazor-Test-App 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | An error has occurred. This application may no longer respond until reloaded. 25 | 26 | 27 | An unhandled exception has occurred. See browser dev tools for details. 28 | 29 | Reload 30 | 🗙 31 |
32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Program.cs: -------------------------------------------------------------------------------- 1 | using Blazor_Test_App; 2 | using Blazor_Test_App.Interop.TeamsSDK; 3 | using Microsoft.IdentityModel.Logging; 4 | 5 | var builder = WebApplication.CreateBuilder(args); 6 | 7 | builder.Services.AddRazorPages(); 8 | builder.Services.AddServerSideBlazor(); 9 | 10 | var config = builder.Configuration.Get(); 11 | builder.Services.AddTeamsFx(config.TeamsFx.Authentication); 12 | builder.Services.AddScoped(); 13 | 14 | builder.Services.AddControllers(); 15 | builder.Services.AddHttpClient("WebClient", client => client.Timeout = TimeSpan.FromSeconds(600)); 16 | builder.Services.AddHttpContextAccessor(); 17 | 18 | var app = builder.Build(); 19 | 20 | if (app.Environment.IsDevelopment()) 21 | { 22 | app.UseDeveloperExceptionPage(); 23 | } 24 | else 25 | { 26 | app.UseExceptionHandler("/Error"); 27 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 28 | app.UseHsts(); 29 | } 30 | 31 | app.UseStaticFiles(); 32 | 33 | app.UseRouting(); 34 | 35 | app.UseEndpoints(endpoints => 36 | { 37 | endpoints.MapBlazorHub(); 38 | endpoints.MapFallbackToPage("/_Host"); 39 | endpoints.MapControllers(); 40 | }); 41 | 42 | app.Run(); 43 | 44 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Microsoft Teams (browser)": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "dotnetRunMessages": "true", 10 | "applicationUrl": "https://localhost:44302", 11 | "hotReloadProfile": "aspnetcore" 12 | }, 13 | "WSL": { 14 | "commandName": "WSL2", 15 | "launchBrowser": true, 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development", 18 | "ASPNETCORE_URLS": "https://localhost:44302" 19 | }, 20 | "distributionName": "" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/blazor-test-app/README.md: -------------------------------------------------------------------------------- 1 | # Blazor Test App 2 | 3 | The Blazor Test App is a test app written in C#/Blazor used to ensure any changes made to the teams-js package do not break any functionality for C# teams apps. 4 | 5 | ## Getting Started 6 | 7 | ### Running the Test App on its own 8 | 9 | If you would like to run this app on its own locally, please follow the steps below. 10 | 11 | ``` 12 | cd {monorepo root} 13 | 14 | // Ensuring you have installed and built the Teams JavaScript client SDK 15 | pnpm install 16 | pnpm build 17 | 18 | pnpm start-blazor-app 19 | ``` 20 | 21 | or if you have already built the Teams JavaScript client SDK and would like to build and run directly from the project directory blazor-test-app, simply `pnpm build` and `pnpm start` there. 22 | 23 | Once starting the app, it will run on https://localhost:44302. Upon visiting the page, the text `Congratulations` on the page indicates the c# app is working properly, otherwise an error will be thrown in the console and the webpage will not render. 24 | 25 | _NOTE: The Blazor Test App Needs to have the latest compiled version of MicrosoftTeams.min.js located inside the blazor-test-app/wwwroot/js directory. This can be copied over manually, however this will be done automatically when building the entire teams-js package from the monorepo root, so that course of action is recommended_ 26 | -------------------------------------------------------------------------------- /apps/blazor-test-app/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | @Body 3 | -------------------------------------------------------------------------------- /apps/blazor-test-app/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Authorization 3 | @using Microsoft.AspNetCore.Components.Authorization 4 | @using Microsoft.AspNetCore.Components.Forms 5 | @using Microsoft.AspNetCore.Components.Routing 6 | @using Microsoft.AspNetCore.Components.Web 7 | @using Microsoft.AspNetCore.Components.Web.Virtualization 8 | @using Microsoft.JSInterop 9 | @using Blazor_Test_App 10 | @using Blazor_Test_App.Shared 11 | @using Blazor_Test_App.Interop.TeamsSDK; 12 | @using Blazor_Test_App.Components 13 | @using Microsoft.Fast.Components.FluentUI; 14 | @using Microsoft.TeamsFx 15 | -------------------------------------------------------------------------------- /apps/blazor-test-app/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "TeamsFx": { 11 | "Authentication": { 12 | "ClientId": "$clientId$", 13 | "ClientSecret": "$client-secret$", 14 | "OAuthAuthority": "$oauthAuthority$" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/blazor-test-app/env/.env.dev: -------------------------------------------------------------------------------- 1 | # This file includes environment variables that will be committed to git by default. 2 | TEAMSFX_ENV=dev 3 | CONFIG__MANIFEST__APPNAME__SHORT=Blazor-Test-App 4 | CONFIG__MANIFEST__APPNAME__FULL=Blazor-Test-App 5 | CONFIG__MANIFEST__DESCRIPTION__SHORT=Test app to ensure proper Blazor app functionalities 6 | CONFIG__MANIFEST__DESCRIPTION__FULL=This is a test app written in Blazor. It is used to ensure that any changes made to the teams-js package do not inadvertently break support for any Blazor app that consumes the @microsoft/teams-js package. 7 | CONFIG__MANIFEST__ICONS__COLOR=resources/color.png 8 | CONFIG__MANIFEST__ICONS__OUTLINE=resources/outline.png 9 | -------------------------------------------------------------------------------- /apps/blazor-test-app/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": ">=8.0.4" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /apps/blazor-test-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blazor-test-app", 3 | "private": true, 4 | "author": "Microsoft Teams", 5 | "description": "Blazor test app to ensure teams-js changes don't break Blazor app functionality", 6 | "version": "1.0.0", 7 | "scripts": { 8 | "blazor-build": "dotnet build", 9 | "start": "dotnet run" 10 | }, 11 | "devDependencies": { 12 | "@microsoft/teams-js": "workspace:*" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/blazor-test-app/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /apps/blazor-test-app/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-library-js/b41cf307375e5469df05da3db6cb8b9d4b70c824/apps/blazor-test-app/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /apps/blazor-test-app/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-library-js/b41cf307375e5469df05da3db6cb8b9d4b70c824/apps/blazor-test-app/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /apps/blazor-test-app/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-library-js/b41cf307375e5469df05da3db6cb8b9d4b70c824/apps/blazor-test-app/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /apps/blazor-test-app/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-library-js/b41cf307375e5469df05da3db6cb8b9d4b70c824/apps/blazor-test-app/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /apps/blazor-test-app/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-library-js/b41cf307375e5469df05da3db6cb8b9d4b70c824/apps/blazor-test-app/wwwroot/favicon.ico -------------------------------------------------------------------------------- /apps/blazor-test-app/wwwroot/hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-library-js/b41cf307375e5469df05da3db6cb8b9d4b70c824/apps/blazor-test-app/wwwroot/hello.png -------------------------------------------------------------------------------- /apps/ssr-test-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['plugin:react/recommended', 'plugin:@next/next/recommended'], 3 | parserOptions: { 4 | project: './tsconfig.json', 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/ssr-test-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /apps/ssr-test-app/README.md: -------------------------------------------------------------------------------- 1 | # SSR Test App 2 | 3 | The SSR Test App is a React and NextJS app that serves to ensure any future changes to teams-js do not break server-side rendering capabilities. As it is included in the apps workspace, it will also be built when building teams-js. 4 | If there are any changes made to teams-js that should break server-side rendering capabilities, the build should fail when it attempts to build the SSR Test App. 5 | 6 | # Running the Test App on its own 7 | 8 | In order to run the SSR Test App on its own, please follow the following steps 9 | 10 | ``` 11 | cd {monorepo root} 12 | 13 | // Ensuring you have installed and built the Teams JavaScript client SDK 14 | pnpm install 15 | pnpm build 16 | 17 | pnpm start-ssr-app 18 | ``` 19 | 20 | or if you have already built the Teams JavaScript client SDK and would like to build and run directly from the project directory ssr-test-app, simply `pnpm build` and `pnpm start` there. 21 | 22 | ### Note 23 | 24 | Running the SSR Test App locally defaults to using an unsecure http connection. In order to run the SSR test app in the Orange app, a secure https connection is required. This can be achieved by generating an SSL certificate. Alternatively, ngrok can be used to generate a secure https connection without the need to generate an SSL certificate. 25 | 26 | # Troubleshooting 27 | 28 | If your build is succeeding locally, however is failing in the PR, it is possible your local version is building the SSR Test App with a cached version of teams-js without the breaking changes. If this is the case, 29 | simply delete your node_modules folder in the ssr-test-app directory, then redo the pnpm commmands above. 30 | -------------------------------------------------------------------------------- /apps/ssr-test-app/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "SSR Test App", 3 | "name": "SSR Test App", 4 | "icons": [], 5 | "start_url": "/", 6 | "display": "standalone", 7 | "theme_color": "#000000", 8 | "background_color": "#ffffff" 9 | } 10 | -------------------------------------------------------------------------------- /apps/ssr-test-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ssr-test-app", 3 | "private": true, 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "next", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "next": "^15.2.3", 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0" 14 | }, 15 | "devDependencies": { 16 | "@microsoft/teams-js": "workspace:*", 17 | "@types/node": "^17.0.43", 18 | "@types/react": "^18.0.12", 19 | "@types/react-dom": "^18.0.5", 20 | "typescript": "^4.7.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/ssr-test-app/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import * as microsoftTeams from '@microsoft/teams-js'; 2 | import { GetServerSideProps } from 'next'; 3 | import Head from 'next/head'; 4 | import React, { ReactElement, useEffect, useState } from 'react'; 5 | 6 | export interface SSRProps { 7 | renderString: string; 8 | time: string; 9 | } 10 | 11 | export default function IndexPage(props: SSRProps): ReactElement { 12 | const [teamsContext, setTeamsContext] = useState({}); 13 | const [clientTime, setClientTime] = useState(''); 14 | 15 | useEffect(() => { 16 | microsoftTeams.app.initialize().then(() => { 17 | microsoftTeams.app.getContext().then((ctx) => { 18 | setTeamsContext(ctx); 19 | }); 20 | microsoftTeams.app.notifySuccess(); 21 | setClientTime(JSON.stringify(new Date())); 22 | }); 23 | }, []); 24 | 25 | return ( 26 |
27 | 28 | SSR Test App 29 | 30 |
31 |

{props.renderString}

32 |

The server render time is {props.time.substring(12, 24)}

33 |

The client render time is {clientTime.substring(12, 24)}

34 |
35 |           Context: {JSON.stringify(teamsContext, null, 2)}
36 |         
37 |
38 |
39 | ); 40 | } 41 | 42 | /** 43 | * @returns prop data 44 | */ 45 | export const getServerSideProps: GetServerSideProps = async () => { 46 | const time = JSON.stringify(new Date()); 47 | return { 48 | props: { 49 | renderString: 'This string brought to you by the server', 50 | time, 51 | }, 52 | }; 53 | }; 54 | -------------------------------------------------------------------------------- /apps/ssr-test-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "esModuleInterop": true, 5 | "forceConsistentCasingInFileNames": true, 6 | "incremental": true, 7 | "isolatedModules": true, 8 | "jsx": "preserve", 9 | "lib": ["dom", "dom.iterable", "esnext"], 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "noEmit": true, 13 | "resolveJsonModule": true, 14 | "skipLibCheck": true, 15 | "strict": true, 16 | "target": "ESNext" 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /apps/teams-perf-test-app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react", 5 | "@babel/preset-typescript" 6 | ], 7 | "plugins": [ 8 | [ 9 | "@babel/plugin-transform-runtime", 10 | { 11 | "regenerator": true 12 | } 13 | ] 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /apps/teams-perf-test-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ignorePatterns: ['.eslintrc.js'], 3 | parserOptions: { 4 | project: './tsconfig.json', 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/teams-perf-test-app/README.md: -------------------------------------------------------------------------------- 1 | # Teams Perf Test App 2 | 3 | The Teams Perf Test App is a React app used to measure Teams App perf running with local host SDK(Orange). This will be used to measure app loading time. 4 | 5 | ## Getting Started 6 | 7 | ### Running the Perf Test App 8 | 9 | If you would like to run this app on its own locally, please follow the steps below. Please note many of the functions in the test app will only work as intended while being run in a Teams host as they communicate with the host to be carried out. 10 | 11 | ``` 12 | cd {monorepo root} 13 | 14 | // Ensuring you have installed and built the Teams JavaScript client SDK 15 | pnpm install 16 | pnpm build-sdk 17 | 18 | pnpm build-perf-app 19 | pnpm start-perf-app 20 | ``` 21 | 22 | or if you have already built the Teams JavaScript client SDK and would like to build and run directly from the project directory teams-perf-app, simply `pnpm build` and `pnpm start` there. 23 | 24 | ## Troubleshooting 25 | 26 | - If you see a directory view of some files after starting the app rather than the test app itself (which should simply be some boxes and buttons), please try removing all three node_modules folders from the repo (you can utilize our pnpm clean:all command at the monorepo root) then redoing the pnpm commands above. 27 | 28 | - Due to Windows loopback security features, you may see a warning from your browser when running the test app saying that your connection is not private. Click Advanced -> Continue to localhost to proceed to the app. 29 | -------------------------------------------------------------------------------- /apps/teams-perf-test-app/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | Teams Perf Test App 14 | 15 | 16 | 17 |
18 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /apps/teams-perf-test-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "teams-perf-test-app", 3 | "private": true, 4 | "author": "Microsoft Teams", 5 | "description": "Teams Perf Test App utilizing Teams JavaScript client SDK to test Teams Host Perf", 6 | "version": "0.0.1", 7 | "scripts": { 8 | "build": "pnpm lint && webpack", 9 | "clean": "rimraf ./build", 10 | "lint": "pnpm eslint ./src --max-warnings 0 --fix --ext .tsx", 11 | "start": "webpack serve" 12 | }, 13 | "dependencies": { 14 | "react": "^17.0.1", 15 | "react-dom": "^17.0.1" 16 | }, 17 | "devDependencies": { 18 | "@microsoft/teams-js": "workspace:*" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/teams-perf-test-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Teams Perf Test App", 3 | "name": "Teams Perf Test App", 4 | "icons": [ 5 | ], 6 | "start_url": "/", 7 | "display": "standalone", 8 | "theme_color": "#000000", 9 | "background_color": "#ffffff" 10 | } 11 | -------------------------------------------------------------------------------- /apps/teams-perf-test-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-header { 6 | background-color: #282c34; 7 | min-height: 100vh; 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | justify-content: center; 12 | font-size: calc(10px + 2vmin); 13 | color: white; 14 | } 15 | -------------------------------------------------------------------------------- /apps/teams-perf-test-app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | 3 | import { app } from '@microsoft/teams-js'; 4 | import React, { ReactElement } from 'react'; 5 | 6 | import AppInitialization from './components/AppInitialization'; 7 | 8 | app.initialize(); 9 | app.notifyAppLoaded(); 10 | app.notifySuccess(); 11 | 12 | export const noHostSdkMsg = ' was called, but there was no response from the Host SDK.'; 13 | 14 | const App = (): ReactElement => { 15 | return ( 16 | <> 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /apps/teams-perf-test-app/src/components/AppInitialization.tsx: -------------------------------------------------------------------------------- 1 | import { app } from '@microsoft/teams-js'; 2 | import React, { ReactElement } from 'react'; 3 | 4 | import BoxAndButton from './BoxAndButton'; 5 | 6 | const AppInitializationAPIs = (): ReactElement => { 7 | const [notifyLoadedRes, setNotifyLoadedRes] = React.useState(''); 8 | const [notifySuccessRes, setNotifySuccessRes] = React.useState(''); 9 | const [notifyFailureRes, setNotifyFailureRes] = React.useState(''); 10 | 11 | const notifyLoaded = (): void => { 12 | app.notifyAppLoaded(); 13 | setNotifyLoadedRes('called'); 14 | }; 15 | 16 | const notifySuccess = (): void => { 17 | app.notifySuccess(); 18 | setNotifySuccessRes('called'); 19 | }; 20 | 21 | const notifyFailure = (reason?: string): void => { 22 | app.notifyFailure({ 23 | reason: (reason as app.FailedReason) || app.FailedReason.Other, 24 | }); 25 | setNotifyFailureRes('called'); 26 | }; 27 | 28 | return ( 29 | <> 30 | 37 | 44 | 51 | 52 | ); 53 | }; 54 | 55 | export default AppInitializationAPIs; 56 | -------------------------------------------------------------------------------- /apps/teams-perf-test-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /apps/teams-perf-test-app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | 6 | import App from './App'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root'), 13 | ); 14 | -------------------------------------------------------------------------------- /apps/teams-perf-test-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.json", 3 | "compilerOptions": { 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "allowSyntheticDefaultImports": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "jsx": "react" 18 | }, 19 | "include": [ 20 | "src" 21 | ], 22 | "exclude": [ 23 | "**/build/*", 24 | "build", 25 | "node_modules", 26 | "**/*.test.*", 27 | "webpack.config.js" 28 | ], 29 | } -------------------------------------------------------------------------------- /apps/teams-perf-test-app/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require('path'); 3 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 4 | 5 | module.exports = { 6 | mode: 'production', 7 | entry: './src/index.tsx', 8 | module: { 9 | rules: [ 10 | { 11 | test: /\.jsx?$/, 12 | exclude: /node_modules/, 13 | use: { 14 | loader: 'babel-loader', 15 | options: { 16 | presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'], 17 | }, 18 | }, 19 | }, 20 | { 21 | test: /\.tsx?$/, 22 | use: 'ts-loader', 23 | exclude: /node_modules/, 24 | }, 25 | { 26 | test: /\.css$/, 27 | use: ['style-loader', 'css-loader'], 28 | }, 29 | ], 30 | }, 31 | resolve: { 32 | extensions: ['.tsx', '.ts', '.js'], 33 | }, 34 | output: { 35 | path: path.resolve(__dirname, 'build'), 36 | filename: 'bundle.js', 37 | }, 38 | devServer: { 39 | static: { 40 | directory: path.join(__dirname, 'build'), 41 | publicPath: '/', 42 | }, 43 | compress: true, 44 | port: 4002, 45 | server: 'https', 46 | allowedHosts: 'all', 47 | }, 48 | performance: { hints: false }, 49 | plugins: [new HtmlWebPackPlugin({ template: './index.html', filename: 'index.html' })], 50 | }; 51 | -------------------------------------------------------------------------------- /apps/teams-test-app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react", 5 | "@babel/preset-typescript" 6 | ], 7 | "plugins": [ 8 | [ 9 | "@babel/plugin-transform-runtime", 10 | { 11 | "regenerator": true 12 | } 13 | ] 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /apps/teams-test-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ignorePatterns: ['.eslintrc.js'], 3 | parserOptions: { 4 | project: './tsconfig.json', 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/appEntity.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AppEntity", 3 | "platforms": "Web", 4 | "version": ">=2.0.1", 5 | "checkIsSupported": { 6 | "expectedOutput": "AppEntity is not supported" 7 | }, 8 | "testCases": [ 9 | { 10 | "title": "SelectAppEntity API Call - Success", 11 | "version": ">2.0.0", 12 | "type": "callResponse", 13 | "boxSelector": "#box_select_appEntity", 14 | "inputValue": { 15 | "threadId": "123", 16 | "categories": ["books", "animals"], 17 | "subEntityId": "abc" 18 | }, 19 | "expectedAlertValue": "appEntity.selectAppEntity called with 123 + books,animals + abc", 20 | "expectedTestAppValue": "{\"appId\":\"007\",\"appIconUrl\":\"appIncon_pengiun.com\",\"contentUrl\":\"contentUrl.com\",\"displayName\":\"penguin\",\"websiteUrl\":\"penguin.com\"}" 21 | }, 22 | { 23 | "title": "SelectAppEntity API Call - Success", 24 | "version": "2.0.0", 25 | "type": "callResponse", 26 | "boxSelector": "#box_select_appEntity", 27 | "inputValue": { 28 | "threadId": "123", 29 | "categories": ["books", "animals"] 30 | }, 31 | "expectedAlertValue": "appEntity.selectAppEntity called with 123 + books,animals", 32 | "expectedTestAppValue": "{\"appId\":\"007\",\"appIconUrl\":\"appIncon_pengiun.com\",\"contentUrl\":\"contentUrl.com\",\"displayName\":\"penguin\",\"websiteUrl\":\"penguin.com\"}" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/appInstallDialog.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AppInstallDialog", 3 | "platforms": "Web", 4 | "version": ">2.0.0", 5 | "checkIsSupported": { 6 | "domElementName": "checkCapabilityAppInstallDialog" 7 | }, 8 | "testCases": [ 9 | { 10 | "title": "openAppInstallDialog API Call - Success", 11 | "type": "callResponse", 12 | "boxSelector": "#box_openAppInstallDialog", 13 | "inputValue": { 14 | "appId": "957f8a7e-fbcd-411d-b69f-acb7eb58b515" 15 | }, 16 | "expectedAlertValue": "openAppInstallDialog called with {\"appId\":\"957f8a7e-fbcd-411d-b69f-acb7eb58b515\"}", 17 | "expectedTestAppValue": "called" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/calendar.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Calendar", 3 | "version": ">=2.0.0", 4 | "checkIsSupported": {}, 5 | "platforms": "*", 6 | "testCases": [ 7 | { 8 | "title": "openCalendarItem API Call - Success", 9 | "type": "callResponse", 10 | "boxSelector": "#box_openCalendarItem", 11 | "inputValue": { 12 | "itemId": "123" 13 | }, 14 | "expectedAlertValue": "openCalendarItem called with itemId: 123", 15 | "expectedTestAppValue": "Completed" 16 | }, 17 | { 18 | "title": "composeMeeting API Call - Success", 19 | "type": "callResponse", 20 | "boxSelector": "#box_composeMeeting", 21 | "inputValue": { 22 | "attendees": ["attendees"], 23 | "startTime": "startTime", 24 | "endTime": "endTime", 25 | "subject": "subject", 26 | "content": "content" 27 | }, 28 | "expectedAlertValue": "composeMeeting called with ##JSON_INPUT_VALUE##", 29 | "expectedTestAppValue": "Completed" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/call.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Call", 3 | "platforms": "Web", 4 | "version": ">=2.0.0", 5 | "checkIsSupported": { 6 | "domElementName": "checkCapabilityCall" 7 | }, 8 | "testCases": [ 9 | { 10 | "title": "startCall API Call - Success", 11 | "type": "callResponse", 12 | "boxSelector": "#box_startCall", 13 | "inputValue": { 14 | "targets": ["user1", "user2"], 15 | "requestedModalities": ["video"], 16 | "source": "source" 17 | }, 18 | "expectedAlertValue": "startCall called with ##JSON_INPUT_VALUE##", 19 | "expectedTestAppValue": "result: true" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/clipboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Clipboard", 3 | "version": ">2.14.0", 4 | "platforms": "*", 5 | "testCases": [ 6 | { 7 | "title": "Copy Text - success", 8 | "type": "callResponse", 9 | "boxSelector": "#box_copyText", 10 | "inputValue": "Hello Team", 11 | "expectedTestAppValue": "true" 12 | }, 13 | { 14 | "title": "Copy Text - failure", 15 | "type": "callResponse", 16 | "boxSelector": "#box_copyText", 17 | "inputValue": "\"\"", 18 | "skipJsonStringifyOnInputValue": true, 19 | "expectedTestAppValue": "Error: String can't be empty" 20 | }, 21 | { 22 | "title": "Copy JPEG Image - success", 23 | "type": "callResponse", 24 | "boxSelector": "#box_copyImage", 25 | "inputValue": "image/jpeg", 26 | "expectedTestAppValue": "true" 27 | }, 28 | { 29 | "title": "Copy PNG Image - success", 30 | "type": "callResponse", 31 | "boxSelector": "#box_copyImage", 32 | "inputValue": "image/png", 33 | "expectedTestAppValue": "true" 34 | }, 35 | { 36 | "title": "Copy Image - failure", 37 | "type": "callResponse", 38 | "boxSelector": "#box_copyImage", 39 | "inputValue": "\"\"", 40 | "skipJsonStringifyOnInputValue": true, 41 | "expectedTestAppValue": "Error: mimeType can't be empty" 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/dialog.card.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dialog Card", 3 | "platforms": "Web", 4 | "checkIsSupported": { 5 | "domElementName": "checkCapabilityDialogAdaptiveCard", 6 | "toggleId": "dialogCardToggle", 7 | "expectedOutput": "Dialog Adaptive Card module is not supported" 8 | }, 9 | "testCases": [] 10 | } 11 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/dialog.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dialog", 3 | "platforms": "Web", 4 | "checkIsSupported": { 5 | "domElementName": "checkCapabilityDialog" 6 | }, 7 | "testCases": [ 8 | { 9 | "title": "dialogResize API Call - Success", 10 | "type": "callResponse", 11 | "testUrlParams": [["frameContext", "task"]], 12 | "boxSelector": "#box_dialogResize", 13 | "inputValue": { 14 | "height": "large", 15 | "width": "large" 16 | }, 17 | "expectedAlertValue": "dialog.resize() called with ##JSON_INPUT_VALUE##" 18 | }, 19 | { 20 | "title": "dialogSubmit API Call - Success", 21 | "type": "callResponse", 22 | "testUrlParams": [["frameContext", "task"]], 23 | "boxSelector": "#box_dialogSubmitWithInput", 24 | "inputValue": { 25 | "result": "testResult" 26 | }, 27 | "expectedAlertValue": "dialog.submit() called with \"testResult\"" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/dialog.update.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dialog Update", 3 | "platforms": "Web", 4 | "checkIsSupported": { 5 | "domElementName": "checkCapabilityResizeDialog", 6 | "toggleId": "dialogUpdateToggle", 7 | "expectedOutput": "Dialog.update module is not supported" 8 | }, 9 | "testCases": [] 10 | } 11 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/dialog.url.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dialog Url", 3 | "platforms": "Web", 4 | "checkIsSupported": { 5 | "domElementName": "checkCapabilityDialogUrl", 6 | "toggleId": "dialogUrlToggle", 7 | "expectedOutput": "Dialog Url module is not supported" 8 | }, 9 | "testCases": [] 10 | } 11 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/dialog.url.parentCommunication.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dialog Url ParentCommunication", 3 | "platforms": "Web", 4 | "version": ">2.23.0", 5 | "checkIsSupported": { 6 | "domElementName": "checkCapabilityDialogParentCommunication", 7 | "toggleId": "dialogParentCommunicationToggle", 8 | "expectedOutput": "Dialog parent communication module is not supported" 9 | }, 10 | "testCases": [] 11 | } 12 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/externalAppAuthentication.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ExternalAppAuthentication", 3 | "version": ">2.18.0", 4 | "platforms": "Web", 5 | "testCases": [ 6 | { 7 | "title": "checkExternalAppAuthenticationCapability API Call - Success", 8 | "type": "callResponse", 9 | "boxSelector": "#box_checkExternalAppAuthenticationCapability", 10 | "expectedTestAppValue": "External App Authentication module is supported" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/externalAppAuthenticationForCEA.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ExternalAppAuthenticationForCEA", 3 | "version": ">2.29.0", 4 | "hostSdkVersion": { 5 | "web": ">=4.3.0" 6 | }, 7 | "platforms": "Web", 8 | "testCases": [ 9 | { 10 | "title": "checkExternalAppAuthenticationForCEACapability API Call - Success", 11 | "type": "callResponse", 12 | "boxSelector": "#box_checkExternalAppAuthenticationForCEACapability", 13 | "expectedTestAppValue": "External App Authentication For CEA module is supported" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/externalAppCardActions.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ExternalAppCardActions", 3 | "version": ">2.18.0", 4 | "platforms": "Web", 5 | "testCases": [ 6 | { 7 | "title": "checkExternalAppCardActionsCapability API Call - Success", 8 | "type": "callResponse", 9 | "boxSelector": "#box_checkExternalAppCardActionsCapability", 10 | "expectedTestAppValue": "External App Card Actions module is supported" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/externalAppCardActionsForCEA.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ExternalAppCardActionsForCEA", 3 | "version": ">=2.29.0", 4 | "hostSdkVersion": { 5 | "web": ">=4.3.0" 6 | }, 7 | "platforms": "Web", 8 | "testCases": [ 9 | { 10 | "title": "checkExternalAppCardActionsForCEACapability API Call - Success", 11 | "type": "callResponse", 12 | "boxSelector": "#box_checkExternalAppCardActionsForCEACapability", 13 | "expectedTestAppValue": "External App Card Actions For CEA module is supported" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/externalAppCardActionsForDA.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ExternalAppCardActionsForDA", 3 | "version": ">2.34.0", 4 | "hostSdkVersion": { 5 | "web": ">7.2.0" 6 | }, 7 | "platforms": "Web", 8 | "testCases": [ 9 | { 10 | "title": "checkExternalAppCardActionsForDACapability API Call - Success", 11 | "type": "callResponse", 12 | "boxSelector": "#box_checkExternalAppCardActionsForDACapability", 13 | "expectedTestAppValue": "External App Card Actions For DA module is supported" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/externalAppCommands.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ExternalAppCommands", 3 | "version": ">2.21.0", 4 | "platforms": "Web", 5 | "testCases": [ 6 | { 7 | "title": "checkExternalAppCommandsCapability API Call - Success", 8 | "type": "callResponse", 9 | "boxSelector": "#box_checkExternalAppCommandsCapability", 10 | "expectedTestAppValue": "External App Commands module is supported" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/log.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Log", 3 | "platforms": "Web", 4 | "testCases": [ 5 | { 6 | "title": "registerGetLogHandler API Call - Handler", 7 | "type": "registerAndRaiseEvent", 8 | "boxSelector": "#box_registerGetLogHandler", 9 | "eventName": "log.request", 10 | "expectedAlertValue": "handleAppLog called with appLog: App log string", 11 | "expectedTestAppValue": "Success" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/mail.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Mail", 3 | "version": ">2.0.0-beta.0", 4 | "platforms": "*", 5 | "checkIsSupported": { 6 | "domElementName": "checkCapabilityMail" 7 | }, 8 | "testCases": [ 9 | { 10 | "title": "openMailItem API Call - Success", 11 | "type": "callResponse", 12 | "boxSelector": "#box_openMailItem", 13 | "inputValue": { 14 | "itemId": "123" 15 | }, 16 | "expectedAlertValue": "openMailItem called with itemId: 123", 17 | "expectedTestAppValue": "Completed" 18 | }, 19 | { 20 | "title": "composeMail API Call - Success", 21 | "type": "callResponse", 22 | "boxSelector": "#box_composeMail", 23 | "inputValue": { 24 | "type": "new", 25 | "toRecipients": ["toRecipients"], 26 | "ccRecipients": ["ccRecipients"], 27 | "bccRecipients": ["bccRecipients"], 28 | "subject": "subject", 29 | "message": "message" 30 | }, 31 | "expectedAlertValue": "composeMail called with ##JSON_INPUT_VALUE##", 32 | "expectedTestAppValue": "Completed" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/meeting.appShareButton.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Meeting", 3 | "platforms": "Web", 4 | "testUrlParams": [["sidePanel"]], 5 | "testCases": [ 6 | { 7 | "title": "setOptions API Call - Success", 8 | "type": "callResponse", 9 | "boxSelector": "#box_setOptions", 10 | "inputValue": { 11 | "contentUrl": "https://www.someUrl.com", 12 | "isVisible": true 13 | }, 14 | "expectedAlertValue": "setOptions called", 15 | "expectedTestAppValue": "setOptions() succeeded" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/messageChannels.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Message Channels", 3 | "platforms": "Web", 4 | "version": ">=2.20.0", 5 | "hostSdkVersion": { 6 | "web": ">=2.12.0" 7 | }, 8 | "testCases": [ 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/monetization.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Monetization", 3 | "platforms": "Web", 4 | "checkIsSupported": { 5 | "domElementName": "checkCapabilityMonetization", 6 | "version": "1.x || >2.0.0-beta.2" 7 | }, 8 | "testCases": [ 9 | { 10 | "title": "openPurchaseExperience API Call - Success", 11 | "type": "callResponse", 12 | "version": "1.x || >2.0.0-beta.2", 13 | "boxSelector": "#box_monetization_openPurchaseExperience", 14 | "inputValue": { 15 | "planId": "abc", 16 | "term": "001" 17 | }, 18 | "expectedAlertValue": "openPurchaseExperience called with ##JSON_INPUT_VALUE##" 19 | }, 20 | 21 | { 22 | "title": "openPurchaseExperience API Call - Success", 23 | "type": "callResponse", 24 | "version": "2.0.0-beta.2", 25 | "boxSelector": "#box_monetization_openPurchaseExperience", 26 | "expectedAlertValue": "openPurchaseExperience called with undefined" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/nestedAppAuth.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestedAppAuth", 3 | "platforms": "*", 4 | "version": ">=2.22.0", 5 | "testCases": [ 6 | { 7 | "title": "nestedAppAuth isNAAChannelRecommended API Call - Success", 8 | "type": "callResponse", 9 | "boxSelector": "#box_checkIsNAAChannelRecommended", 10 | "expectedTestAppValue": "NAA channel is recommended" 11 | }, 12 | { 13 | "title": "nestedAppAuth get parent origin", 14 | "type": "callResponse", 15 | "version": ">2.35.0", 16 | "boxSelector": "#box_getParentOrigin", 17 | "platformsExcluded": ["iOS", "Android"], 18 | "expectedTestAppValue": "https://local.teams.office.com:8080" 19 | }, 20 | { 21 | "title": "nestedAppAuth isDeeplyNestedAuthSupported API Call - Success", 22 | "type": "callResponse", 23 | "version": ">2.35.0", 24 | "boxSelector": "#box_checkIsDeeplyNestedAuthSupported", 25 | "hostSdkVersion": { 26 | "web": ">=7.1.0", 27 | "android": ">=6.0.0", 28 | "ios": ">=6.0.0" 29 | }, 30 | "expectedTestAppValue": "NAA deeply nested auth is supported" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/notifications.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Notifications", 3 | "platforms": "Web", 4 | "version": ">2.0.0-beta.0", 5 | "checkIsSupported": { 6 | "domElementName": "checkCapabilityNotifications" 7 | }, 8 | "testCases": [ 9 | { 10 | "title": "showNotification API Call - Success", 11 | "type": "callResponse", 12 | "boxSelector": "#box_showNotification", 13 | "inputValue": { 14 | "message": "testMessage", 15 | "notificationType": "fileDownloadStart" 16 | }, 17 | "expectedAlertValue": "showNotification called with ##JSON_INPUT_VALUE##" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/otherAppStateChange.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OtherAppStateChange", 3 | "platforms": "Web", 4 | "version": ">2.21.0", 5 | "hostSdkVersion": { 6 | "web": ">2.13.1" 7 | }, 8 | "testCases": [ 9 | { 10 | "title": "isSupported - Success", 11 | "type": "callResponse", 12 | "boxSelector": "#box_otherAppStateChange_isSupported", 13 | "expectedTestAppValue": "OtherAppStateChanged module is supported" 14 | }, 15 | { 16 | "title": "registerAppInstallationHandler - Success", 17 | "type": "callResponse", 18 | "boxSelector": "#box_otherAppStateChange_registerInstallHandler", 19 | "expectedTestAppValue": "received" 20 | }, 21 | { 22 | "title": "registerAppInstallationHandler should not receive install events if app is not approved", 23 | "type": "registerAndRaiseEvent", 24 | "boxSelector": "#box_otherAppStateChange_registerInstallHandler", 25 | "eventName": "otherApp.install", 26 | "eventData": { 27 | "appIds": ["123", "456"] 28 | }, 29 | "expectedTestAppValue": "received" 30 | }, 31 | { 32 | "title": "unregisterAppInstallationHandler - Sends but not processed if app is not approved", 33 | "type": "callResponse", 34 | "boxSelector": "#box_otherAppStateChange_unregisterInstallHandler", 35 | "expectedTestAppValue": "received" 36 | }, 37 | { 38 | "title": "notifyInstallCompleted - Success", 39 | "type": "callResponse", 40 | "boxSelector": "#box_otherAppStateChange_notifyInstallCompleted", 41 | "expectedTestAppValue": "notified" 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/pages.appButton.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pages AppButton", 3 | "platforms": "Web", 4 | "testCases": [ 5 | { 6 | "title": "registerAppButtonClick API Call - Handler", 7 | "type": "registerAndRaiseEvent", 8 | "boxSelector": "#box_registerAppButtonClickHandler", 9 | "eventName": "appButtonClick", 10 | "expectedTestAppValue": "successfully called" 11 | }, 12 | { 13 | "title": "registerAppButtonHoverEnterHandler API Call - Handler", 14 | "type": "registerAndRaiseEvent", 15 | "boxSelector": "#box_registerAppButtonHoverEnterHandler", 16 | "eventName": "appButtonHoverEnter", 17 | "expectedTestAppValue": "successfully called" 18 | }, 19 | { 20 | "title": "registerAppButtonHoverLeaveHandler API Call - Handler", 21 | "type": "registerAndRaiseEvent", 22 | "boxSelector": "#box_registerAppButtonHoverLeaveHandler", 23 | "eventName": "appButtonHoverLeave", 24 | "expectedTestAppValue": "successfully called" 25 | }, 26 | { 27 | "title": "registerAppButtonHoverLeaveHandler API Call - Handler", 28 | "type": "registerAndRaiseEvent", 29 | "boxSelector": "#box_registerAppButtonHoverLeaveHandler", 30 | "eventName": "appButtonHoverLeave", 31 | "expectedTestAppValue": "successfully called" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/pages.backStack.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pages BackStack", 3 | "platforms": "*", 4 | "testCases": [ 5 | { 6 | "title": "navigateBack API Call - Success", 7 | "type": "callResponse", 8 | "boxSelector": "#box_navigateBack", 9 | "expectedAlertValue": "navigateBack called", 10 | "expectedTestAppValue": "Completed" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/pages.fullTrust.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pages FullTrust", 3 | "platforms": "Web", 4 | "version": ">2.0.0-beta.0", 5 | "testCases": [ 6 | { 7 | "title": "enterFullscreen API Call - Success", 8 | "type": "callResponse", 9 | "boxSelector": "#box_enterFullscreen", 10 | "expectedAlertValue": "enterFullscreen called" 11 | }, 12 | { 13 | "title": "exitFullscreen API Call - Success", 14 | "type": "callResponse", 15 | "boxSelector": "#box_exitFullscreen", 16 | "expectedAlertValue": "exitFullscreen called" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/pages.tabs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pages Tabs", 3 | "platforms": "Web", 4 | "checkIsSupported": { 5 | "capabilityName": "PageTabs", 6 | "expectedOutput": "Pages.tabs module is not supported" 7 | }, 8 | "testCases": [ 9 | { 10 | "title": "navigateToTab API Call - Success", 11 | "type": "callResponse", 12 | "boxSelector": "#box_navigateToTab", 13 | "inputValue": { 14 | "tabName": "TestTab", 15 | "internalTabInstanceId": "23", 16 | "channelIsFavorite": true, 17 | "url": "https://test.com" 18 | }, 19 | "expectedAlertValue": "navigateToTab called with ##JSON_INPUT_VALUE##", 20 | "expectedTestAppValue": "Completed" 21 | }, 22 | { 23 | "title": "getTabInstance API Call - Success", 24 | "type": "callResponse", 25 | "boxSelector": "#box_getTabInstance", 26 | "inputValue": { 27 | "favoriteTeamsOnly": true 28 | }, 29 | "expectedAlertValue": "getTabInstances called with ##JSON_INPUT_VALUE##", 30 | "expectedTestAppValue": "{\"teamTabs\":[{\"tabName\":\"dummy1\",\"channelId\":\"1\"},{\"tabName\":\"dummy2\",\"channelId\":\"1\"}]}" 31 | }, 32 | { 33 | "title": "getMRUTabInstance API Call - Success", 34 | "type": "callResponse", 35 | "boxSelector": "#box_getMRUTabInstance", 36 | "inputValue": { 37 | "favoriteTeamsOnly": true 38 | }, 39 | "expectedAlertValue": "getMruTabInstances called with ##JSON_INPUT_VALUE##", 40 | "expectedTestAppValue": "{\"teamTabs\":[{\"tabName\":\"dummy1\",\"channelId\":\"1\"},{\"tabName\":\"dummy2\",\"channelId\":\"1\"}]}" 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/people.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "People", 3 | "platforms": "*", 4 | "featureTests": [ 5 | { 6 | "feature": { 7 | "id": "selectPeople", 8 | "version": 1 9 | }, 10 | "testCases": [ 11 | { 12 | "title": "selectPeople API Call - Success", 13 | "type": "callResponse", 14 | "boxSelector": "#box_selectPeople", 15 | "inputValue": { 16 | "title": "title", 17 | "setSelected": ["setSelected"], 18 | "openOrgWideSearchInChatOrChannel": true, 19 | "singleSelect": true 20 | }, 21 | "expectedAlertValue": "selectPeople called with ##JSON_INPUT_VALUE##", 22 | "expectedTestAppValue": "[{\"objectId\":\"1\",\"displayName\":\"Name\",\"email\":\"test@microsoft.com\"}]" 23 | } 24 | ] 25 | } 26 | ], 27 | "testCases": [ 28 | { 29 | "title": "selectPeople API Call - Success", 30 | "type": "callResponse", 31 | "boxSelector": "#box_selectPeople", 32 | "inputValue": { 33 | "title": "title", 34 | "setSelected": ["setSelected"], 35 | "openOrgWideSearchInChatOrChannel": true, 36 | "singleSelect": true 37 | }, 38 | "expectedAlertValue": "selectPeople called with ##JSON_INPUT_VALUE##", 39 | "expectedTestAppValue": "[{\"objectId\":\"1\",\"displayName\":\"Name\",\"email\":\"test@microsoft.com\"}]" 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Profile", 3 | "platforms": "Web", 4 | "version": ">2.5.0", 5 | "checkIsSupported": { 6 | "domElementName": "checkCapabilityProfile" 7 | }, 8 | "testCases": [ 9 | { 10 | "title": "showProfile API Call - Success", 11 | "type": "callResponse", 12 | "boxSelector": "#box_showProfile", 13 | "inputValue": { 14 | "modality": "Card", 15 | "persona": { 16 | "identifiers": { 17 | "Smtp": "test@microsoft.com" 18 | } 19 | }, 20 | "targetElementBoundingRect": { 21 | "x": 0, 22 | "y": 0, 23 | "width": 0, 24 | "height": 0 25 | }, 26 | "triggerType": "MouseClick" 27 | }, 28 | "expectedAlertValue": "showProfile called with {\"modality\":\"Card\",\"persona\":{\"identifiers\":{\"Smtp\":\"test@microsoft.com\"}},\"targetElementBoundingRect\":{\"x\":243,\"y\":0,\"width\":0,\"height\":0,\"top\":0,\"right\":243,\"bottom\":0,\"left\":243},\"triggerType\":\"MouseClick\"}" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/secondaryBrowser.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SecondaryBrowser", 3 | "version": ">=2.12.0", 4 | "platforms": "iOS", 5 | "checkIsSupported": {}, 6 | "testCases": [ 7 | { 8 | "title": "openUrl API Call - Success", 9 | "type": "callResponse", 10 | "boxSelector": "#box_secondaryBrowser_open", 11 | "inputValue": "https://www.bing.com", 12 | "expectedSecondViewTarget": "SFSafariViewController" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/sharing.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sharing", 3 | "platforms": "Web", 4 | "checkIsSupported": { 5 | "expectedOutput": "Sharing is not supported" 6 | }, 7 | "testCases": [ 8 | { 9 | "title": "shareWebContent API Call - Success", 10 | "type": "callResponse", 11 | "boxSelector": "#box_share_shareWebContent", 12 | "inputValue": { 13 | "content": [ 14 | { 15 | "type": "URL", 16 | "url": "https://bing.com", 17 | "message": "def" 18 | } 19 | ] 20 | }, 21 | "expectedAlertValue": "shareWebContent called with ##JSON_INPUT_VALUE##" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/stageView.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "StageView", 3 | "platforms": "Web", 4 | "version": ">2.0.0-beta.3", 5 | "checkIsSupported": { 6 | "expectedOutput": "StageView is not supported", 7 | "version": ">2.0.0" 8 | }, 9 | "testCases": [ 10 | { 11 | "title": "openStageView API Call - Success", 12 | "type": "callResponse", 13 | "boxSelector": "#box_stageViewOpen", 14 | "hostSdkVersion": { 15 | "web": "<4.0.1" 16 | }, 17 | "inputValue": { 18 | "appId": "appId", 19 | "contentUrl": "contentUrl", 20 | "threadId": "threadId", 21 | "title": "title", 22 | "websiteUrl": "websiteUrl", 23 | "entityId": "entityId", 24 | "openMode": "modal", 25 | "messageId": "messageId" 26 | }, 27 | "expectedAlertValue": "stageView.open called with ##JSON_INPUT_VALUE##", 28 | "expectedTestAppValue": "opened", 29 | "skipForCallbackBasedRuns": true 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/teams.fullTrust.joinedTeams.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Teams FullTrust JoinedTeams", 3 | "platforms": "Web", 4 | "version": ">2.0.0-beta.0", 5 | "testCases": [ 6 | { 7 | "title": "getUserJoinedTeams API Call - Success", 8 | "type": "callResponse", 9 | "boxSelector": "#box_getUserJoinedTeams", 10 | "inputValue": { 11 | "favoriteTeamsOnly": true 12 | }, 13 | "expectedAlertValue": "getUserJoinedTeams called with ##JSON_INPUT_VALUE##", 14 | "expectedTestAppValue": "{\"userJoinedTeams\":[{\"teamId\":\"testTeamId\",\"teamName\":\"testTeamName\"}]}" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/teams.fullTrust.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Teams FullTrust", 3 | "platforms": "Web", 4 | "version": ">2.0.0-beta.0", 5 | "testCases": [ 6 | { 7 | "title": "getConfigSetting API Call - Success", 8 | "type": "callResponse", 9 | "version": ">2.0.0-beta.2", 10 | "boxSelector": "#box_getConfigSetting2", 11 | "inputValue": "testKey", 12 | "expectedAlertValue": "getConfigSetting called with key: testKey", 13 | "expectedTestAppValue": "testValue" 14 | }, 15 | { 16 | "title": "getConfigSetting API Call - Success", 17 | "type": "callResponse", 18 | "version": "2.0.0-beta.2", 19 | "boxSelector": "#box_getConfigSetting", 20 | "inputValue": "testKey", 21 | "expectedAlertValue": "getConfigSetting called with key: testKey", 22 | "expectedTestAppValue": "testValue" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/teams.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Teams", 3 | "platforms": "Web", 4 | "version": "1.x || >2.0.0-beta.2", 5 | "testCases": [ 6 | { 7 | "title": "getTeamChannels API Call - Success", 8 | "type": "callResponse", 9 | "boxSelector": "#box_getTeamChannels2", 10 | "inputValue": "groupId", 11 | "expectedAlertValue": "getTeamChannels called with: groupId", 12 | "expectedTestAppValue": "[{\"siteUrl\":\"siteURL\",\"objectId\":\"objectId\",\"folderRelativeUrl\":\"folderRelativeUrl\",\"displayName\":\"displayname\",\"channelType\":\"Regular\"}]" 13 | }, 14 | { 15 | "title": "refreshSiteUrl API Call - Success", 16 | "type": "callResponse", 17 | "boxSelector": "#box_refreshSiteUrl2", 18 | "inputValue": "threadId", 19 | "expectedAlertValue": "refreshSiteUrl called with: threadId", 20 | "expectedTestAppValue": "Success" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/thirdPartyCloudStorage.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ThirdPartyCloudStorage", 3 | "platforms": "Web", 4 | "version": ">2.18.0", 5 | "checkIsSupported": { 6 | "domElementName": "thirdPartyCloudStorageCapability", 7 | "expectedOutput": "ThirdPartyCloudStorage is not supported" 8 | }, 9 | "testCases": [ 10 | { 11 | "title": "thirdPartyCloudStorage API Call - Success", 12 | "type": "callResponse", 13 | "boxSelector": "#box_thirdPartyCloudStorage", 14 | "inputValue": "testThreadId", 15 | "expectedAlertValue": "thirdPartyCloudStorage called with ##JSON_INPUT_VALUE##", 16 | "expectedTestAppValue": "Received files in callback" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/video.mediaStream.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Video MediaStream", 3 | "platforms": "Web", 4 | "testUrlParams": [["frameContext", "sidePanel"]], 5 | "version": ">2.11.0", 6 | "testCases": [ 7 | { 8 | "title": "video.mediaStream.registerForVideoFrame - Throw error", 9 | "type": "callResponse", 10 | "modulesToDisable": ["videoSharedFrame"], 11 | "boxSelector": "#box_videoMediaStreamRegisterForVideoFrame", 12 | "expectedTestAppValue": "Faild to register for video frame: {\"errorCode\":100}" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /apps/teams-test-app/e2e-test-data/webStorage.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WebStorage", 3 | "platforms": "Web", 4 | "version": ">2.22.0", 5 | "hostSdkVersion": { 6 | "web": ">2.16.0" 7 | }, 8 | "checkIsSupported": { 9 | "domElementName": "checkWebStorageCapability", 10 | "expectedOutput": "webStorage is not supported" 11 | }, 12 | "testCases": [ 13 | { 14 | "title": "isWebStorageClearedOnUserLogOut function Call - Success", 15 | "type": "callResponse", 16 | "boxSelector": "#box_isWebStorageClearedOnUserLogOut", 17 | "expectedAlertValue": "isWebStorageClearedOnUserLogOut called", 18 | "expectedTestAppValue": "webStorage is cleared on user log out" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /apps/teams-test-app/index_bundle.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | Teams Test App 14 | 15 | 16 | 17 |
18 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /apps/teams-test-app/index_cdn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | Teams Test App 14 | 15 | 16 | 17 | 22 |
23 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /apps/teams-test-app/index_local.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | Teams Test App 14 | 15 | 16 | 17 |
18 | 19 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /apps/teams-test-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "teams-test-app", 3 | "private": true, 4 | "author": "Microsoft Teams", 5 | "description": "Teams Test App utilizing Teams JavaScript client SDK to test Hosts", 6 | "version": "2.37.0", 7 | "scripts": { 8 | "build": "pnpm build:bundle", 9 | "build:bundle": "pnpm validate-test-schema && pnpm lint && webpack", 10 | "build:CDN": "pnpm lint && webpack --config webpack.cdn.config.js", 11 | "build:local": "pnpm lint && webpack --config webpack.local.config.js && pnpm copy", 12 | "clean": "rimraf ./build", 13 | "copy": "shx cp ../../packages/teams-js/dist/umd/MicrosoftTeams.min.js ./build/ && shx cp ../../packages/teams-js/dist/umd/MicrosoftTeams.min.js.map ./build/", 14 | "lint": "pnpm eslint ./src --max-warnings 0 --fix --ext .tsx", 15 | "start": "pnpm start:bundle", 16 | "start:bundle": "webpack serve", 17 | "start:CDN": "webpack serve --config webpack.cdn.config.js", 18 | "start:local": "webpack serve --config webpack.local.config.js", 19 | "validate-test-schema": "cd ../.. && pnpm validate-test-schema" 20 | }, 21 | "dependencies": { 22 | "react": "^17.0.1", 23 | "react-dom": "^17.0.1", 24 | "react-router-dom": "^6.21.3" 25 | }, 26 | "devDependencies": { 27 | "@microsoft/teams-js": "workspace:*" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/teams-test-app/public/README.md: -------------------------------------------------------------------------------- 1 | # M365 Test App 2 | 3 | ## Sideloading the test app in a host 4 | 5 | 1. Follow the instructions [here](../README.md) to build the test app and run it locally. 6 | 2. Open the [manifest.json](./manifest.json) file and replace all instances of "https://" with the url the test app is running at. 7 | 3. Zip all of the files in this directory _except this README file_ into a `manifest.zip` file 8 | 4. Follow the sideloading instructions of the host of your choice and upload your new `manifest.zip` file when asked for an app manifest. 9 | -------------------------------------------------------------------------------- /apps/teams-test-app/public/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-library-js/b41cf307375e5469df05da3db6cb8b9d4b70c824/apps/teams-test-app/public/color.png -------------------------------------------------------------------------------- /apps/teams-test-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json", 3 | "version": "1.0.0", 4 | "manifestVersion": "1.16", 5 | "id": "a79a2374-ec2a-486e-a3dc-b913fdb1f365", 6 | "packageName": "com.package.name", 7 | "name": { "short": "M365 Test App", "full": "M365 Test Application" }, 8 | "developer": { 9 | "name": "M365", 10 | "mpnId": "", 11 | "websiteUrl": "https://", 12 | "privacyUrl": "https://", 13 | "termsOfUseUrl": "https://" 14 | }, 15 | "description": { 16 | "short": "App to test infrastructure", 17 | "full": "App to test infrastructure" 18 | }, 19 | "icons": { "outline": "outline.png", "color": "color.png" }, 20 | "accentColor": "#FFFFFF", 21 | "configurableTabs": [ 22 | { 23 | "configurationUrl": "https://", 24 | "canUpdateConfiguration": true, 25 | "scopes": ["groupChat", "team"], 26 | "context": [ 27 | "channelTab", 28 | "privateChatTab", 29 | "meetingSidePanel", 30 | "meetingStage", 31 | "meetingDetailsTab", 32 | "meetingChatTab" 33 | ] 34 | } 35 | ], 36 | "staticTabs": [ 37 | { 38 | "entityId": "test", 39 | "name": "Test", 40 | "contentUrl": "https://", 41 | "websiteUrl": "https://", 42 | "scopes": ["personal"] 43 | }, 44 | { "entityId": "about", "scopes": ["personal"] } 45 | ], 46 | "validDomains": [], 47 | "devicePermissions": ["openExternal", "midi", "notifications", "media", "geolocation"] 48 | } 49 | -------------------------------------------------------------------------------- /apps/teams-test-app/public/outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-library-js/b41cf307375e5469df05da3db6cb8b9d4b70c824/apps/teams-test-app/public/outline.png -------------------------------------------------------------------------------- /apps/teams-test-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-header { 6 | background-color: #282c34; 7 | min-height: 100vh; 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | justify-content: center; 12 | font-size: calc(10px + 2vmin); 13 | color: white; 14 | } 15 | 16 | .App-container { 17 | display: grid; 18 | } 19 | 20 | /* ============================================================== 21 | * Grouped Mode - Button, Section Button, and Section Content Styles 22 | * ============================================================== */ 23 | 24 | /* Style for individual section buttons */ 25 | .section-button-in-grouped-mode { 26 | background-color: #0f6cbd; /* Blue background for buttons */ 27 | color: white; /* White text */ 28 | border: none; /* Remove default border */ 29 | border-radius: 4px; /* Rounded corners */ 30 | padding: 8px 15px; /* Padding inside the button */ 31 | cursor: pointer; /* Pointer cursor on hover */ 32 | font-size: 14px; /* Font size of the button text */ 33 | } 34 | 35 | /* Style for section button hover effect */ 36 | .section-button-in-grouped-mode:hover { 37 | background-color: #0056b3; /* Darker blue on hover */ 38 | } 39 | 40 | /* Style for the section content */ 41 | .section-content-in-grouped-mode { 42 | margin-top: 2px; /* Space above each section's content */ 43 | border: 1px solid #ddd; /* Light grey border around the section */ 44 | padding: 4px; /* Padding inside the section */ 45 | border-radius: 4px; /* Rounded corners */ 46 | } 47 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/AppInstallDialog.tsx: -------------------------------------------------------------------------------- 1 | import { appInstallDialog } from '@microsoft/teams-js'; 2 | import React from 'react'; 3 | 4 | import { ApiWithoutInput, ApiWithTextInput } from './utils'; 5 | import { ModuleWrapper } from './utils/ModuleWrapper'; 6 | 7 | const CheckAppInstallDialogCapability = (): React.ReactElement => 8 | ApiWithoutInput({ 9 | name: 'checkCapabilityAppInstallDialog', 10 | title: 'Check Capability App Install Dialog', 11 | onClick: async () => `AppInstallDialog module ${appInstallDialog.isSupported() ? 'is' : 'is not'} supported`, 12 | }); 13 | 14 | const OpenAppInstallDialog = (): React.ReactElement => 15 | ApiWithTextInput({ 16 | name: 'openAppInstallDialog', 17 | title: 'Open App Install Dialog', 18 | onClick: { 19 | validateInput: (input) => { 20 | if (!input.appId) { 21 | throw new Error('appId is required'); 22 | } 23 | }, 24 | submit: async (input) => { 25 | await appInstallDialog.openAppInstallDialog(input); 26 | return 'called'; 27 | }, 28 | }, 29 | defaultInput: JSON.stringify({ 30 | appId: '957f8a7e-fbcd-411d-b69f-acb7eb58b515', 31 | }), 32 | }); 33 | 34 | const AppInstallDialogAPIs: React.FC = () => ( 35 | 36 | 37 | 38 | 39 | ); 40 | 41 | export default AppInstallDialogAPIs; 42 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/CallAPIs.tsx: -------------------------------------------------------------------------------- 1 | import { call } from '@microsoft/teams-js'; 2 | import React from 'react'; 3 | 4 | import { ApiWithoutInput, ApiWithTextInput } from './utils'; 5 | import { ModuleWrapper } from './utils/ModuleWrapper'; 6 | 7 | const CheckCallCapability = (): React.ReactElement => 8 | ApiWithoutInput({ 9 | name: 'checkCapabilityCall', 10 | title: 'Check Capability Call', 11 | onClick: async () => `Call module ${call.isSupported() ? 'is' : 'is not'} supported`, 12 | }); 13 | 14 | const StartCall = (): React.ReactElement => 15 | ApiWithTextInput({ 16 | name: 'startCall', 17 | title: 'Start Call', 18 | onClick: { 19 | validateInput: (input) => { 20 | if (!input.targets) { 21 | throw new Error('targets is required'); 22 | } 23 | const targets = input.targets; 24 | if (!Array.isArray(targets) || targets.length === 0 || targets.some((x) => typeof x !== 'string')) { 25 | throw new Error('targets has to be a non-empty array of strings'); 26 | } 27 | }, 28 | submit: async (callParams) => { 29 | const result = await call.startCall(callParams); 30 | return 'result: ' + result; 31 | }, 32 | }, 33 | defaultInput: JSON.stringify({ 34 | targets: ['user1', 'user2'], 35 | requestedModalities: ['video'], 36 | source: 'source', 37 | }), 38 | }); 39 | 40 | const CallAPIs: React.FC = () => ( 41 | 42 | 43 | 44 | 45 | ); 46 | 47 | export default CallAPIs; 48 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/Custom.tsx: -------------------------------------------------------------------------------- 1 | import { registerCustomHandler, sendCustomMessage } from '@microsoft/teams-js'; 2 | import React from 'react'; 3 | 4 | import { ApiWithoutInput } from './utils'; 5 | import { ModuleWrapper } from './utils/ModuleWrapper'; 6 | 7 | const CustomApiWithEvent = (): React.ReactElement => 8 | ApiWithoutInput({ 9 | name: 'customApiEvent', 10 | title: 'Call Custom API (Event)', 11 | onClick: async () => { 12 | await sendCustomMessage('custom-service-event'); 13 | return ''; 14 | }, 15 | }); 16 | 17 | const CustomApiReturnData = (): React.ReactElement => 18 | ApiWithoutInput({ 19 | name: 'customApiResponse', 20 | title: 'Call Custom API (Response)', 21 | onClick: async (setResult) => { 22 | await sendCustomMessage('custom-service-test', undefined, (args) => { 23 | setResult(args); 24 | }); 25 | return ''; 26 | }, 27 | }); 28 | 29 | const RegisterCustomHandler = (): React.ReactElement => 30 | ApiWithoutInput({ 31 | name: 'registerCustomHandler', 32 | title: 'Register Custom Handler', 33 | onClick: async (setResult) => { 34 | registerCustomHandler('custom-service-event', (result: string) => { 35 | setResult(result); 36 | return []; 37 | }); 38 | 39 | return 'registered'; 40 | }, 41 | }); 42 | 43 | const CustomAPIs: React.FC = () => ( 44 | 45 | 46 | 47 | 48 | 49 | ); 50 | 51 | export default CustomAPIs; 52 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/DialogAPIs.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-types */ 2 | import { dialog } from '@microsoft/teams-js'; 3 | import React, { ReactElement } from 'react'; 4 | 5 | import { ApiWithoutInput } from './utils'; 6 | import { ModuleWrapper } from './utils/ModuleWrapper'; 7 | 8 | const DialogAPIs = (): ReactElement => { 9 | const CheckDialogCapability = (): ReactElement => 10 | ApiWithoutInput({ 11 | name: 'checkCapabilityDialog', 12 | title: 'Check Capability Dialog', 13 | onClick: async () => { 14 | if (dialog.isSupported()) { 15 | return 'Dialog module is supported'; 16 | } else { 17 | return 'Dialog module is not supported'; 18 | } 19 | }, 20 | }); 21 | 22 | return ( 23 | 24 | 25 | 26 | ); 27 | }; 28 | 29 | export default DialogAPIs; 30 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/LogsAPIs.tsx: -------------------------------------------------------------------------------- 1 | import { logs } from '@microsoft/teams-js'; 2 | import React, { ReactElement } from 'react'; 3 | 4 | import { generateRegistrationMsg } from '../App'; 5 | import { ApiWithoutInput } from './utils'; 6 | import { ModuleWrapper } from './utils/ModuleWrapper'; 7 | 8 | const RegisterGetLogHandler = (): React.ReactElement => 9 | ApiWithoutInput({ 10 | name: 'registerGetLogHandler', 11 | title: 'Register Get Log Handler', 12 | onClick: async (setResult) => { 13 | logs.registerGetLogHandler(() => { 14 | setResult('Success'); 15 | return 'App log string'; 16 | }); 17 | return generateRegistrationMsg('it is invoked to get the app log'); 18 | }, 19 | }); 20 | 21 | const LogsAPIs = (): ReactElement => ( 22 | 23 | 24 | 25 | ); 26 | 27 | export default LogsAPIs; 28 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/PagesCurrentAppAPIs.tsx: -------------------------------------------------------------------------------- 1 | import { pages } from '@microsoft/teams-js'; 2 | import React, { ReactElement } from 'react'; 3 | 4 | import { ApiWithoutInput, ApiWithTextInput } from './utils'; 5 | import { ModuleWrapper } from './utils/ModuleWrapper'; 6 | 7 | const NavigateTo = (): React.ReactElement => 8 | ApiWithTextInput({ 9 | name: 'navigateTo', 10 | title: 'Navigate To', 11 | onClick: { 12 | validateInput: (input) => { 13 | if (!input.pageId) { 14 | throw 'PageID are required.'; 15 | } 16 | }, 17 | submit: async (input) => { 18 | await pages.currentApp.navigateTo(input); 19 | return 'Completed'; 20 | }, 21 | }, 22 | defaultInput: JSON.stringify({ pageId: 'page1' }), 23 | }); 24 | 25 | const NavigateToDefaultPage = (): React.ReactElement => 26 | ApiWithoutInput({ 27 | name: 'navigateToDefaultPage', 28 | title: 'Navigate To Default Page', 29 | onClick: async () => { 30 | await pages.currentApp.navigateToDefaultPage(); 31 | return 'Completed'; 32 | }, 33 | }); 34 | 35 | const CheckPageCurrentAppCapability = (): React.ReactElement => 36 | ApiWithoutInput({ 37 | name: 'checkPageCurrentAppCapability', 38 | title: 'Check Page currentApp Call', 39 | onClick: async () => `Pages.currentApp module ${pages.currentApp.isSupported() ? 'is' : 'is not'} supported`, 40 | }); 41 | const PagesCurrentAppAPIs = (): ReactElement => ( 42 | 43 | 44 | 45 | 46 | 47 | ); 48 | 49 | export default PagesCurrentAppAPIs; 50 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/ProfileAPIs.tsx: -------------------------------------------------------------------------------- 1 | import { profile } from '@microsoft/teams-js'; 2 | import React, { ReactElement } from 'react'; 3 | 4 | import { ApiWithoutInput, ApiWithTextInput } from './utils'; 5 | import { ModuleWrapper } from './utils/ModuleWrapper'; 6 | 7 | const CheckProfileCapability = (): React.ReactElement => 8 | ApiWithoutInput({ 9 | name: 'checkCapabilityProfile', 10 | title: 'Check Profile Call', 11 | onClick: async () => `Profile module ${profile.isSupported() ? 'is' : 'is not'} supported`, 12 | }); 13 | 14 | const ShowProfile = (): React.ReactElement => 15 | ApiWithTextInput({ 16 | name: 'showProfile', 17 | title: 'Show Profile', 18 | defaultInput: 19 | '{"modality":"Card","persona":{"identifiers":{"Smtp":"test@microsoft.com"}},"targetElementBoundingRect":{"x":0,"y":0,"width":0,"height":0},"triggerType":"MouseClick"}', 20 | onClick: { 21 | validateInput: (input) => { 22 | if (!input) { 23 | throw 'ShowProfileRequest is required'; 24 | } 25 | }, 26 | submit: async (input) => { 27 | try { 28 | await profile.showProfile(input); 29 | } catch (e) { 30 | if (typeof e === 'object') { 31 | return JSON.stringify(e); 32 | } 33 | 34 | throw e; 35 | } 36 | 37 | return ''; 38 | }, 39 | }, 40 | }); 41 | 42 | const ProfileAPIs = (): ReactElement => ( 43 | 44 | 45 | 46 | 47 | ); 48 | 49 | export default ProfileAPIs; 50 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/SecondaryBrowserAPIs.tsx: -------------------------------------------------------------------------------- 1 | import { secondaryBrowser } from '@microsoft/teams-js'; 2 | import React, { ReactElement } from 'react'; 3 | 4 | import { ApiWithoutInput, ApiWithTextInput } from './utils'; 5 | import { ModuleWrapper } from './utils/ModuleWrapper'; 6 | 7 | const CheckSecondaryBrowserCapability = (): React.ReactElement => 8 | ApiWithoutInput({ 9 | name: 'CheckSecondaryBrowserCapability', 10 | title: 'Check SecondaryBrowser Capability', 11 | onClick: async () => `secondaryBrowser module ${secondaryBrowser.isSupported() ? 'is' : 'is not'} supported`, 12 | }); 13 | 14 | const Open = (): React.ReactElement => 15 | ApiWithTextInput({ 16 | name: 'secondaryBrowser_open', 17 | title: 'Open URL', 18 | onClick: { 19 | validateInput: (input) => { 20 | if (typeof input !== 'string') { 21 | throw new Error('Input should be a string'); 22 | } 23 | 24 | // validate that input should also be a valid URL 25 | new URL(input); 26 | }, 27 | submit: async (props) => { 28 | await secondaryBrowser.open(new URL(props)); 29 | return 'Completed'; 30 | }, 31 | }, 32 | defaultInput: '"https://www.bing.com"', 33 | }); 34 | 35 | const SecondaryBrowserAPIs = (): ReactElement => ( 36 | 37 | 38 | 39 | 40 | ); 41 | 42 | export default SecondaryBrowserAPIs; 43 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/StageViewSelfAPIs.tsx: -------------------------------------------------------------------------------- 1 | import { /*SdkError,*/ stageView } from '@microsoft/teams-js'; 2 | import React, { ReactElement } from 'react'; 3 | 4 | import { ApiWithoutInput /*, ApiWithTextInput*/ } from './utils'; 5 | import { ModuleWrapper } from './utils/ModuleWrapper'; 6 | 7 | const CheckStageViewSelfCapability = (): React.ReactElement => 8 | ApiWithoutInput({ 9 | name: 'checkStageViewSelfCapability', 10 | title: 'Check StageView Self Capability', 11 | onClick: async () => `StageView Self ${stageView.self.isSupported() ? 'is' : 'is not'} supported`, 12 | }); 13 | 14 | const CloseStageView = (): ReactElement => 15 | ApiWithoutInput({ 16 | name: 'stageViewSelfClose', 17 | title: 'StageView Self Close', 18 | onClick: async () => { 19 | await stageView.self.close(); 20 | return 'closed'; 21 | }, 22 | }); 23 | 24 | const StageViewSelfAPIs = (): ReactElement => ( 25 | 26 | 27 | 28 | 29 | ); 30 | 31 | export default StageViewSelfAPIs; 32 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/Version.tsx: -------------------------------------------------------------------------------- 1 | import { version } from '@microsoft/teams-js'; 2 | import React from 'react'; 3 | 4 | const Version = (): React.ReactElement => ( 5 |
6 | Current library version: {version ?? 'unavailable'} 7 |
8 | ); 9 | 10 | export default Version; 11 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/WebStorageAPIs.tsx: -------------------------------------------------------------------------------- 1 | import { webStorage } from '@microsoft/teams-js'; 2 | import React, { ReactElement } from 'react'; 3 | 4 | import { ApiWithoutInput } from './utils'; 5 | import { ModuleWrapper } from './utils/ModuleWrapper'; 6 | 7 | const CheckWebStorageCapability = (): React.ReactElement => 8 | ApiWithoutInput({ 9 | name: 'checkWebStorageCapability', 10 | title: 'Check Web Storage Capability', 11 | onClick: async () => `webStorage ${webStorage.isSupported() ? 'is' : 'is not'} supported`, 12 | }); 13 | 14 | const IsWebStorageClearedOnLogOut = (): React.ReactElement => 15 | ApiWithoutInput({ 16 | name: 'isWebStorageClearedOnUserLogOut', 17 | title: 'Is Web Storage Cleared on Log Out', 18 | onClick: async () => 19 | `webStorage ${(await webStorage.isWebStorageClearedOnUserLogOut()) ? 'is' : 'is not'} cleared on user log out`, 20 | }); 21 | 22 | const WebStorageAPIs = (): ReactElement => ( 23 | 24 | 25 | 26 | 27 | ); 28 | 29 | export default WebStorageAPIs; 30 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/privateApis/NotificationAPIs.tsx: -------------------------------------------------------------------------------- 1 | import { notifications, NotificationTypes, ShowNotificationParameters } from '@microsoft/teams-js'; 2 | import React, { ReactElement } from 'react'; 3 | 4 | import { ApiWithoutInput, ApiWithTextInput } from '../utils'; 5 | import { ModuleWrapper } from '../utils/ModuleWrapper'; 6 | 7 | const CheckNotificationCapability = (): React.ReactElement => 8 | ApiWithoutInput({ 9 | name: 'checkCapabilityNotifications', 10 | title: 'Check Capability Notifications', 11 | onClick: async () => `Notifications module ${notifications.isSupported() ? 'is' : 'is not'} supported`, 12 | }); 13 | 14 | const ShowNotification = (): React.ReactElement => 15 | ApiWithTextInput({ 16 | name: 'showNotification', 17 | title: 'Show Notification', 18 | onClick: { 19 | validateInput: (input) => { 20 | if (!input.message || !input.notificationType) { 21 | throw new Error('message and notificationType are required.'); 22 | } 23 | }, 24 | submit: async (input) => { 25 | notifications.showNotification(input); 26 | return 'Called'; 27 | }, 28 | }, 29 | defaultInput: JSON.stringify({ 30 | message: 'Test message', 31 | notificationType: NotificationTypes.fileDownloadStart, 32 | }), 33 | }); 34 | 35 | const NotificationAPIs = (): ReactElement => ( 36 | 37 | 38 | 39 | 40 | ); 41 | 42 | export default NotificationAPIs; 43 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/utils/ApiContainer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { PrettyPrintJson } from './PrettyPrintJson'; 4 | 5 | export interface ApiContainerProps { 6 | title: string; 7 | name: string; // system identifiable unique name in context of Teams Client and should contain no spaces 8 | result?: string; 9 | } 10 | 11 | export const ApiContainer = (props: React.PropsWithChildren): React.ReactElement => { 12 | const { children, name, result, title } = props; 13 | 14 | if (!name || !/^[a-zA-Z0-9._]+$/.test(name)) { 15 | throw new Error('name has to be set and it can only contain alphanumeric characters, dots and underscores.'); 16 | } 17 | 18 | return ( 19 |
30 | {title} 31 | {children} 32 |
40 | 41 | {result} 42 | 43 |
44 | 45 |
46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/utils/ModuleWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement } from 'react'; 2 | 3 | export interface ModuleWrapperProps { 4 | title: string; 5 | } 6 | 7 | export const ModuleWrapper = (props: React.PropsWithChildren): ReactElement => { 8 | const { children, title } = props; 9 | return ( 10 |
11 |

{title}

12 | {children} 13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/utils/PrettyPrintJson.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type PrettyPrintJsonProps = { 4 | result?: string; 5 | }; 6 | 7 | export const PrettyPrintJson = ({ result }: PrettyPrintJsonProps): JSX.Element => { 8 | const [formattedResult, setFormattedResult] = React.useState(result); 9 | 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | const parseJSON = (data: any): unknown => { 12 | try { 13 | return JSON.parse(data); 14 | } catch (e) { 15 | return data; 16 | } 17 | }; 18 | 19 | React.useEffect(() => { 20 | setFormattedResult(JSON.stringify(parseJSON(result), null, 2)); 21 | }, [result]); 22 | 23 | return ( 24 | <> 25 | Formatted Output: 26 |
32 |
42 |           {formattedResult}
43 |         
44 |
45 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ApiWithCheckboxInput'; 2 | export * from './ApiWithTextInput'; 3 | export * from './ApiWithoutInput'; 4 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/utils/isTestBackCompat.ts: -------------------------------------------------------------------------------- 1 | const urlParams = new URLSearchParams(window.location.search); 2 | 3 | export const isTestBackCompat = (): boolean => { 4 | return urlParams.get('testCallback') === 'true'; 5 | }; 6 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/components/utils/utils.css: -------------------------------------------------------------------------------- 1 | .apiWithTextInputHeader { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: flex-start; 5 | margin-bottom: 20px; 6 | padding: 0 5px; 7 | gap: 5px; 8 | } 9 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | 6 | import App from './App'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root'), 13 | ); 14 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/pages/SecondRoute.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement } from 'react'; 2 | 3 | import AppAPIs from '../components/AppAPIs'; 4 | 5 | export const SecondRoute = (): ReactElement => ( 6 |
7 | This is an additional route for testing purposes. 8 | 9 |
10 | ); 11 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/public/auth_end.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Ending auth

5 |
6 | 7 | 8 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/public/auth_start.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Starting auth

5 | 6 | 7 | 8 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /apps/teams-test-app/src/public/externalOauth_end.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 |

Starting mock external oauth2

9 | 39 | 40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /apps/teams-test-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.json", 3 | "compilerOptions": { 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "allowSyntheticDefaultImports": true, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "jsx": "react", 14 | "sourceMap": true 15 | }, 16 | "include": ["src"], 17 | "exclude": [ 18 | "**/build/*", 19 | "build", 20 | "node_modules", 21 | "**/*.test.*", 22 | "webpack.config.js", 23 | "webpack.cdn.config.js", 24 | "webpack.local.config.js" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /apps/teams-test-app/webpack.cdn.config.js: -------------------------------------------------------------------------------- 1 | /* eslint @typescript-eslint/no-var-requires: off*/ 2 | 3 | const path = require('path'); 4 | const commonConfig = require('./webpack.common.js'); 5 | const { merge } = require('webpack-merge'); 6 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 7 | 8 | module.exports = merge(commonConfig, { 9 | output: { 10 | path: path.resolve(__dirname, 'build'), 11 | filename: 'indexCDN.js', 12 | }, 13 | plugins: [new HtmlWebPackPlugin({ template: './index_cdn.html', filename: 'index.html' })], 14 | externals: { 15 | '@microsoft/teams-js': 'microsoftTeams', 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /apps/teams-test-app/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint @typescript-eslint/no-var-requires: off*/ 2 | 3 | const path = require('path'); 4 | const commonConfig = require('./webpack.common.js'); 5 | const { merge } = require('webpack-merge'); 6 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 7 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 8 | 9 | module.exports = merge(commonConfig, { 10 | output: { 11 | path: path.resolve(__dirname, 'build'), 12 | filename: 'indexBundle.js', 13 | }, 14 | plugins: [ 15 | new HtmlWebPackPlugin({ template: './index_bundle.html', filename: 'index.html' }), 16 | new CopyWebpackPlugin({ patterns: [{ from: './src/public' }] }), 17 | ], 18 | }); 19 | -------------------------------------------------------------------------------- /apps/teams-test-app/webpack.local.config.js: -------------------------------------------------------------------------------- 1 | /* eslint @typescript-eslint/no-var-requires: off*/ 2 | 3 | const path = require('path'); 4 | const commonConfig = require('./webpack.common.js'); 5 | const { merge } = require('webpack-merge'); 6 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 7 | 8 | module.exports = merge(commonConfig, { 9 | output: { 10 | path: path.resolve(__dirname, 'build'), 11 | filename: 'indexLocal.js', 12 | }, 13 | plugins: [new HtmlWebPackPlugin({ template: './index_local.html', filename: 'index.html' })], 14 | externals: { 15 | '@microsoft/teams-js': 'microsoftTeams', 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /apps/tree-shaking-test-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tree-shaking-test-app", 3 | "private": true, 4 | "author": "Noah", 5 | "description": "Test app to test the tree-shakability of TeamsJS", 6 | "version": "0.0.1", 7 | "main": "index.ts", 8 | "type": "module", 9 | "scripts": { 10 | "build-rollup": "pnpm clean && rollup --c", 11 | "build-webpack": "webpack", 12 | "clean": "rimraf ./dist" 13 | }, 14 | "dependencies": { 15 | "@microsoft/teams-js": "workspace:*" 16 | }, 17 | "devDependencies": { 18 | "@rollup/plugin-node-resolve": "^15.2.3", 19 | "@rollup/plugin-typescript": "^11.1.6", 20 | "@rollup/plugin-terser": "0.4.4", 21 | "rollup": "^4.24.4", 22 | "webpack": "^5.97.1", 23 | "webpack-cli": "^5.1.4" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/tree-shaking-test-app/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 2 | import terser from '@rollup/plugin-terser'; 3 | import typescript from '@rollup/plugin-typescript'; 4 | 5 | // rollup.config.mjs 6 | export default { 7 | input: 'src/index.ts', 8 | output: [ 9 | { 10 | file: 'dist/bundle.js', 11 | format: 'es', 12 | sourcemap: true, 13 | }, 14 | { 15 | file: 'dist/bundle.min.js', 16 | format: 'es', 17 | plugins: [terser()], 18 | sourcemap: true, 19 | }, 20 | ], 21 | plugins: [ 22 | nodeResolve({ 23 | extension: ['.js', '.ts', '.d.ts', '.json'], 24 | }), 25 | typescript(), 26 | ], 27 | treeshake: true, 28 | }; 29 | -------------------------------------------------------------------------------- /apps/tree-shaking-test-app/src/index.ts: -------------------------------------------------------------------------------- 1 | import { geoLocation } from '@microsoft/teams-js'; 2 | geoLocation.requestPermission(); 3 | geoLocation.map.isSupported(); 4 | -------------------------------------------------------------------------------- /apps/tree-shaking-test-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "allowSyntheticDefaultImports": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "module": "es6", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "jsx": "react", 14 | "sourceMap": true, 15 | "outDir": "./dist" 16 | }, 17 | "include": ["src"], 18 | "exclude": [ 19 | "**/build/*", 20 | "build", 21 | "node_modules", 22 | "**/*.test.*", 23 | "webpack.config.js", 24 | "webpack.cdn.config.js", 25 | "webpack.cdnV1.config.js", 26 | "webpack.local.config.js" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /apps/tree-shaking-test-app/webpack.config.cjs: -------------------------------------------------------------------------------- 1 | /* eslint @typescript-eslint/no-var-requires: off*/ 2 | /* eslint-disable no-undef */ 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | entry: './src/index.ts', 7 | output: { 8 | filename: 'index.js', 9 | path: path.resolve(__dirname, 'dist'), 10 | }, 11 | mode: 'development', 12 | optimization: { 13 | usedExports: true, 14 | innerGraph: true, 15 | sideEffects: false, 16 | }, 17 | devtool: false, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | use: 'ts-loader', 23 | exclude: /node_modules/, 24 | }, 25 | ], 26 | }, 27 | resolve: { 28 | extensions: ['.tsx', '.ts', '.js'], 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /apps/typed-dependency-tester/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ignorePatterns: ['.eslintrc.js'], 3 | parserOptions: { 4 | project: './tsconfig.json', 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/typed-dependency-tester/README.md: -------------------------------------------------------------------------------- 1 | # Typed Dependency Tester 2 | 3 | The Typed Dependency Tester is a tool used to detect if types in `MicrosoftTeams.d.ts` are importing types from other modules and giving them an implicit `any` resolution 4 | 5 | ## Getting Started 6 | 7 | ### Running the Dependency Tester 8 | 9 | If you would like to use this tester locally, simply type `pnpm build` in your terminal. The tester will then proceed to install the workspace version of the TeamsJS library. 10 | 11 | If you would like to test against a specific version of TeamsJS you will need to edit the `@microsoft/teams-js` version in the `package.json` file. 12 | 13 | If a `TS2304` type error occurs during the build, then the tester has successfully detected a build failure in the `MicrosoftTeams.d.ts` file. Please investigate the generated typed file using the outputted build failure information. 14 | 15 | ## Troubleshooting 16 | 17 | - If you see an error that is unrelated to types during the `pnpm build` check to make sure you have installed and built the `teams-js` package by running `pnpm i` and `pnpm build` from the repository root. 18 | -------------------------------------------------------------------------------- /apps/typed-dependency-tester/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typed-dependency-tester", 3 | "private": true, 4 | "author": "Microsoft Teams", 5 | "description": "A tester to check if types in the dependency array will cause errors", 6 | "version": "0.0.1", 7 | "scripts": { 8 | "build": "pnpm i && pnpm copy && cd ./types && pnpm tsc && cd ../ && pnpm clean", 9 | "copy": "mkdir types && cp -R ./node_modules/@microsoft/teams-js/dist/esm/packages/teams-js/dts/* ./types || xcopy .\\node_modules\\@microsoft\\teams-js\\dist\\esm\\packages\\teams-js\\dts\\* .\\types /Y /E", 10 | "clean": "rimraf node_modules && rimraf ./types" 11 | }, 12 | "dependencies": { 13 | "debug": "^4.3.3" 14 | }, 15 | "devDependencies": { 16 | "@types/debug": "^4.1.7", 17 | "@microsoft/teams-js": "workspace:*" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/typed-dependency-tester/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.json", 3 | "compilerOptions": { 4 | "lib": ["ES5", "DOM", "ES2015.promise"], 5 | "module": "es6", 6 | "moduleResolution": "node", 7 | "noResolve": false, 8 | "noImplicitAny": false, 9 | "noEmitOnError": false, 10 | "noImplicitReturns": true, 11 | "sourceMap": true, 12 | "skipLibCheck": false, 13 | "skipDefaultLibCheck": true 14 | }, 15 | "exclude": ["node_modules"] 16 | } 17 | -------------------------------------------------------------------------------- /apps/typed-dependency-tester/tsconfig.strictNullChecks.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /change/@microsoft-teams-js-06b44e75-5613-4594-a024-dfb68a5d7a05.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "patch", 3 | "comment": "Unblocked apps on Mobile to call `dialog.url.submit` from dialog by allowing this API from `FrameContext.content`.\nThere is a bug in Teams mobile that returns `frameContext.content in dialog instead of `frameContext.task`. Once the bug is fixed, this change will be reverted.", 4 | "packageName": "@microsoft/teams-js", 5 | "email": "lakhveerkaur@microsoft.com", 6 | "dependentChangeType": "patch" 7 | } 8 | -------------------------------------------------------------------------------- /change/@microsoft-teams-js-2d20cf0b-3ef5-4d6a-a6b0-b3110792b83f.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "none", 3 | "comment": "Released 2.37.0.", 4 | "packageName": "@microsoft/teams-js", 5 | "email": "31258166+juanscr@users.noreply.github.com", 6 | "dependentChangeType": "none" 7 | } 8 | -------------------------------------------------------------------------------- /change/@microsoft-teams-js-3a3fbb36-90e2-4d00-bb1e-7633a46f3f8c.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "patch", 3 | "comment": "Removed Beta tag from nestedAppAuth.isNAAChannelRecommended API", 4 | "packageName": "@microsoft/teams-js", 5 | "email": "baljesingh@microsoft.com", 6 | "dependentChangeType": "patch" 7 | } 8 | -------------------------------------------------------------------------------- /change/@microsoft-teams-js-96be3430-7bd4-4a40-8554-4be120a97747.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "minor", 3 | "comment": "Added `renderingSurface` property to `{app.Page.Context}` capability.", 4 | "packageName": "@microsoft/teams-js", 5 | "email": "niharikad@microsoft.com", 6 | "dependentChangeType": "patch" 7 | } 8 | -------------------------------------------------------------------------------- /change/@microsoft-teams-js-ade231c1-27a2-4670-b268-bdfc0daa5b0a.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "minor", 3 | "comment": "Added a new client version `2.1.2` to support isDeeplyNestedAuthSupported for Teams Mobile legacy code", 4 | "packageName": "@microsoft/teams-js", 5 | "email": "prpatwa@microsoft.com", 6 | "dependentChangeType": "patch" 7 | } 8 | -------------------------------------------------------------------------------- /enforceBeachball.js: -------------------------------------------------------------------------------- 1 | const cp = require('child_process'); 2 | 3 | cp.exec('pnpm changefile', { timeout: 30000 }, (err, stdout, stderr) => { 4 | if (!err && stdout.includes('No change files are needed')) { 5 | console.log('Beachball guidelines were correctly followed. Continuing...'); 6 | return; 7 | } else { 8 | throw new Error("Change files are required before merging. Please run 'pnpm changefile' from the monorepo root."); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /jest.config.common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '.(ts|tsx)': ['ts-jest', { tsconfig: { esModuleInterop: true, strictNullChecks: false, target: 'ES2015' } }], 4 | }, 5 | testRegex: '(/__tests__/.*|\\.(test|spec))\\.(ts|tsx)$', 6 | testEnvironment: 'jsdom', 7 | moduleFileExtensions: ['ts', 'tsx', 'js'], 8 | reporters: [ 9 | 'default', 10 | [ 11 | 'jest-junit', 12 | { 13 | outputDirectory: 'test-results/unit', 14 | outputName: 'unit-tests-report.xml', 15 | addFileAttribute: true, 16 | classNameTemplate: '{filepath}', 17 | }, 18 | ], 19 | ], 20 | clearMocks: true, 21 | }; 22 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "npmClient": "pnpm" 4 | } 5 | -------------------------------------------------------------------------------- /packages/teams-js/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ignorePatterns: ['.eslintrc.js'], 3 | parserOptions: { 4 | project: './tsconfig.eslint.json', 5 | }, 6 | plugins: ['recommend-no-namespaces', 'strict-null-checks'], 7 | rules: { 8 | '@typescript-eslint/interface-name-prefix': 'off', 9 | '@typescript-eslint/no-namespace': 'off', 10 | '@typescript-eslint/no-unused-vars': [ 11 | 'error', 12 | { argsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', varsIgnorePattern: '^_' }, 13 | ], 14 | 'no-inner-declarations': 'off', 15 | 'recommend-no-namespaces/recommend-no-namespaces': 'warn', 16 | 'strict-null-checks/all': 'warn', 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/teams-js/LICENSE: -------------------------------------------------------------------------------- 1 | Microsoft Teams JS Library 2 | 3 | Copyright (c) Microsoft Corporation 4 | All rights reserved. 5 | 6 | MIT License 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /packages/teams-js/eslint-rules/eslint-plugin-recommend-no-namespaces/index.js: -------------------------------------------------------------------------------- 1 | const recommendNoNamespacesRule = require('./recommendNoNamespaces.js'); 2 | 3 | const plugin = { rules: { 'recommend-no-namespaces': recommendNoNamespacesRule } }; 4 | module.exports = plugin; 5 | -------------------------------------------------------------------------------- /packages/teams-js/eslint-rules/eslint-plugin-recommend-no-namespaces/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-recommend-no-namespaces", 3 | "version": "0.1.0", 4 | "license": "MIT", 5 | "author": "Noah", 6 | "description": "Recommend against the use of namespaces as they cannot be treeshaken", 7 | "type": "commonjs", 8 | "keywords": [ 9 | "eslint", 10 | "eslint-plugin", 11 | "eslintplugin", 12 | "treeshaking", 13 | "namespaces" 14 | ], 15 | "peerDependencies": { 16 | "eslint": ">=5.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/teams-js/eslint-rules/eslint-plugin-recommend-no-namespaces/recommendNoNamespaces.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | meta: { 3 | type: 'suggestion', 4 | docs: { 5 | description: 'Recommend against the usage of namespaces as they are not treeshakable', 6 | category: 'Best Practices', 7 | recommended: true, 8 | }, 9 | fixable: false, 10 | schema: [], 11 | }, 12 | 13 | create: function (context) { 14 | return { 15 | TSModuleDeclaration: function (node) { 16 | if (node.id && node.kind === 'namespace') { 17 | context.report({ 18 | node, 19 | message: 20 | 'Please do not use namespaces as they cannot be treeshaken. Please use modules to separate code instead. If you have determined it ABSOLUTELY necessary to use a namespace, add "/* eslint-disable-next-line recommend-no-namespaces/recommend-no-namespaces */" to the line above to disable this warning, as well as a comment explaining why a namespace is necessary', 21 | severity: 2, 22 | data: node, 23 | }); 24 | } 25 | }, 26 | }; 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /packages/teams-js/jest-setup.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict-null-checks/all */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | 4 | /** 5 | * This while TextDecoder is supported in both browser and Node environments, it is not supported in jsdom, which we use for our jest environment. 6 | * To resolve this issue, we polyfill TextDecoder with the node implementation prior to running the tests. 7 | */ 8 | 9 | const TextDecoder = require('util').TextDecoder; 10 | global.TextDecoder = TextDecoder; 11 | -------------------------------------------------------------------------------- /packages/teams-js/jest.config.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const commonSettings = require('../../jest.config.common.js'); 3 | const packageVersion = require('./package.json').version; 4 | 5 | module.exports = { 6 | ...commonSettings, 7 | globals: { 8 | PACKAGE_VERSION: packageVersion, 9 | fetch: global.fetch, 10 | }, 11 | setupFilesAfterEnv: ['./test/setupTest.ts'], 12 | }; 13 | -------------------------------------------------------------------------------- /packages/teams-js/scripts/add-npmrc-npmtoken.ps1: -------------------------------------------------------------------------------- 1 | Set-Content -Path $Env:SYSTEM_DEFAULTWORKINGDIRECTORY/_OfficeDev.microsoft-teams-library-js/NPMFeed/.npmrc -Value "//registry.npmjs.org/:_authToken=$Env:NPM_TOKEN" 2 | -------------------------------------------------------------------------------- /packages/teams-js/scripts/view-published-packages-urls.ps1: -------------------------------------------------------------------------------- 1 | $version = Get-ChildItem -Path $Env:SYSTEM_DEFAULTWORKINGDIRECTORY/_OfficeDev.microsoft-teams-library-js/CDNFeed -Directory -Name 2 | Write-Host "Releasing version $version" 3 | Write-Host "CDN: https://res-sdf.cdn.office.net/teams-js/$version/js/MicrosoftTeams.min.js " 4 | Write-Host "NPM: https://www.npmjs.com/package/@microsoft/teams-js/v/$version" 5 | -------------------------------------------------------------------------------- /packages/teams-js/src/artifactsForCDN/validDomains.json: -------------------------------------------------------------------------------- 1 | { 2 | "validOrigins": [ 3 | "teams.microsoft.com", 4 | "teams.microsoft.us", 5 | "gov.teams.microsoft.us", 6 | "dod.teams.microsoft.us", 7 | "int.teams.microsoft.com", 8 | "outlook.office.com", 9 | "outlook-sdf.office.com", 10 | "outlook.office365.com", 11 | "outlook-sdf.office365.com", 12 | "outlook.live.com", 13 | "outlook-sdf.live.com", 14 | "teams.live.com", 15 | "local.teams.live.com", 16 | "local.teams.live.com:8080", 17 | "local.teams.office.com", 18 | "local.teams.office.com:8080", 19 | "devspaces.skype.com", 20 | "*.www.office.com", 21 | "www.office.com", 22 | "word.office.com", 23 | "excel.office.com", 24 | "powerpoint.office.com", 25 | "www.officeppe.com", 26 | "*.www.microsoft365.com", 27 | "www.microsoft365.com", 28 | "bing.com", 29 | "edgeservices.bing.com", 30 | "work.bing.com", 31 | "www.bing.com", 32 | "www.staging-bing-int.com", 33 | "*.cloud.microsoft", 34 | "*.m365.cloud.microsoft", 35 | "chatuxmanager.svc.cloud.microsoft", 36 | "copilot.microsoft.com", 37 | "windows.msn.com", 38 | "fa000000125.resources.office.net", 39 | "fa000000129.resources.office.net", 40 | "fa000000124.resources.office.net", 41 | "fa000000128.resources.office.net", 42 | "fa000000136.resources.office.net" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /packages/teams-js/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './private/index'; 2 | export * from './public/index'; 3 | -------------------------------------------------------------------------------- /packages/teams-js/src/internal/deepLinkConstants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * App install dialog constants 3 | */ 4 | export const teamsDeepLinkUrlPathForAppInstall = '/l/app/'; 5 | 6 | /** 7 | * Calendar constants 8 | */ 9 | export const teamsDeepLinkUrlPathForCalendar = '/l/meeting/new'; 10 | export const teamsDeepLinkAttendeesUrlParameterName = 'attendees'; 11 | export const teamsDeepLinkStartTimeUrlParameterName = 'startTime'; 12 | export const teamsDeepLinkEndTimeUrlParameterName = 'endTime'; 13 | export const teamsDeepLinkSubjectUrlParameterName = 'subject'; 14 | export const teamsDeepLinkContentUrlParameterName = 'content'; 15 | 16 | /** 17 | * Call constants 18 | */ 19 | export const teamsDeepLinkUrlPathForCall = '/l/call/0/0'; 20 | export const teamsDeepLinkSourceUrlParameterName = 'source'; 21 | export const teamsDeepLinkWithVideoUrlParameterName = 'withVideo'; 22 | 23 | /** 24 | * Chat constants 25 | */ 26 | export const teamsDeepLinkUrlPathForChat = '/l/chat/0/0'; 27 | export const teamsDeepLinkUsersUrlParameterName = 'users'; 28 | export const teamsDeepLinkTopicUrlParameterName = 'topicName'; 29 | export const teamsDeepLinkMessageUrlParameterName = 'message'; 30 | -------------------------------------------------------------------------------- /packages/teams-js/src/internal/emailAddressValidation.ts: -------------------------------------------------------------------------------- 1 | export function validateEmailAddress(emailString: string | null | undefined): void { 2 | const emailIsEmptyOrUndefined = emailString ? emailString.length <= 0 : true; 3 | const atIndex = emailString?.indexOf('@'); 4 | const periodIndexAfterAt = emailString?.indexOf('.', atIndex); 5 | 6 | if (emailIsEmptyOrUndefined || atIndex === -1 || periodIndexAfterAt === -1) { 7 | throw new Error('Input email address does not have the correct format.'); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/teams-js/src/internal/globalVars.ts: -------------------------------------------------------------------------------- 1 | import { FrameContexts } from '../public/constants'; 2 | import { UUID } from '../public/uuidObject'; 3 | export class GlobalVars { 4 | public static initializeCalled = false; 5 | public static initializeCompleted = false; 6 | public static additionalValidOrigins: string[] = []; 7 | public static initializePromise: Promise | undefined = undefined; 8 | public static isFramelessWindow = false; 9 | public static frameContext: FrameContexts | undefined = undefined; 10 | public static hostClientType: string | undefined = undefined; 11 | public static clientSupportedSDKVersion: string; 12 | public static printCapabilityEnabled = false; 13 | public static readonly teamsJsInstanceId: string = new UUID().toString(); 14 | } 15 | -------------------------------------------------------------------------------- /packages/teams-js/src/internal/interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @hidden 3 | * Hide from docs 4 | * Shim in definitions used for browser-compat 5 | * 6 | * @internal 7 | * Limited to Microsoft-internal use 8 | */ 9 | /* eslint-disable @typescript-eslint/no-explicit-any */ 10 | export interface DOMMessageEvent { 11 | origin?: any; 12 | source?: any; 13 | data?: any; 14 | ports?: any; 15 | // Needed for Chrome1964 16 | originalEvent: DOMMessageEvent; 17 | } 18 | 19 | /** 20 | * @hidden 21 | * Hide from docs 22 | * 23 | * @internal 24 | * Limited to Microsoft-internal use 25 | */ 26 | export interface TeamsNativeClient { 27 | framelessPostMessage(msg: string): void; 28 | } 29 | 30 | /** 31 | * @hidden 32 | * Hide from docs 33 | * 34 | * @internal 35 | * Limited to Microsoft-internal use 36 | */ 37 | export interface ExtendedWindow extends Window { 38 | nativeInterface: TeamsNativeClient; 39 | onNativeMessage(evt: DOMMessageEvent): void; 40 | } 41 | 42 | /** 43 | * @hidden 44 | * Meant for Message objects that are sent to children without id 45 | * 46 | * @internal 47 | * Limited to Microsoft-internal use 48 | */ 49 | export interface DOMMessageEvent { 50 | func: string; 51 | args?: any[]; 52 | } 53 | 54 | /** 55 | * @hidden 56 | * Meant for providing information related to certain callback context. 57 | * 58 | * @internal 59 | * Limited to Microsoft-internal use 60 | */ 61 | export interface CallbackInformation { 62 | name: string; 63 | calledAt: number; 64 | } 65 | -------------------------------------------------------------------------------- /packages/teams-js/src/internal/responseHandler.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is used for validating and deserializing the response from the host. 3 | * 4 | * @typeParam SerializedReturnValueFromHost The type of the response received from the host 5 | * @typeParam DeserializedReturnValueFromHost The type of the response after deserialization 6 | */ 7 | export abstract class ResponseHandler { 8 | /** 9 | * Checks if the response from the host is valid. 10 | * 11 | * @param response The response from the host to validate 12 | * 13 | * @returns True if the response is valid, false otherwise 14 | */ 15 | public abstract validate(response: SerializedReturnValueFromHost): boolean; 16 | 17 | /** 18 | * This function converts the response from the host into a different format 19 | * before returning it to the caller (if needed). 20 | * @param response 21 | */ 22 | public abstract deserialize(response: SerializedReturnValueFromHost): DeserializedReturnValueFromHost; 23 | } 24 | 25 | export type SimpleType = string | number | boolean | null | undefined | SimpleType[]; 26 | 27 | /** 28 | * This class is used for validating and deserializing boolean responses from the host. 29 | */ 30 | export class SimpleTypeResponseHandler extends ResponseHandler { 31 | public validate(_response: T): boolean { 32 | return true; 33 | } 34 | 35 | public deserialize(response: T): T { 36 | return response; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/teams-js/src/internal/typeCheckUtilities.ts: -------------------------------------------------------------------------------- 1 | export function isNullOrUndefined(value?: unknown): boolean { 2 | return value === null || value === undefined; 3 | } 4 | -------------------------------------------------------------------------------- /packages/teams-js/src/internal/visualMediaHelpers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @hidden 3 | * All properties common to Image and Video Props 4 | * 5 | * @beta 6 | */ 7 | export interface VisualMediaProps { 8 | /** 9 | * @hidden 10 | * The maximum number of media items that can be selected at once is limited to values that are less than or equal to the maximum visual media selection limit. 11 | */ 12 | maxVisualMediaCount: number; 13 | } 14 | 15 | export const maxVisualMediaSelectionLimit = 10; 16 | -------------------------------------------------------------------------------- /packages/teams-js/src/internal/webStorageHelpers.ts: -------------------------------------------------------------------------------- 1 | // It is safe to cache the host name because the host cannot change at runtime 2 | import * as app from '../public/app/app'; 3 | import { HostName } from '../public/constants'; 4 | 5 | export let cachedHostName: HostName | null = null; 6 | 7 | export async function getCachedHostName(): Promise { 8 | if (cachedHostName === null) { 9 | cachedHostName = (await app.getContext()).app.host.name; 10 | } 11 | 12 | return cachedHostName; 13 | } 14 | 15 | // ...except during unit tests, where we will change it at runtime regularly for testing purposes 16 | export function clearWebStorageCachedHostNameForTests(): void { 17 | cachedHostName = null; 18 | } 19 | -------------------------------------------------------------------------------- /packages/teams-js/src/private/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @hidden 3 | * Error codes that can be thrown from externalAppCommands and externalAppCardCommands Action Submit specifically 4 | * @internal 5 | * Limited to Microsoft-internal use 6 | */ 7 | export enum ExternalAppErrorCode { 8 | INTERNAL_ERROR = 'INTERNAL_ERROR', // Generic error 9 | } 10 | -------------------------------------------------------------------------------- /packages/teams-js/src/private/copilot/copilot.ts: -------------------------------------------------------------------------------- 1 | import * as customTelemetry from './customTelemetry'; 2 | import * as eligibility from './eligibility'; 3 | 4 | export { customTelemetry, eligibility }; 5 | -------------------------------------------------------------------------------- /packages/teams-js/src/private/externalAppErrorHandling.ts: -------------------------------------------------------------------------------- 1 | import { ExternalAppErrorCode } from './constants'; 2 | 3 | /** 4 | * @hidden 5 | * Error object that can be thrown from externalAppCommands, externalAppCardCommands and other external app APIs 6 | * @internal 7 | * Limited to Microsoft-internal use 8 | */ 9 | export interface ExternalAppError { 10 | errorCode: ExternalAppErrorCode; 11 | message?: string; 12 | } 13 | 14 | /** 15 | * @hidden 16 | * Determines if the provided error object is an instance of ExternalAppError 17 | * @internal 18 | * Limited to Microsoft-internal use 19 | * @param err The error object to check whether it is of ExternalAppError type 20 | */ 21 | export function isExternalAppError(err: unknown): err is ExternalAppError { 22 | if (typeof err !== 'object' || err === null) { 23 | return false; 24 | } 25 | 26 | const error = err as ExternalAppError; 27 | 28 | return ( 29 | Object.values(ExternalAppErrorCode).includes(error.errorCode) && 30 | (error.message === undefined || typeof error.message === 'string') 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /packages/teams-js/src/private/hostEntity/hostEntity.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @hidden 3 | * @internal 4 | * @beta 5 | * @module 6 | * Limited to Microsoft-internal use 7 | * 8 | * This capability allows an app to associate apps with a host entity, such as a Teams channel or chat, and configure them as needed. 9 | */ 10 | 11 | import { ensureInitialized } from '../../internal/internalAPIs'; 12 | import { runtime } from '../../public/runtime'; 13 | import * as tab from './tab'; 14 | 15 | export enum AppTypes { 16 | edu = 'EDU', 17 | /** 18 | * Enum to indicate apps should be filtered for base Townhall. 19 | */ 20 | baseTownhall = 'BASE_TOWNHALL', 21 | /** 22 | * Enum to indicate apps should be filtered for streaming Townhall. 23 | */ 24 | streamingTownhall = 'STREAMING_TOWNHALL', 25 | } 26 | 27 | /** 28 | * Id of the teams entity like channel, chat 29 | */ 30 | interface TeamsEntityId { 31 | threadId: string; 32 | } 33 | 34 | /** 35 | * Id of message in which channel meeting is created 36 | */ 37 | export interface TeamsChannelMeetingEntityIds extends TeamsEntityId { 38 | parentMessageId: string; 39 | } 40 | 41 | /** 42 | * Id of the host entity 43 | */ 44 | export type HostEntityIds = TeamsEntityId | TeamsChannelMeetingEntityIds; 45 | 46 | /** 47 | * @hidden 48 | * @internal 49 | * @beta 50 | * Limited to Microsoft-internal use 51 | * 52 | * Checks if the hostEntity capability is supported by the host 53 | * @returns boolean to represent whether the hostEntity capability is supported 54 | * 55 | * @throws Error if {@linkcode app.initialize} has not successfully completed 56 | */ 57 | export function isSupported(): boolean { 58 | return ensureInitialized(runtime) && runtime.supports.hostEntity ? true : false; 59 | } 60 | 61 | export { tab }; 62 | -------------------------------------------------------------------------------- /packages/teams-js/src/private/messageChannels/messageChannels.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @hidden 3 | * Module to request message ports from the host application. 4 | * 5 | * @beta 6 | * 7 | * @internal 8 | * Limited to Microsoft-internal use 9 | * 10 | * @module 11 | */ 12 | 13 | import { ensureInitialized } from '../../internal/internalAPIs'; 14 | import { runtime } from '../../public/runtime'; 15 | import * as dataLayer from './dataLayer'; 16 | import * as telemetry from './telemetry'; 17 | 18 | /** 19 | * @hidden 20 | * 21 | * @beta 22 | * 23 | * Checks if the messageChannels capability is supported by the host 24 | * @returns boolean to represent whether the messageChannels capability is supported 25 | * 26 | * @throws Error if {@linkcode app.initialize} has not successfully completed 27 | * 28 | * @internal 29 | * Limited to Microsoft-internal use 30 | */ 31 | export function isSupported(): boolean { 32 | return ensureInitialized(runtime) && runtime.supports.messageChannels ? true : false; 33 | } 34 | 35 | export { dataLayer, telemetry }; 36 | -------------------------------------------------------------------------------- /packages/teams-js/src/public/adaptiveCards.ts: -------------------------------------------------------------------------------- 1 | import { AdaptiveCardVersion } from './interfaces'; 2 | import { runtime } from './runtime'; 3 | 4 | /** 5 | * @returns The {@linkcode AdaptiveCardVersion} representing the Adaptive Card schema 6 | * version supported by the host, or undefined if the host does not support Adaptive Cards 7 | */ 8 | export function getAdaptiveCardSchemaVersion(): AdaptiveCardVersion | undefined { 9 | if (!runtime.hostVersionsInfo) { 10 | return undefined; 11 | } else { 12 | return runtime.hostVersionsInfo.adaptiveCardSchemaVersion; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/teams-js/src/public/appId.ts: -------------------------------------------------------------------------------- 1 | import { validateStringLength } from '../internal/idValidation'; 2 | import { ValidatedSafeString } from './validatedSafeString'; 3 | 4 | /** 5 | * A strongly-typed class used to represent a "valid" app id. 6 | * 7 | * Valid is a relative term, in this case. Truly valid app ids are UUIDs as documented in the schema: 8 | * https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema#id 9 | * However, there are some older internal/hard-coded apps which violate this schema and use names like 10 | * com.microsoft.teamspace.tab.youtube. For compatibility with these legacy apps, we unfortunately cannot 11 | * securely and completely validate app ids as UUIDs. Based on this, the validation is limited to checking 12 | * for script tags, length, and non-printable characters. Validation will be updated in the future to ensure 13 | * the app id is a valid UUID as legacy apps update. 14 | */ 15 | export class AppId extends ValidatedSafeString { 16 | /** 17 | * Creates a strongly-typed AppId from a string 18 | * 19 | * @param appIdAsString An app id represented as a string 20 | * @throws Error with a message describing the exact validation violation 21 | */ 22 | public constructor(appIdAsString: string) { 23 | super(appIdAsString); 24 | validateStringLength(appIdAsString); 25 | } 26 | 27 | /** 28 | * Returns a JSON representation of the AppId object 29 | * @returns A JSON representation of the AppId object 30 | * 31 | * note: this method maintains backward compatibility for JSON serialization 32 | */ 33 | public toJSON(): object { 34 | return { appIdAsString: this.toString() }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/teams-js/src/public/dialog/update.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Module to update the dialog 3 | * 4 | * @module 5 | */ 6 | 7 | import { dialogTelemetryVersionNumber, updateResizeHelper } from '../../internal/dialogHelpers'; 8 | import { ensureInitialized } from '../../internal/internalAPIs'; 9 | import { ApiName, getApiVersionTag } from '../../internal/telemetry'; 10 | import { DialogSize } from '../interfaces'; 11 | import { runtime } from '../runtime'; 12 | 13 | /** 14 | * Update dimensions - height/width of a dialog. 15 | * 16 | * @param dimensions - An object containing width and height properties. 17 | */ 18 | export function resize(dimensions: DialogSize): void { 19 | updateResizeHelper(getApiVersionTag(dialogTelemetryVersionNumber, ApiName.Dialog_Update_Resize), dimensions); 20 | } 21 | 22 | /** 23 | * Checks if dialog.update capability is supported by the host 24 | * @returns boolean to represent whether dialog.update capabilty is supported 25 | * 26 | * @throws Error if {@linkcode app.initialize} has not successfully completed 27 | */ 28 | export function isSupported(): boolean { 29 | return ensureInitialized(runtime) && runtime.supports.dialog 30 | ? runtime.supports.dialog.update 31 | ? true 32 | : false 33 | : false; 34 | } 35 | -------------------------------------------------------------------------------- /packages/teams-js/src/public/emailAddress.ts: -------------------------------------------------------------------------------- 1 | import { validateEmailAddress } from '../internal/emailAddressValidation'; 2 | 3 | /** 4 | * Represents a validated email. 5 | */ 6 | export class EmailAddress { 7 | /** Represents the input email address string */ 8 | private readonly val: string; 9 | 10 | public constructor(val: string) { 11 | this.val = val; 12 | validateEmailAddress(val); 13 | } 14 | 15 | /** 16 | * Retrieve the validated email address as a string. 17 | */ 18 | public toString(): string { 19 | return this.val; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/teams-js/src/public/serializable.interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Interface for objects that can be serialized and passed to the host 3 | */ 4 | export interface ISerializable { 5 | /** 6 | * @returns A serializable representation of the object, used for passing objects to the host. 7 | */ 8 | serialize(): string | object; 9 | } 10 | 11 | /** 12 | * @hidden 13 | * @internal 14 | * Used by the communication layer to make sure that an argument being passed to the host is serializable. 15 | * @param arg The argument to evaluate 16 | * @returns Whether or not the argument is serializable. 17 | */ 18 | export function isSerializable(arg: unknown): arg is ISerializable { 19 | return ( 20 | arg !== undefined && 21 | arg !== null && 22 | (arg as ISerializable).serialize !== undefined && 23 | typeof (arg as ISerializable).serialize === 'function' 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /packages/teams-js/src/public/validatedSafeString.ts: -------------------------------------------------------------------------------- 1 | import { validateSafeContent } from '../internal/idValidation'; 2 | import { ISerializable } from './serializable.interface'; 3 | 4 | /** 5 | * A strongly typed class used to represent a "valid" string id. 6 | */ 7 | export class ValidatedSafeString implements ISerializable { 8 | /** 9 | * Creates a strongly-typed Id from a string 10 | * 11 | * @param idAsString An id represented as a string 12 | * @throws Error with a message describing the exact validation violation 13 | */ 14 | public constructor(private readonly idAsString: string) { 15 | validateSafeContent(idAsString); 16 | } 17 | 18 | /** 19 | * @hidden 20 | * @internal 21 | * 22 | * @returns A serializable representation of an AppId, used for passing AppIds to the host. 23 | */ 24 | public serialize(): object | string { 25 | return this.toString(); 26 | } 27 | 28 | /** 29 | * Returns the app id as a string 30 | */ 31 | public toString(): string { 32 | return this.idAsString; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/teams-js/src/public/version.ts: -------------------------------------------------------------------------------- 1 | // This assignment is replaced at build time by a webpack plugin (or Jest during unit tests) which ensures the value matches the version set in the package version 2 | declare const PACKAGE_VERSION = 'ERROR: This value should be replaced by webpack!'; 3 | /** 4 | * @hidden 5 | * Package version. 6 | */ 7 | export const version = PACKAGE_VERSION; 8 | -------------------------------------------------------------------------------- /packages/teams-js/test/internal/emailAddressValidation.spec.ts: -------------------------------------------------------------------------------- 1 | import { validateEmailAddress } from '../../src/internal/emailAddressValidation'; 2 | 3 | describe('emailAddressValidation', () => { 4 | const invalidEmails = ['domain.com', 'name.domain@com', 'name@domain', '', null]; 5 | invalidEmails.forEach((invalidEmail) => { 6 | it('should throw errors for invalid email addresses', () => { 7 | expect(() => validateEmailAddress(invalidEmail)).toThrow('Input email address does not have the correct format.'); 8 | }); 9 | }); 10 | const validEmails = [ 11 | 'email@domain.com', 12 | 'firstname+lastname@domain.com', 13 | '123@domain.com', 14 | 'name@domain.subdomain.com', 15 | ]; 16 | validEmails.forEach((validEmail) => { 17 | it('should not throw errors for valid email addresses', () => { 18 | expect(() => validateEmailAddress(validEmail)).not.toThrow( 19 | 'Input email address does not have the correct format.', 20 | ); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/teams-js/test/internal/responseHandler.spec.ts: -------------------------------------------------------------------------------- 1 | import { SimpleTypeResponseHandler } from '../../src/internal/responseHandler'; 2 | 3 | describe('ResponseHandler', () => { 4 | describe('BooleanResponseHandler', () => { 5 | describe('validate', () => { 6 | let handler: SimpleTypeResponseHandler = new SimpleTypeResponseHandler(); 7 | 8 | beforeEach(() => { 9 | handler = new SimpleTypeResponseHandler(); 10 | }); 11 | test('should always return true', () => { 12 | const resultWhenTrue = handler.validate(true); 13 | const resultWhenFalse = handler.validate(false); 14 | 15 | expect(resultWhenTrue).toBe(true); 16 | expect(resultWhenFalse).toBe(true); 17 | }); 18 | }); 19 | describe('deserialize', () => { 20 | let handler: SimpleTypeResponseHandler = new SimpleTypeResponseHandler(); 21 | 22 | beforeEach(() => { 23 | handler = new SimpleTypeResponseHandler(); 24 | }); 25 | test('should return the response as is', () => { 26 | const resultWhenTrue = handler.deserialize(true); 27 | const resultWhenFalse = handler.deserialize(false); 28 | 29 | expect(resultWhenTrue).toBe(true); 30 | expect(resultWhenFalse).toBe(false); 31 | }); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/teams-js/test/promiseTester.ts: -------------------------------------------------------------------------------- 1 | export enum PromiseState { 2 | Pending = 'pending', 3 | Resolved = 'resolved', 4 | Rejected = 'rejected', 5 | } 6 | 7 | export async function getPromiseState(promiseInQuestion: Promise): Promise { 8 | const objectThatActsLikeAResolvedPromise = {}; 9 | try { 10 | const firstPromiseNotPending = await Promise.race([promiseInQuestion, objectThatActsLikeAResolvedPromise]); 11 | 12 | if (firstPromiseNotPending === objectThatActsLikeAResolvedPromise) { 13 | // Promise.race will return the first promise in the given iterable that has settled (either resolved or rejected). 14 | // If the promise it returned is objectThatActsLikeAResolvedPromise, then we know that the promiseInQuestion is still pending. 15 | return PromiseState.Pending; 16 | } else { 17 | return PromiseState.Resolved; 18 | } 19 | } catch (e) { 20 | // If the promiseInQuestion is rejected, then Promise.race will reject. 21 | return PromiseState.Rejected; 22 | } 23 | } 24 | 25 | export async function isPromiseStillPending(promiseInQuestion: Promise): Promise { 26 | return (await getPromiseState(promiseInQuestion)) === PromiseState.Pending; 27 | } 28 | -------------------------------------------------------------------------------- /packages/teams-js/test/public/emailAddress.spec.ts: -------------------------------------------------------------------------------- 1 | import { EmailAddress } from '../../src/public'; 2 | 3 | describe('emailAddress', () => { 4 | const invalidEmails = ['domain.com', 'name.domain@com', 'name@domain']; 5 | invalidEmails.forEach((invalidEmail) => { 6 | it('should throw errors for invalid email addresses', () => { 7 | expect(() => new EmailAddress(invalidEmail)).toThrowError( 8 | 'Input email address does not have the correct format.', 9 | ); 10 | }); 11 | }); 12 | const validEmails = [ 13 | 'email@domain.com', 14 | 'firstname+lastname@domain.com', 15 | '123@domain.com', 16 | 'name@domain.subdomain.com', 17 | ]; 18 | validEmails.forEach((validEmail) => { 19 | it('should not throw errors for valid email addresses', () => { 20 | const email = new EmailAddress(validEmail); 21 | expect(email.toString()).toBe(validEmail); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/teams-js/test/public/serializable.spec.ts: -------------------------------------------------------------------------------- 1 | // Since there are plenty of tests validating the individual validation functionality, these tests are intentionally not as 2 | 3 | import { isSerializable } from '../../src/public/serializable.interface'; 4 | 5 | // comprehensive as those. It executes a few basic tests and also validates that the error messages thrown are as expected. 6 | describe('isSerialiazable', () => { 7 | test('should return false if arg is undefined', () => { 8 | expect(isSerializable(undefined)).toBe(false); 9 | }); 10 | test('should return false if arg is null', () => { 11 | expect(isSerializable(null)).toBe(false); 12 | }); 13 | test('should return false if arg does not contain a member named serialize', () => { 14 | expect(isSerializable({ name: 'test' })).toBe(false); 15 | }); 16 | test('should return false if arg does not contain a function named serialize', () => { 17 | expect(isSerializable({ serialize: 'test' })).toBe(false); 18 | }); 19 | test('should return true if arg contains a function named serialize', () => { 20 | expect(isSerializable({ serialize: () => {} })).toBe(true); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/teams-js/test/public/version.spec.ts: -------------------------------------------------------------------------------- 1 | import { version } from '../../src/public/version'; 2 | 3 | // This is a regular expression that matches any valid semVer version number. It was sourced from here: 4 | // https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string 5 | const semVerRegularExpressionAsString = 6 | '^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$'; 7 | 8 | describe('Testing version constant', () => { 9 | it('Ensure PACKAGE_VERSION has been properly replaced by webpack (or Jest)', () => { 10 | expect(version).toMatch(new RegExp(semVerRegularExpressionAsString)); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/teams-js/test/setupTest.ts: -------------------------------------------------------------------------------- 1 | import { validOriginsFallback } from '../src/internal/constants'; 2 | 3 | /** 4 | * We currently run a fetch call to acquire CDN assets as soon as TeamsJS is loaded. 5 | * Since fetch is supported in both browser and Node environments, but not supported in jest/jsdom, 6 | * we polyfill fetch with a mock implementation that acquires the fallback domain list prior to running the tests. 7 | */ 8 | global.fetch = jest.fn(() => 9 | Promise.resolve({ 10 | status: 200, 11 | ok: true, 12 | json: async () => { 13 | return { validOrigins: validOriginsFallback }; 14 | }, 15 | } as Response), 16 | ); 17 | -------------------------------------------------------------------------------- /packages/teams-js/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "./webpack.config.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/teams-js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/esm", 5 | "declarationDir": "./dist/esm/packages/teams-js/dts", 6 | "lib": ["DOM", "ES2015", "ES5", "ES2021.String"], 7 | "module": "ES6", 8 | "moduleResolution": "node", 9 | "noResolve": false, 10 | "noImplicitAny": false, 11 | "noEmitOnError": false, 12 | "noImplicitReturns": true, 13 | "sourceMap": true, 14 | "declaration": true, 15 | "strictNullChecks": true, 16 | "resolveJsonModule": true 17 | }, 18 | "exclude": ["node_modules", "./test", "./webpack.config.ts"] 19 | } 20 | -------------------------------------------------------------------------------- /packages/teams-js/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://typedoc.org/schema.json", 3 | "entryPoints": ["./src/index.ts"], 4 | "exclude": ["**/node_modules/**", "./src/private/**"], 5 | "out": "docs", 6 | "treatWarningsAsErrors": true, 7 | "validation": { 8 | "notExported": true, 9 | "invalidLink": true, 10 | "notDocumented": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'apps/*' 3 | - 'packages/*' 4 | - 'tools/**/*' -------------------------------------------------------------------------------- /tools/bundle-size-tools/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ignorePatterns: ['.eslintrc.js'], 3 | parserOptions: { 4 | project: './tsconfig.strictNullChecks.json', 5 | }, 6 | plugins: ['strict-null-checks'], 7 | rules: { 8 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 9 | '@typescript-eslint/no-namespace': 'off', 10 | '@typescript-eslint/interface-name-prefix': 'off', 11 | 'no-inner-declarations': 'off', 12 | 'strict-null-checks/all': 'warn', 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/README.md: -------------------------------------------------------------------------------- 1 | # Bundle size tools 2 | 3 | This is a package which is responsible for comparing the bundle analysis of two commits. In a nutshell, this package is responsible for first finding and downloading the baseline commit's analysis in ADO And then comparing against the local bundle analysis thereby generating a summary result. This is a slightly modified version of the fluid framework package which contains specifically two changes on top of it. 4 | 5 | | No. | Change | AffectedFiles | 6 | | --- | ----------------------------------------------------------------------------- | -------------------------------------------------- | 7 | | 1. | Pass branch-name to find LCA/merge-base of it and git HEAD of current branch. | utilities\gitCommands.ts, ADO\AdoSizeComparator.ts | 8 | | 2. | Remove newlines from html markups while generating comment message | ADO\getCommentForBundleDiff.ts | 9 | 10 | ## Building package 11 | 12 | ``` 13 | pnpm build 14 | ``` 15 | 16 | Reference: 17 | bundle-size-tools : https://github.com/microsoft/FluidFramework/tree/main/tools/bundle-size-tools 18 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/bundle-analysis-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: '../../../.eslintrc.js', 3 | parserOptions: { 4 | project: './tsconfig.json', 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/bundle-analysis-app/README.md: -------------------------------------------------------------------------------- 1 | # Bundle analysis app 2 | 3 | This is an app which contains minimal code for just using the _teams-js_ package and is responsible for monitoring the size of it. It's configured with webpack to output zipped webpack stats which is being used for comparing the size of app across different commits/changes. This webpack stats contains the size of the bundle along with its dependencies. 4 | 5 | ## Generating bundle analysis 6 | 7 | ``` 8 | pnpm webpack:profile 9 | ``` 10 | 11 | ## Building project 12 | 13 | ``` 14 | pnpm build 15 | ``` 16 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/bundle-analysis-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bundle analysis app 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/bundle-analysis-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@microsoft/bundle-analysis-app", 3 | "version": "0.0.1", 4 | "description": "A test app to analyse the package bundles", 5 | "license": "UNLICENSED", 6 | "main": "./dist/index.js", 7 | "scripts": { 8 | "build": "pnpm lint && webpack", 9 | "clean": "rimraf ./dist ./bundleAnalysis", 10 | "lint": "pnpm eslint --max-warnings 0 --fix --ext .ts ./src", 11 | "webpack:profile": "webpack" 12 | }, 13 | "dependencies": { 14 | "@microsoft/teams-js": "workspace:*" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/bundle-analysis-app/src/index.ts: -------------------------------------------------------------------------------- 1 | import { app } from '@microsoft/teams-js'; 2 | 3 | app.initialize(); 4 | app.notifyAppLoaded(); 5 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/bundle-analysis-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.common.json", 3 | "compilerOptions": { 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "allowSyntheticDefaultImports": true, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "isolatedModules": false, 14 | "declaration": false, 15 | "noEmit": false 16 | }, 17 | "include": ["src", "src/**/*.ts"], 18 | "exclude": ["**/build/*", "build", "node_modules/**", "**/*.test.*", "webpack.config.js"] 19 | } 20 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/jest.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const commonSettings = require('../../jest.config.common.js'); 3 | // eslint-disable-next-line @typescript-eslint/no-var-requires 4 | const packageVersion = require('./package.json').version; 5 | 6 | module.exports = { 7 | ...commonSettings, 8 | globals: { 9 | PACKAGE_VERSION: packageVersion, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bundle-size-tools", 3 | "version": "0.0.6", 4 | "description": "Utility for analyzing bundle size regressions", 5 | "license": "MIT", 6 | "author": "Microsoft", 7 | "main": "./dist/index.js", 8 | "types": "dist/index.d.ts", 9 | "scripts": { 10 | "build": "pnpm clean && pnpm lint && pnpm tsc", 11 | "clean": "rimraf dist && rimraf tsconfig.tsbuildinfo", 12 | "lint": "pnpm eslint ./src ./test --max-warnings 0 --fix --ext .ts", 13 | "test": "jest" 14 | }, 15 | "dependencies": { 16 | "azure-devops-node-api": "^12.0.0", 17 | "jszip": "^3.10.1", 18 | "msgpack-lite": "^0.1.26", 19 | "pako": "^2.0.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/ADO/Constants.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | export interface IADOConstants { 7 | // URL for the ADO org 8 | orgUrl: string; 9 | 10 | // The ADO project that contains the repo 11 | projectName: string; 12 | 13 | // The ID for the build that runs against main when PRs are merged 14 | ciBuildDefinitionId: number; 15 | 16 | // The ID for the build that runs to validate PRs 17 | // Used to update tagged PRs on CI build completion 18 | // Note: Assumes CI and PR builds both run in the same org/project 19 | prBuildDefinitionId?: number; 20 | 21 | // The name of the build artifact that contains the bundle size artifacts 22 | bundleAnalysisArtifactName: string; 23 | 24 | // The guid of the repo 25 | // Used to post/update comments in ADO 26 | projectRepoGuid?: string; 27 | 28 | // The number of most recent ADO builds to pull when searching for one associated 29 | // with a specific commit, default 20. Pulling more builds takes longer, but may 30 | // be useful when there are a high volume of commits/builds. 31 | buildsToSearch?: number; 32 | } 33 | 34 | // The name of the metric that represents the size of the whole bundle 35 | export const totalSizeMetricName = 'Total Size'; 36 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/ADO/DefaultStatsProcessors.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | import { getBundleBuddyConfigProcessor, getEntryStatsProcessor, getTotalSizeStatsProcessor } from '../statsProcessors'; 7 | import { totalSizeMetricName } from './Constants'; 8 | 9 | /** 10 | * The set of stats file processors we will run on bundles 11 | */ 12 | export const DefaultStatsProcessors = [ 13 | getBundleBuddyConfigProcessor({ 14 | metricNameProvider: (chunk) => `${chunk.name}.js `, 15 | }), 16 | getEntryStatsProcessor({ metricNameProvider: (chunkName) => `${chunkName}.js` }), 17 | getTotalSizeStatsProcessor({ metricName: totalSizeMetricName }), 18 | ]; 19 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/ADO/FileSystemBundleFileProvider.ts: -------------------------------------------------------------------------------- 1 | import { promises as fsPromises } from 'fs'; 2 | 3 | import { BundleBuddyConfig, WebpackStatsJson } from '../BundleBuddyTypes'; 4 | import { decompressStatsFile, getAllFilesInDirectory } from '../utilities'; 5 | import { BundleFileData, getBundleFilePathsFromFolder } from './getBundleFilePathsFromFolder'; 6 | 7 | /*! 8 | * Copyright (c) Microsoft Corporation. All rights reserved. 9 | * Licensed under the MIT License. 10 | */ 11 | 12 | /** 13 | * Returns a list of all the files relevant to bundle buddy from the given folder 14 | * @param bundleReportPath - The path to the folder containing the bundle report 15 | */ 16 | export async function getBundlePathsFromFileSystem(bundleReportPath: string): Promise { 17 | const filePaths = await getAllFilesInDirectory(bundleReportPath); 18 | 19 | return getBundleFilePathsFromFolder(filePaths); 20 | } 21 | 22 | /** 23 | * Gets and parses a BundleBuddyConfig file from the filesystem 24 | * @param path - the full path to the file in the filesystem 25 | */ 26 | export async function getBundleBuddyConfigFromFileSystem(path: string): Promise { 27 | const file = await fsPromises.readFile(path); 28 | 29 | return JSON.parse(file.toString()); 30 | } 31 | 32 | /** 33 | * Gets a decompressed webpack stats file from the filesystem 34 | * @param path - the full path to the file in the filesystem 35 | */ 36 | export async function getStatsFileFromFileSystem(path: string): Promise { 37 | const file = await fsPromises.readFile(path); 38 | 39 | return decompressStatsFile(file); 40 | } 41 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/ADO/getAzureDevopsApi.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | import { getPersonalAccessTokenHandler, WebApi } from 'azure-devops-node-api'; 7 | 8 | export function getAzureDevopsApi(accessToken: string, orgUrl: string): WebApi { 9 | const authHandler = getPersonalAccessTokenHandler(accessToken); 10 | return new WebApi(orgUrl, authHandler); 11 | } 12 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/ADO/getBuildTagForCommit.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | /** 7 | * Returns the git tag to use to mark that a build is waiting for the baseline to be available for a commit hash. 8 | */ 9 | export function getBuildTagForCommit(commitHash: string): string { 10 | return `bundle-size-tools-pending-${commitHash}`; 11 | } 12 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/ADO/getBundleBuddyConfigMap.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | import { BundleBuddyConfig } from '../BundleBuddyTypes'; 7 | import { BundleFileData } from './getBundleFilePathsFromFolder'; 8 | 9 | export interface GetBundleBuddyConfigMapArgs { 10 | bundleFileData: BundleFileData[]; 11 | 12 | getBundleBuddyConfig: (relativePath: string) => Promise; 13 | } 14 | 15 | export async function getBundleBuddyConfigMap( 16 | args: GetBundleBuddyConfigMapArgs, 17 | ): Promise> { 18 | const result = new Map(); 19 | 20 | const asyncWork: Promise[] = []; 21 | args.bundleFileData.forEach((bundle) => { 22 | if (bundle.relativePathToConfigFile) { 23 | asyncWork.push( 24 | args.getBundleBuddyConfig(bundle.relativePathToConfigFile).then((configFile) => { 25 | result.set(bundle.bundleName, configFile); 26 | }), 27 | ); 28 | } 29 | }); 30 | 31 | await Promise.all(asyncWork); 32 | 33 | return result; 34 | } 35 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/ADO/getBundleSummaries.ts: -------------------------------------------------------------------------------- 1 | import { BundleBuddyConfig, BundleSummaries, WebpackStatsJson, WebpackStatsProcessor } from '../BundleBuddyTypes'; 2 | import { runProcessorsOnStatsFile } from '../utilities/runProcessorOnStatsFile'; 3 | import { BundleFileData } from './getBundleFilePathsFromFolder'; 4 | 5 | /*! 6 | * Copyright (c) Microsoft Corporation. All rights reserved. 7 | * Licensed under the MIT License. 8 | */ 9 | 10 | export interface GetBundleSummariesArgs { 11 | bundlePaths: BundleFileData[]; 12 | 13 | statsProcessors: WebpackStatsProcessor[]; 14 | 15 | getStatsFile: (relativePath: string) => Promise; 16 | 17 | getBundleBuddyConfigFile: ( 18 | bundleName: string, 19 | ) => Promise | (BundleBuddyConfig | undefined); 20 | } 21 | 22 | export async function getBundleSummaries(args: GetBundleSummariesArgs): Promise { 23 | const result: BundleSummaries = new Map(); 24 | 25 | const pendingAsyncWork = args.bundlePaths.map(async (bundle) => { 26 | const [statsFile, bundleBuddyConfig] = await Promise.all([ 27 | args.getStatsFile(bundle.relativePathToStatsFile), 28 | args.getBundleBuddyConfigFile(bundle.bundleName), 29 | ]); 30 | 31 | const bundleSummary = runProcessorsOnStatsFile( 32 | bundle.bundleName, 33 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 34 | statsFile!, // non-null assertion here needed to due TS bug. Stats file is never undefined here 35 | bundleBuddyConfig, 36 | args.statsProcessors, 37 | ); 38 | 39 | result.set(bundle.bundleName, bundleSummary); 40 | }); 41 | 42 | await Promise.all(pendingAsyncWork); 43 | 44 | return result; 45 | } 46 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/ADO/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | export * from './AdoArtifactFileProvider'; 7 | export * from './AdoSizeComparator'; 8 | export * from './Constants'; 9 | export * from './DefaultStatsProcessors'; 10 | export * from './FileSystemBundleFileProvider'; 11 | export * from './getAzureDevopsApi'; 12 | export * from './getBuildTagForCommit'; 13 | export * from './getBundleBuddyConfigMap'; 14 | export * from './getBundleFilePathsFromFolder'; 15 | export * from './getBundleSummaries'; 16 | export * from './getCommentForBundleDiff'; 17 | export * from './PrCommentsUtils'; 18 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | export * from './ADO'; 7 | export * from './BundleBuddyTypes'; 8 | export * from './compareBundles'; 9 | export * from './statsProcessors'; 10 | export * from './utilities'; 11 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/statsProcessors/bundleBuddyConfigProcessor.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | import { BundleMetric, ChunkToAnalyze, WebpackStatsProcessor } from '../BundleBuddyTypes'; 7 | import { getChunkAndDependencySizes } from '../utilities'; 8 | 9 | export interface BundleBuddyConfigProcessorOptions { 10 | // Custom callback to customize what text will be used as the metric name 11 | metricNameProvider?: (chunk: ChunkToAnalyze) => string; 12 | } 13 | 14 | /** 15 | * A stats processor that takes in a bundle specific configuration object for use in bundle analysis 16 | */ 17 | export function getBundleBuddyConfigProcessor(options: BundleBuddyConfigProcessorOptions): WebpackStatsProcessor { 18 | return (stats, bundleBuddyConfig) => { 19 | // This processor requires a config file to run, so return no metrics if no config file is given 20 | if (!bundleBuddyConfig) { 21 | return undefined; 22 | } 23 | 24 | const result = new Map(); 25 | 26 | bundleBuddyConfig.chunksToAnalyze.forEach((chunk) => { 27 | const chunkAnalysis = getChunkAndDependencySizes(stats, chunk.name); 28 | 29 | // Right now we log the size of the chunk plus all it's dependencies. We could support logging just the chunk via a configuration 30 | const parsedSize = chunkAnalysis.dependencies.reduce((prev, current) => prev + current.size, chunkAnalysis.size); 31 | 32 | const metricName = options.metricNameProvider ? options.metricNameProvider(chunk) : chunk.name; 33 | result.set(metricName, { 34 | parsedSize, 35 | }); 36 | }); 37 | 38 | return result; 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/statsProcessors/entryStatsProcessor.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | import { BundleMetric, WebpackStatsProcessor } from '../BundleBuddyTypes'; 7 | import { getChunkParsedSize } from '../utilities'; 8 | 9 | export interface EntryStatsProcessorOptions { 10 | // Custom callback to customize what text will be used as the metric name 11 | metricNameProvider?: (chunkName: string) => string; 12 | } 13 | 14 | /** 15 | * A simple stats processor that simply returns the size information for the entry chunk 16 | */ 17 | export function getEntryStatsProcessor(options: EntryStatsProcessorOptions): WebpackStatsProcessor { 18 | return (stats) => { 19 | const result = new Map(); 20 | 21 | if (!stats.entrypoints) { 22 | return result; 23 | } 24 | 25 | Object.entries(stats.entrypoints).forEach((value) => { 26 | const [chunkName, chunkGroupStats] = value; 27 | const metricName = options.metricNameProvider ? options.metricNameProvider(chunkName) : chunkName; 28 | result.set(metricName, { parsedSize: getChunkParsedSize(stats, chunkGroupStats.chunks[0]) }); 29 | }); 30 | 31 | return result; 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/statsProcessors/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | export * from './bundleBuddyConfigProcessor'; 7 | export * from './entryStatsProcessor'; 8 | export * from './totalSizeStatsProcessor'; 9 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/statsProcessors/totalSizeStatsProcessor.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | import { BundleMetric, WebpackStatsProcessor } from '../BundleBuddyTypes'; 7 | 8 | export interface TotalSizeStatsProcessorOptions { 9 | // The total stats processor reports a single metric for the total size of the bundle, this is the string that should be used for that metric 10 | metricName: string; 11 | } 12 | 13 | /** 14 | * A simple stats processor that simply returns the size information for the entry chunk 15 | */ 16 | export function getTotalSizeStatsProcessor(options: TotalSizeStatsProcessorOptions): WebpackStatsProcessor { 17 | return (stats) => { 18 | const result = new Map(); 19 | 20 | if (!stats.assets) { 21 | return result; 22 | } 23 | 24 | const totalSize = stats.assets.reduce((prev, current) => { 25 | // Assets contains many file types, including source maps, we only care abut js files 26 | if (current.name.endsWith('.js')) { 27 | return prev + current.size; 28 | } 29 | return prev; 30 | }, 0); 31 | 32 | result.set(options.metricName, { parsedSize: totalSize }); 33 | 34 | return result; 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/utilities/decompressStatsFile.ts: -------------------------------------------------------------------------------- 1 | import { decode } from 'msgpack-lite'; 2 | import { inflate } from 'pako'; 3 | 4 | import { WebpackStatsJson } from '../BundleBuddyTypes'; 5 | 6 | /*! 7 | * Copyright (c) Microsoft Corporation. All rights reserved. 8 | * Licensed under the MIT License. 9 | */ 10 | 11 | /** 12 | * To save storage space, we store stats files as gzipped mspack files. This method takes 13 | * in a compressed file path and outputs the webpack stats object. 14 | */ 15 | export function decompressStatsFile(buffer: Buffer): WebpackStatsJson { 16 | // Inflate the gzipped data to get the mspack data 17 | const mspackData = inflate(buffer); 18 | 19 | return decode(mspackData); 20 | } 21 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/utilities/getAllFilesInDirectory.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | import { promises as fsPromises } from 'fs'; 7 | import { join } from 'path'; 8 | /** 9 | * Gets the relative path of all files in this directory 10 | * @param sourceFolder - The path of the directory to scan 11 | * @param partialPathPrefix - The partial path built up as we recurse through directories. External callers probably don't want to set this. 12 | */ 13 | export async function getAllFilesInDirectory(sourceFolder: string, partialPathPrefix = ''): Promise { 14 | const result: string[] = []; 15 | for (const file of await fsPromises.readdir(sourceFolder)) { 16 | const fullPath = join(sourceFolder, file); 17 | if ((await fsPromises.stat(fullPath)).isFile()) { 18 | result.push(join(partialPathPrefix, file)); 19 | } else { 20 | result.push(...(await getAllFilesInDirectory(join(sourceFolder, file), join(partialPathPrefix, file)))); 21 | } 22 | } 23 | return result; 24 | } 25 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/utilities/getBuilds.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | import { WebApi } from 'azure-devops-node-api'; 7 | 8 | export interface GetBuildOptions { 9 | // The ADO project name 10 | project: string; 11 | 12 | // An array of ADO definitions that should be considered for this query 13 | definitions: number[]; 14 | 15 | // An optional set of tags that should be on the returned builds 16 | tagFilters?: string[]; 17 | 18 | // An upper limit on the number of queries to return. Can be used to improve performance 19 | maxBuildsPerDefinition?: number; 20 | } 21 | 22 | /** 23 | * A wrapper around the terrible API signature for ADO getBuilds 24 | */ 25 | // The type that getBuilds returns doesn't seem to be exported from the library we're using, so using 26 | // any and disabling warning for now 27 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 28 | export async function getBuilds(adoConnection: WebApi, options: GetBuildOptions): Promise { 29 | const buildApi = await adoConnection.getBuildApi(); 30 | 31 | return buildApi.getBuilds( 32 | options.project, 33 | options.definitions, 34 | undefined, 35 | undefined, 36 | undefined, 37 | undefined, 38 | undefined, 39 | undefined, 40 | undefined, 41 | undefined, 42 | options.tagFilters, 43 | undefined, 44 | undefined, 45 | undefined, 46 | options.maxBuildsPerDefinition, 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/utilities/getChunkParsedSize.ts: -------------------------------------------------------------------------------- 1 | import { WebpackStatsJson } from '../BundleBuddyTypes'; 2 | 3 | /*! 4 | * Copyright (c) Microsoft Corporation. All rights reserved. 5 | * Licensed under the MIT License. 6 | */ 7 | 8 | /** 9 | * This gets the size of a chunk after minification, which is what the browser will parse. 10 | */ 11 | export function getChunkParsedSize(stats: WebpackStatsJson, chunkId: string | number): number { 12 | if (stats.assets === undefined) { 13 | throw new Error("No assets property in the stats file, can't compute parsed sizes of chunks"); 14 | } 15 | 16 | const matchingAsset = stats.assets.find((asset) => { 17 | // Make sure to only look at js files and not source maps (assumes source maps don't end in .js) 18 | if (asset.name.endsWith('.js')) { 19 | // Assumes only a single chunk per asset, this may not hold for all apps. 20 | return asset.chunks[0] === chunkId; 21 | } 22 | 23 | return false; 24 | }); 25 | 26 | if (matchingAsset === undefined) { 27 | throw new Error(`Could not find asset for chunk with id '${chunkId}' in the webpack stats`); 28 | } 29 | 30 | return matchingAsset.size; 31 | } 32 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/utilities/getLastCommitHashFromPR.ts: -------------------------------------------------------------------------------- 1 | import { WebApi } from 'azure-devops-node-api'; 2 | 3 | /*! 4 | * Copyright (c) Microsoft Corporation. All rights reserved. 5 | * Licensed under the MIT License. 6 | */ 7 | 8 | /** 9 | * Fetches the last commit hash for a PR. 10 | */ 11 | export async function getLastCommitHashFromPR( 12 | adoConnection: WebApi, 13 | prId: number, 14 | repoGuid: string, 15 | ): Promise { 16 | const gitApi = await adoConnection.getGitApi(); 17 | const prCommits = await gitApi.getPullRequestCommits(repoGuid, prId); 18 | 19 | return prCommits[0].commitId; 20 | } 21 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/utilities/gitCommands.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | import { execFileSync } from 'child_process'; 7 | /** 8 | * Gets the commit in main that the current branch is based on. 9 | */ 10 | export function getBaselineCommit(baseBranch: string): string { 11 | if (!baseBranch || !baseBranch.trim()) { 12 | throw new Error(`Invalid input passed to getBaselineCommit: "${baseBranch}"`); 13 | } 14 | return execFileSync('git', ['merge-base', `origin/${baseBranch}`, 'HEAD']) 15 | .toString() 16 | .trim(); 17 | } 18 | 19 | export function getPriorCommit(baseCommit: string): string { 20 | if (!baseCommit || !baseCommit.trim()) { 21 | throw new Error(`Invalid input passed to getPriorCommit: "${baseCommit}"`); 22 | } 23 | return execFileSync('git', ['log', '--pretty=format:"%H"', '-1', `${baseCommit}~1`]) 24 | .toString() 25 | .trim(); 26 | } 27 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/utilities/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | export * from './decompressStatsFile'; 7 | export * from './getAllFilesInDirectory'; 8 | export * from './getBuilds'; 9 | export * from './getChunkAndDependenciesSizes'; 10 | export * from './getChunkParsedSize'; 11 | export * from './getLastCommitHashFromPR'; 12 | export * from './gitCommands'; 13 | export * from './unzipStream'; 14 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/utilities/runProcessorOnStatsFile.ts: -------------------------------------------------------------------------------- 1 | import { BundleBuddyConfig, BundleMetricSet, WebpackStatsJson, WebpackStatsProcessor } from '../BundleBuddyTypes'; 2 | 3 | /*! 4 | * Copyright (c) Microsoft Corporation. All rights reserved. 5 | * Licensed under the MIT License. 6 | */ 7 | 8 | /** 9 | * Runs a set of stats file processors in order on a given webpack stats file to produce metrics. 10 | * @param bundleName - A friendly name of the bundle being processed, used for error handling 11 | * @param stats - The webpack stats file being processed 12 | * @param config - An optional bundle specific configuration for specifying custom metrics 13 | * @param statsProcessors - The set of processors to run on this bundle 14 | */ 15 | export function runProcessorsOnStatsFile( 16 | bundleName: string, 17 | stats: WebpackStatsJson, 18 | config: BundleBuddyConfig | undefined, 19 | statsProcessors: WebpackStatsProcessor[], 20 | ): BundleMetricSet { 21 | const result: BundleMetricSet = new Map(); 22 | 23 | statsProcessors.forEach((processor) => { 24 | const localMetrics = processor(stats, config); 25 | 26 | if (localMetrics) { 27 | localMetrics.forEach((value, key) => { 28 | if (result.has(key)) { 29 | throw new Error( 30 | `Multiple stats processors tried to write a metric with the same name: ${key} for bundle: ${bundleName}`, 31 | ); 32 | } 33 | 34 | result.set(key, value); 35 | }); 36 | } 37 | }); 38 | 39 | return result; 40 | } 41 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/src/utilities/unzipStream.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | import * as JSZip from 'jszip'; 7 | 8 | function readStreamAsBuffer(stream: NodeJS.ReadableStream): Promise { 9 | return new Promise((resolve, reject) => { 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | const data: any[] = []; 12 | stream.on('data', (chunk) => { 13 | data.push(chunk); 14 | }); 15 | stream.on('close', () => { 16 | resolve(Buffer.concat(data)); 17 | }); 18 | stream.on('error', (error) => { 19 | reject(error); 20 | }); 21 | }); 22 | } 23 | 24 | // JSZip doesn't appear to export the JSZip type after a very quick scan 25 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 26 | export async function unzipStream(stream: NodeJS.ReadableStream): Promise { 27 | return JSZip.loadAsync(await readStreamAsBuffer(stream)); 28 | } 29 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "rootDir": "./src/", 6 | "module": "commonjs", 7 | "noImplicitAny": false, 8 | "types": ["webpack", "node", "jest"], 9 | "esModuleInterop": true, 10 | "typeRoots": ["types", "node_modules/@types", "../../node_modules/@types"] 11 | }, 12 | "include": ["src/**/*.ts", "../../node_modules/@types/webpack/index.d.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /tools/bundle-size-tools/tsconfig.strictNullChecks.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tools/cli/readChangelog.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | const readChangeLog = version => { 7 | const relativePathToChangelog = '../../packages/teams-js/CHANGELOG.md'; 8 | const absolutePathToChangelog = path.resolve(__dirname, relativePathToChangelog); 9 | if (!fs.existsSync(absolutePathToChangelog)) { 10 | throw `ERROR: ${absolutePathToChangelog} was not found.`; 11 | } 12 | const fullChangelog = fs.readFileSync(absolutePathToChangelog, 'utf8'); 13 | if (!version) { 14 | return fullChangelog; 15 | } else { 16 | const result = fullChangelog.split(/(## .*\d)/); 17 | const index = result.findIndex(substr => substr.startsWith(`## ${version}`)); 18 | if (index !== -1) { 19 | const log = result[index + 1]; 20 | return log; 21 | } 22 | throw new Error('Matching version in changelog was not found'); 23 | } 24 | }; 25 | 26 | (async () => { 27 | const args = process.argv.slice(2); 28 | const version = args[0]; 29 | try { 30 | const section = readChangeLog(version); 31 | console.log(section); 32 | } catch (e) { 33 | console.log('Something went wrong!'); 34 | console.error(e); 35 | process.exit(1); 36 | } 37 | })(); 38 | -------------------------------------------------------------------------------- /tools/yaml-templates/web-e2e-versions.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: AppHostingSdk 3 | default: none 4 | type: string 5 | - name: versionBranch 6 | default: 'Latest' 7 | type: string 8 | 9 | jobs: 10 | - template: web-e2e-tests-job.yml@self 11 | parameters: 12 | AppHostingSdk: AppHostingSdk 13 | hostingEnvironmentType: 'standardWeb' 14 | teamsJsReferenceType: 'npm' 15 | testPrefixPatternGroups: ['{[0-9],[A-D],[a-d]}', '{[E],[e]}', '{[F-M],[f-m]}', '{[N-Z],[n-z]}'] 16 | versionBranch: ${{parameters.versionBranch}} 17 | 18 | - template: web-e2e-tests-job.yml@self 19 | parameters: 20 | AppHostingSdk: AppHostingSdk 21 | hostingEnvironmentType: 'standardWeb' 22 | teamsJsReferenceType: 'scriptTag' 23 | testPrefixPatternGroups: ['{[0-9],[A-D],[a-d]}', '{[E],[e]}', '{[F-M],[f-m]}', '{[N-Z],[n-z]}'] 24 | versionBranch: ${{parameters.versionBranch}} 25 | 26 | - template: web-e2e-tests-job.yml@self 27 | parameters: 28 | AppHostingSdk: AppHostingSdk 29 | hostingEnvironmentType: 'electron' 30 | teamsJsReferenceType: 'npm' 31 | versionBranch: ${{parameters.versionBranch}} 32 | -------------------------------------------------------------------------------- /tsconfig.common.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "noResolve": false, 6 | "noImplicitAny": false, 7 | "noEmitOnError": false, 8 | "noImplicitReturns": true, 9 | "sourceMap": true, 10 | "skipLibCheck": true, 11 | "target": "ES2015" 12 | }, 13 | "exclude": ["node_modules"] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.strictNullChecks.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true 4 | } 5 | } 6 | --------------------------------------------------------------------------------