From 517c34a98140de110150ee8d2ea14232f3559250 Mon Sep 17 00:00:00 2001 From: Lakhveer Kaur Date: Mon, 26 Aug 2024 15:45:47 -0700 Subject: [PATCH 1/9] initial cecAuth changes --- .../ExternalAppAuthenticationCECAPIs.tsx | 102 +++++++ apps/teams-test-app/src/pages/TestApp.tsx | 2 + packages/teams-js/src/internal/telemetry.ts | 4 + .../externalAppAuthenticationForCEC.ts | 129 +++++++++ packages/teams-js/src/private/index.ts | 1 + packages/teams-js/src/private/interfaces.ts | 262 ++++++++++++++++++ packages/teams-js/src/public/runtime.ts | 1 + 7 files changed, 501 insertions(+) create mode 100644 apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx create mode 100644 packages/teams-js/src/private/externalAppAuthenticationForCEC.ts diff --git a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx new file mode 100644 index 0000000000..9a6e7f77dc --- /dev/null +++ b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx @@ -0,0 +1,102 @@ +import { AuthTokenRequestParameters, externalAppAuthenticationForCEC } from '@microsoft/teams-js'; +import React from 'react'; + +import { ApiWithoutInput } from '../utils/ApiWithoutInput'; +import { ApiWithTextInput } from '../utils/ApiWithTextInput'; +import { ModuleWrapper } from '../utils/ModuleWrapper'; + +const CheckExternalAppAuthenticationCapability = (): React.ReactElement => + ApiWithoutInput({ + name: 'checkExternalAppAuthenticationCapability', + title: 'Check External App Authentication Capability', + onClick: async () => + `External App Authentication module ${externalAppAuthenticationForCEC.isSupported() ? 'is' : 'is not'} supported`, + }); + +const AuthenticateWithOAuth = (): React.ReactElement => + ApiWithTextInput<{ + appId: string; + authenticateParameters: { + url: string; + width?: number; + height?: number; + isExternal?: boolean; + }; + }>({ + name: 'AuthenticateWithOAuth', + title: 'Authenticate With OAuth', + onClick: { + validateInput: (input) => { + if (!input.appId) { + throw new Error('appId is required'); + } + if (!input.authenticateParameters) { + throw new Error('authenticateParameters is required'); + } + }, + submit: async (input) => { + const oAuthcallback = () => { + console.log('callback received'); + }; + const result = await externalAppAuthenticationForCEC.authenticateWithOAuth( + input.appId, + { ...input.authenticateParameters, url: new URL(input.authenticateParameters.url) }, + oAuthcallback, + ); + return JSON.stringify(result); + }, + }, + defaultInput: JSON.stringify({ + appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a', + authenticateParameters: { + url: 'https://www.example.com', + width: 100, + height: 100, + isExternal: true, + }, + }), + }); + +const AuthenticateWithSSO = (): React.ReactElement => + ApiWithTextInput<{ + appId: string; + authTokenRequest: AuthTokenRequestParameters; + }>({ + name: 'authenticateWithSSO', + title: 'Authenticate With SSO', + onClick: { + validateInput: (input) => { + if (!input.appId) { + throw new Error('appId is required'); + } + if (!input.authTokenRequest) { + throw new Error('authTokenRequest is required'); + } + }, + submit: async (input, setResult) => { + const ssoCallback = () => { + console.log('callback received'); + setResult('callback received'); + }; + await externalAppAuthenticationForCEC.authenticateWithSSO(input.appId, input.authTokenRequest, ssoCallback); + return 'Completed'; + }, + }, + defaultInput: JSON.stringify({ + appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a', + authTokenRequest: { + claims: ['https://graph.microsoft.com'], + silent: true, + }, + }), + }); + +const ExternalAppAuthenticationForCECAPIs = (): React.ReactElement => ( + + + + + +); + +export default ExternalAppAuthenticationForCECAPIs; diff --git a/apps/teams-test-app/src/pages/TestApp.tsx b/apps/teams-test-app/src/pages/TestApp.tsx index 421c8faa8c..d9a5751e74 100644 --- a/apps/teams-test-app/src/pages/TestApp.tsx +++ b/apps/teams-test-app/src/pages/TestApp.tsx @@ -40,6 +40,7 @@ import PeopleAPIs from '../components/PeopleAPIs'; import ChatAPIs from '../components/privateApis/ChatAPIs'; import CopilotAPIs from '../components/privateApis/CopilotAPIs'; import ExternalAppAuthenticationAPIs from '../components/privateApis/ExternalAppAuthenticationAPIs'; +import ExternalAppAuthenticationForCECAPIs from '../components/privateApis/ExternalAppAuthenticationCECAPIs'; import ExternalAppCardActionsAPIs from '../components/privateApis/ExternalAppCardActionsAPIs'; import ExternalAppCommandsAPIs from '../components/privateApis/ExternalAppCommandsAPIs'; import FilesAPIs from '../components/privateApis/FilesAPIs'; @@ -93,6 +94,7 @@ export const TestApp: React.FC = () => { + diff --git a/packages/teams-js/src/internal/telemetry.ts b/packages/teams-js/src/internal/telemetry.ts index 87a49abde7..70ae823752 100644 --- a/packages/teams-js/src/internal/telemetry.ts +++ b/packages/teams-js/src/internal/telemetry.ts @@ -111,6 +111,10 @@ export const enum ApiName { ExternalAppAuthentication_AuthenticateWithSSOAndResendRequest = 'externalAppAuthentication.authenticateWithSSOAndResendRequest', ExternalAppAuthentication_AuthenticateWithOauth2 = 'externalAppAuthentication.authenticateWithOauth2', ExternalAppAuthentication_AuthenticateWithPowerPlatformConnectorPlugins = 'externalAppAuthentication.authenticateWithPowerPlatformConnectorPlugins', + ExternalAppAuthenticationForCEC_AuthenticateWithOAuth = 'externalAppAuthentication.cec.authenticateWithOAuth', + ExternalAppAuthenticationForCEC_AuthenticateWithSSO = 'externalAppAuthentication.cec.AuthenticateWithSSO', + ExternalAppAuthenticationForCEC_SSOAuthCompleted = 'externalAppAuthentication.cec.SSOAuthCompleted', + ExternalAppAuthenticationForCEC_OAuthCompleted = 'externalAppAuthentication.cec.OAuthCompleted', ExternalAppCardActions_ProcessActionOpenUrl = 'externalAppCardActions.processActionOpenUrl', ExternalAppCardActions_ProcessActionSubmit = 'externalAppCardActions.processActionSubmit', ExternalAppCommands_ProcessActionCommands = 'externalAppCommands.processActionCommand', diff --git a/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts b/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts new file mode 100644 index 0000000000..483819c2f0 --- /dev/null +++ b/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts @@ -0,0 +1,129 @@ +import { sendMessageToParentAsync } from '../internal/communication'; +import { registerHandler, removeHandler } from '../internal/handlers'; +import { ensureInitialized } from '../internal/internalAPIs'; +import { ApiName, ApiVersionNumber, getApiVersionTag } from '../internal/telemetry'; +import { validateId } from '../internal/utils'; +import { errorNotSupportedOnPlatform, FrameContexts } from '../public/constants'; +import { runtime } from '../public/runtime'; +import { AuthenticatePopUpParameters, AuthTokenRequestParameters, InvokeError } from './interfaces'; + +const externalAppAuthenticationTelemetryVersionNumber: ApiVersionNumber = ApiVersionNumber.V_3; + +// export namespace externalAppAuthenticationForCEC { +// export function authenticateWithSSO( +// appId: string, +// authTokenRequest: AuthTokenRequestParameters, +// SSOAuthCompletedCallback: () => void, +// ): void { +// ensureInitialized(runtime, FrameContexts.content); + +// if (!isSupported()) { +// throw errorNotSupportedOnPlatform; +// } +// validateId(appId, new Error('App id is not valid.')); +// registerHandler( +// getApiVersionTag(ApiVersionNumber.V_3, ApiName.ExternalAppAuthenticationForCEC_SSOAuthCompleted), +// 'ssoAuthCompleted', +// SSOAuthCompletedCallback, +// ); +// return sendMessageToParent( +// getApiVersionTag( +// externalAppAuthenticationTelemetryVersionNumber, +// ApiName.ExternalAppAuthenticationForCEC_AuthenticateWithSSO, +// ), +// 'externalAppAuthenticationForCEC.authenticateWithSSO', +// [appId, authTokenRequest.claims, authTokenRequest.silent], +// (wasSuccessful, error) => { +// if (!wasSuccessful) { +// console.log('not successfull ' + error); +// } else { +// console.log('successfull ' + error); +// } +// }, +// ); +// // .then(([wasSuccessful, error]: [boolean, InvokeError]) => { +// // if (!wasSuccessful) { +// // throw error; +// // } +// // }) +// // .finally(() => { +// // // removeHandler('ssoAuthCompleted'); +// // }); +// } + +export namespace externalAppAuthenticationForCEC { + export function authenticateWithSSO( + appId: string, + authTokenRequest: AuthTokenRequestParameters, + SSOAuthCompletedCallback: () => void, + ): Promise { + ensureInitialized(runtime, FrameContexts.content); + + if (!isSupported()) { + throw errorNotSupportedOnPlatform; + } + validateId(appId, new Error('App id is not valid.')); + registerHandler( + getApiVersionTag(ApiVersionNumber.V_3, ApiName.ExternalAppAuthenticationForCEC_SSOAuthCompleted), + 'ssoAuthCompleted', + SSOAuthCompletedCallback, + ); + return sendMessageToParentAsync( + getApiVersionTag( + externalAppAuthenticationTelemetryVersionNumber, + ApiName.ExternalAppAuthenticationForCEC_AuthenticateWithSSO, + ), + 'externalAppAuthenticationForCEC.authenticateWithSSO', + [appId, authTokenRequest.claims, authTokenRequest.silent], + ).then(([wasSuccessful, error]: [boolean, InvokeError]) => { + removeHandler('ssoAuthCompleted'); + if (!wasSuccessful) { + throw error; + } + }); + } + + export function authenticateWithOAuth( + appId: string, + authenticateParameters: AuthenticatePopUpParameters, + // callback that will be called when hubsdk is done with Authentication + OAuthCompletedCallback: () => void, + ): Promise { + ensureInitialized(runtime, FrameContexts.content); + + if (!isSupported()) { + throw errorNotSupportedOnPlatform; + } + validateId(appId, new Error('App id is not valid.')); + registerHandler( + getApiVersionTag(ApiVersionNumber.V_3, ApiName.ExternalAppAuthenticationForCEC_SSOAuthCompleted), + 'oAuthCompleted', + OAuthCompletedCallback, + ); + + // Ask the parent window to open an authentication window with the parameters provided by the caller. + return sendMessageToParentAsync( + getApiVersionTag( + externalAppAuthenticationTelemetryVersionNumber, + ApiName.ExternalAppAuthenticationForCEC_AuthenticateWithOAuth, + ), + 'externalAppAuthenticationForCEC.authenticateWithOAuth', + [ + appId, + authenticateParameters.url.href, + authenticateParameters.width, + authenticateParameters.height, + authenticateParameters.isExternal, + ], + ).then(([wasSuccessful, error]: [boolean, InvokeError]) => { + removeHandler('oAuthCompleted'); + if (!wasSuccessful) { + throw error; + } + }); + } + + export function isSupported(): boolean { + return ensureInitialized(runtime) && runtime.supports.externalAppAuthenticationForCEC ? true : false; + } +} diff --git a/packages/teams-js/src/private/index.ts b/packages/teams-js/src/private/index.ts index 178e407cee..3238bbef84 100644 --- a/packages/teams-js/src/private/index.ts +++ b/packages/teams-js/src/private/index.ts @@ -21,6 +21,7 @@ export { export { conversations } from './conversations'; export { copilot } from './copilot'; export { externalAppAuthentication } from './externalAppAuthentication'; +export { externalAppAuthenticationForCEC } from './externalAppAuthenticationForCEC'; export { externalAppCardActions } from './externalAppCardActions'; export { externalAppCommands } from './externalAppCommands'; export { files } from './files'; diff --git a/packages/teams-js/src/private/interfaces.ts b/packages/teams-js/src/private/interfaces.ts index 7f745e90c5..5c3d5c2072 100644 --- a/packages/teams-js/src/private/interfaces.ts +++ b/packages/teams-js/src/private/interfaces.ts @@ -271,3 +271,265 @@ export interface UserJoinedTeamsInformation { */ userJoinedTeams: TeamInformation[]; } + +/** + * @hidden + * Information about the bot request that should be resent by the host + * @internal + * Limited to Microsoft-internal use + */ +export type IOriginalRequestInfo = IQueryMessageExtensionRequest | IActionExecuteInvokeRequest; + +/** + * @hidden + * Parameters OauthWindow + * @internal + * Limited to Microsoft-internal use + */ +export type OauthWindowProperties = { + /** + * The preferred width for the pop-up. This value can be ignored if outside the acceptable bounds. + */ + width?: number; + /** + * The preferred height for the pop-up. This value can be ignored if outside the acceptable bounds. + */ + height?: number; + /** + * Some identity providers restrict their authentication pages from being displayed in embedded browsers (e.g., a web view inside of a native application) + * If the identity provider you are using prevents embedded browser usage, this flag should be set to `true` to enable the authentication page + * to be opened in an external browser. If this flag is `false`, the page will be opened directly within the current hosting application. + * + * This flag is ignored when the host for the application is a web app (as opposed to a native application) as the behavior is unnecessary in a web-only + * environment without an embedded browser. + */ + isExternal?: boolean; +}; +/** + * @hidden + * Parameters for the authentication pop-up. This interface is used exclusively with the externalAppAuthentication APIs + * @internal + * Limited to Microsoft-internal use + */ +export type AuthenticatePopUpParameters = { + /** + * The URL for the authentication pop-up. + */ + url: URL; + /** + * The preferred width for the pop-up. This value can be ignored if outside the acceptable bounds. + */ + width?: number; + /** + * The preferred height for the pop-up. This value can be ignored if outside the acceptable bounds. + */ + height?: number; + /** + * Some identity providers restrict their authentication pages from being displayed in embedded browsers (e.g., a web view inside of a native application) + * If the identity provider you are using prevents embedded browser usage, this flag should be set to `true` to enable the authentication page specified in + * the {@link url} property to be opened in an external browser. + * If this flag is `false`, the page will be opened directly within the current hosting application. + * + * This flag is ignored when the host for the application is a web app (as opposed to a native application) as the behavior is unnecessary in a web-only + * environment without an embedded browser. + */ + isExternal?: boolean; +}; + +/** + * @hidden + * Parameters for SSO authentication. This interface is used exclusively with the externalAppAuthentication APIs + * @internal + * Limited to Microsoft-internal use + */ +export type AuthTokenRequestParameters = { + /** + * An optional list of claims which to pass to Microsoft Entra when requesting the access token. + */ + claims?: string[]; + /** + * An optional flag indicating whether to attempt the token acquisition silently or allow a prompt to be shown. + */ + silent?: boolean; +}; + +/** + * @hidden + * Information about the message extension request that should be resent by the host. Corresponds to request schema in https://learn.microsoft.com/microsoftteams/platform/resources/messaging-extension-v3/search-extensions#receive-user-requests + * @internal + * Limited to Microsoft-internal use + */ +export interface IQueryMessageExtensionRequest { + requestType: OriginalRequestType.QueryMessageExtensionRequest; + commandId: string; + parameters: { + name: string; + value: string; + }[]; + queryOptions?: { + count: number; + skip: number; + }; +} + +/** + * @hidden + * Information about the Action.Execute request that should be resent by the host. Corresponds to schema in https://adaptivecards.io/explorer/Action.Execute.html + * @internal + * Limited to Microsoft-internal use + */ +export interface IActionExecuteInvokeRequest { + requestType: OriginalRequestType.ActionExecuteInvokeRequest; + type: string; // This must be "Action.Execute" + id: string; // The unique identifier associated with the action + verb: string; // The card author defined verb associated with the action + data: string | Record; +} + +/** + * @hidden + * Used to differentiate between IOriginalRequestInfo types + * @internal + * Limited to Microsoft-internal use + */ +export enum OriginalRequestType { + ActionExecuteInvokeRequest = 'ActionExecuteInvokeRequest', + QueryMessageExtensionRequest = 'QueryMessageExtensionRequest', +} +/*********** END REQUEST TYPE ************/ + +/*********** BEGIN RESPONSE TYPE ************/ +/** + * @hidden + * The response from the bot returned via the host + * @internal + * Limited to Microsoft-internal use + */ +export type IInvokeResponse = IQueryMessageExtensionResponse | IActionExecuteResponse; + +/** + * @hidden + * Used to differentiate between IInvokeResponse types + * @internal + * Limited to Microsoft-internal use + */ +export enum InvokeResponseType { + ActionExecuteInvokeResponse = 'ActionExecuteInvokeResponse', + QueryMessageExtensionResponse = 'QueryMessageExtensionResponse', +} + +/** + * @hidden + * The response from the bot returned via the host for a message extension query request. + * @internal + * Limited to Microsoft-internal use + */ +export interface IQueryMessageExtensionResponse { + responseType: InvokeResponseType.QueryMessageExtensionResponse; + composeExtension?: ComposeExtensionResponse; +} + +/** + * @hidden + * The response from the bot returned via the host for an Action.Execute request. + * @internal + * Limited to Microsoft-internal use + */ +export interface IActionExecuteResponse { + responseType: InvokeResponseType.ActionExecuteInvokeResponse; + value: Record; + signature?: string; + statusCode: number; + type: string; +} + +/** + * @hidden + * The compose extension response returned for a message extension query request. `suggestedActions` will be present only when the type is is 'config' or 'auth'. + * @internal + * Limited to Microsoft-internal use + */ +export type ComposeExtensionResponse = { + attachmentLayout: AttachmentLayout; + type: ComposeResultTypes; + attachments?: QueryMessageExtensionAttachment[]; + suggestedActions?: QueryMessageExtensionSuggestedActions; + text?: string; +}; + +/** + * @hidden + * + * @internal + * Limited to Microsoft-internal use + */ +export type QueryMessageExtensionSuggestedActions = { + actions: Action[]; +}; + +/** + * @hidden + * + * @internal + * Limited to Microsoft-internal use + */ +export type Action = { + type: string; + title: string; + value: string; +}; + +/** + * @hidden + * + * @internal + * Limited to Microsoft-internal use + */ +export type QueryMessageExtensionCard = { + contentType: string; + content: Record; + fallbackHtml?: string; + signature?: string; +}; + +/** + * @hidden + * + * @internal + * Limited to Microsoft-internal use + */ +export type QueryMessageExtensionAttachment = QueryMessageExtensionCard & { + preview?: QueryMessageExtensionCard; +}; + +/** + * @hidden + * + * @internal + * Limited to Microsoft-internal use + */ +export type AttachmentLayout = 'grid' | 'list'; +/** + * @hidden + * + * @internal + * Limited to Microsoft-internal use + */ +export type ComposeResultTypes = 'auth' | 'config' | 'message' | 'result' | 'silentAuth'; +/*********** END RESPONSE TYPE ************/ + +/*********** BEGIN ERROR TYPE ***********/ +export interface InvokeError { + errorCode: InvokeErrorCode; + message?: string; +} + +/** + * @hidden + * + * @internal + * Limited to Microsoft-internal use + */ +export enum InvokeErrorCode { + INTERNAL_ERROR = 'INTERNAL_ERROR', // Generic error +} diff --git a/packages/teams-js/src/public/runtime.ts b/packages/teams-js/src/public/runtime.ts index 946639d3e3..d51ec9aceb 100644 --- a/packages/teams-js/src/public/runtime.ts +++ b/packages/teams-js/src/public/runtime.ts @@ -242,6 +242,7 @@ interface IRuntimeV4 extends IBaseRuntime { readonly update?: {}; }; readonly externalAppAuthentication?: {}; + readonly externalAppAuthenticationForCEC?: {}; readonly externalAppCardActions?: {}; readonly externalAppCommands?: {}; readonly geoLocation?: { From 2181db7d51faf830603eb685e41f29c67d7b6070 Mon Sep 17 00:00:00 2001 From: Lakhveer Kaur Date: Thu, 5 Sep 2024 13:16:48 -0700 Subject: [PATCH 2/9] name fixes --- .../ExternalAppAuthenticationCECAPIs.tsx | 15 +- packages/teams-js/src/internal/handlers.ts | 1 + packages/teams-js/src/internal/telemetry.ts | 2 +- .../externalAppAuthenticationForCEC.ts | 139 ++++++++++++------ packages/teams-js/src/private/interfaces.ts | 9 ++ 5 files changed, 117 insertions(+), 49 deletions(-) diff --git a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx index 9a6e7f77dc..9b6b0c07c7 100644 --- a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx +++ b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx @@ -16,6 +16,7 @@ const CheckExternalAppAuthenticationCapability = (): React.ReactElement => const AuthenticateWithOAuth = (): React.ReactElement => ApiWithTextInput<{ appId: string; + conversationId: string; authenticateParameters: { url: string; width?: number; @@ -40,14 +41,16 @@ const AuthenticateWithOAuth = (): React.ReactElement => }; const result = await externalAppAuthenticationForCEC.authenticateWithOAuth( input.appId, + input.conversationId, { ...input.authenticateParameters, url: new URL(input.authenticateParameters.url) }, oAuthcallback, ); - return JSON.stringify(result); + return 'Completed'; }, }, defaultInput: JSON.stringify({ appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a', + conversationId: 'testConversationId', authenticateParameters: { url: 'https://www.example.com', width: 100, @@ -60,6 +63,7 @@ const AuthenticateWithOAuth = (): React.ReactElement => const AuthenticateWithSSO = (): React.ReactElement => ApiWithTextInput<{ appId: string; + conversationId: string; authTokenRequest: AuthTokenRequestParameters; }>({ name: 'authenticateWithSSO', @@ -78,12 +82,19 @@ const AuthenticateWithSSO = (): React.ReactElement => console.log('callback received'); setResult('callback received'); }; - await externalAppAuthenticationForCEC.authenticateWithSSO(input.appId, input.authTokenRequest, ssoCallback); + await externalAppAuthenticationForCEC.authenticateWithSSO( + input.appId, + input.conversationId, + input.authTokenRequest, + ssoCallback, + ); + console.log('completed'); return 'Completed'; }, }, defaultInput: JSON.stringify({ appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a', + conversationId: 'testConversationId', authTokenRequest: { claims: ['https://graph.microsoft.com'], silent: true, diff --git a/packages/teams-js/src/internal/handlers.ts b/packages/teams-js/src/internal/handlers.ts index e8e1d8935b..3affa66e7c 100644 --- a/packages/teams-js/src/internal/handlers.ts +++ b/packages/teams-js/src/internal/handlers.ts @@ -119,6 +119,7 @@ export function registerHandler( * Limited to Microsoft-internal use */ export function removeHandler(name: string): void { + console.log(name + ' handler is removed ' + JSON.stringify(HandlersPrivate.handlers)); delete HandlersPrivate.handlers[name]; } diff --git a/packages/teams-js/src/internal/telemetry.ts b/packages/teams-js/src/internal/telemetry.ts index 70ae823752..32bff22c69 100644 --- a/packages/teams-js/src/internal/telemetry.ts +++ b/packages/teams-js/src/internal/telemetry.ts @@ -111,7 +111,7 @@ export const enum ApiName { ExternalAppAuthentication_AuthenticateWithSSOAndResendRequest = 'externalAppAuthentication.authenticateWithSSOAndResendRequest', ExternalAppAuthentication_AuthenticateWithOauth2 = 'externalAppAuthentication.authenticateWithOauth2', ExternalAppAuthentication_AuthenticateWithPowerPlatformConnectorPlugins = 'externalAppAuthentication.authenticateWithPowerPlatformConnectorPlugins', - ExternalAppAuthenticationForCEC_AuthenticateWithOAuth = 'externalAppAuthentication.cec.authenticateWithOAuth', + ExternalAppAuthenticationForCEC_AuthenticateWithOauth = 'externalAppAuthentication.cec.authenticateWithOauth', ExternalAppAuthenticationForCEC_AuthenticateWithSSO = 'externalAppAuthentication.cec.AuthenticateWithSSO', ExternalAppAuthenticationForCEC_SSOAuthCompleted = 'externalAppAuthentication.cec.SSOAuthCompleted', ExternalAppAuthenticationForCEC_OAuthCompleted = 'externalAppAuthentication.cec.OAuthCompleted', diff --git a/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts b/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts index 483819c2f0..5f4a2f4a1c 100644 --- a/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts +++ b/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts @@ -5,55 +5,23 @@ import { ApiName, ApiVersionNumber, getApiVersionTag } from '../internal/telemet import { validateId } from '../internal/utils'; import { errorNotSupportedOnPlatform, FrameContexts } from '../public/constants'; import { runtime } from '../public/runtime'; -import { AuthenticatePopUpParameters, AuthTokenRequestParameters, InvokeError } from './interfaces'; +import { + ActionExecuteInvokeRequestType, + AuthenticatePopUpParameters, + AuthTokenRequestParameters, + IActionExecuteInvokeRequest, + IActionExecuteResponse, + InvokeError, + InvokeErrorCode, + InvokeErrorWrapper, +} from './interfaces'; const externalAppAuthenticationTelemetryVersionNumber: ApiVersionNumber = ApiVersionNumber.V_3; -// export namespace externalAppAuthenticationForCEC { -// export function authenticateWithSSO( -// appId: string, -// authTokenRequest: AuthTokenRequestParameters, -// SSOAuthCompletedCallback: () => void, -// ): void { -// ensureInitialized(runtime, FrameContexts.content); - -// if (!isSupported()) { -// throw errorNotSupportedOnPlatform; -// } -// validateId(appId, new Error('App id is not valid.')); -// registerHandler( -// getApiVersionTag(ApiVersionNumber.V_3, ApiName.ExternalAppAuthenticationForCEC_SSOAuthCompleted), -// 'ssoAuthCompleted', -// SSOAuthCompletedCallback, -// ); -// return sendMessageToParent( -// getApiVersionTag( -// externalAppAuthenticationTelemetryVersionNumber, -// ApiName.ExternalAppAuthenticationForCEC_AuthenticateWithSSO, -// ), -// 'externalAppAuthenticationForCEC.authenticateWithSSO', -// [appId, authTokenRequest.claims, authTokenRequest.silent], -// (wasSuccessful, error) => { -// if (!wasSuccessful) { -// console.log('not successfull ' + error); -// } else { -// console.log('successfull ' + error); -// } -// }, -// ); -// // .then(([wasSuccessful, error]: [boolean, InvokeError]) => { -// // if (!wasSuccessful) { -// // throw error; -// // } -// // }) -// // .finally(() => { -// // // removeHandler('ssoAuthCompleted'); -// // }); -// } - export namespace externalAppAuthenticationForCEC { export function authenticateWithSSO( appId: string, + conversationId: string, authTokenRequest: AuthTokenRequestParameters, SSOAuthCompletedCallback: () => void, ): Promise { @@ -74,7 +42,7 @@ export namespace externalAppAuthenticationForCEC { ApiName.ExternalAppAuthenticationForCEC_AuthenticateWithSSO, ), 'externalAppAuthenticationForCEC.authenticateWithSSO', - [appId, authTokenRequest.claims, authTokenRequest.silent], + [appId, conversationId, authTokenRequest.claims, authTokenRequest.silent], ).then(([wasSuccessful, error]: [boolean, InvokeError]) => { removeHandler('ssoAuthCompleted'); if (!wasSuccessful) { @@ -85,6 +53,7 @@ export namespace externalAppAuthenticationForCEC { export function authenticateWithOAuth( appId: string, + conversationId: string, authenticateParameters: AuthenticatePopUpParameters, // callback that will be called when hubsdk is done with Authentication OAuthCompletedCallback: () => void, @@ -105,11 +74,12 @@ export namespace externalAppAuthenticationForCEC { return sendMessageToParentAsync( getApiVersionTag( externalAppAuthenticationTelemetryVersionNumber, - ApiName.ExternalAppAuthenticationForCEC_AuthenticateWithOAuth, + ApiName.ExternalAppAuthenticationForCEC_AuthenticateWithOauth, ), - 'externalAppAuthenticationForCEC.authenticateWithOAuth', + 'externalAppAuthenticationForCEC.authenticateWithOauth', [ appId, + conversationId, authenticateParameters.url.href, authenticateParameters.width, authenticateParameters.height, @@ -123,7 +93,84 @@ export namespace externalAppAuthenticationForCEC { }); } + export function authenticateAndResendRequest( + appId: string, + authenticateParameters: AuthenticatePopUpParameters, + originalRequestInfo: IActionExecuteInvokeRequest, + ): Promise { + ensureInitialized(runtime, FrameContexts.content); + + if (!isSupported()) { + throw errorNotSupportedOnPlatform; + } + validateId(appId, new Error('App id is not valid.')); + validateOriginalRequestInfo(originalRequestInfo); + + // Ask the parent window to open an authentication window with the parameters provided by the caller. + return sendMessageToParentAsync<[boolean, IActionExecuteResponse | InvokeErrorWrapper]>( + getApiVersionTag( + externalAppAuthenticationTelemetryVersionNumber, + ApiName.ExternalAppAuthentication_AuthenticateAndResendRequest, + ), + 'externalAppAuthentication.authenticateAndResendRequest', + [ + appId, + originalRequestInfo, + authenticateParameters.url.href, + authenticateParameters.width, + authenticateParameters.height, + authenticateParameters.isExternal, + ], + ).then(([wasSuccessful, response]: [boolean, IActionExecuteResponse | InvokeErrorWrapper]) => { + if (wasSuccessful && response.responseType != null) { + return response as IActionExecuteResponse; + } else { + const error = response as InvokeError; + throw error; + } + }); + } + export function authenticateWithSSOAndResendRequest( + appId: string, + authTokenRequest: AuthTokenRequestParameters, + originalRequestInfo: IActionExecuteInvokeRequest, + ): Promise { + ensureInitialized(runtime, FrameContexts.content); + + if (!isSupported()) { + throw errorNotSupportedOnPlatform; + } + validateId(appId, new Error('App id is not valid.')); + + validateOriginalRequestInfo(originalRequestInfo); + + return sendMessageToParentAsync<[boolean, IActionExecuteResponse | InvokeErrorWrapper]>( + getApiVersionTag( + externalAppAuthenticationTelemetryVersionNumber, + ApiName.ExternalAppAuthentication_AuthenticateWithSSOAndResendRequest, + ), + 'externalAppAuthentication.authenticateWithSSOAndResendRequest', + [appId, originalRequestInfo, authTokenRequest.claims, authTokenRequest.silent], + ).then(([wasSuccessful, response]: [boolean, IActionExecuteResponse | InvokeErrorWrapper]) => { + if (wasSuccessful && response.responseType != null) { + return response as IActionExecuteResponse; + } else { + const error = response as InvokeError; + throw error; + } + }); + } export function isSupported(): boolean { return ensureInitialized(runtime) && runtime.supports.externalAppAuthenticationForCEC ? true : false; } + + function validateOriginalRequestInfo(actionExecuteRequest: IActionExecuteInvokeRequest): void { + if (actionExecuteRequest.type !== ActionExecuteInvokeRequestType) { + const error: InvokeError = { + errorCode: InvokeErrorCode.INTERNAL_ERROR, + message: `Invalid action type ${actionExecuteRequest.type}. Action type must be "${ActionExecuteInvokeRequestType}"`, + }; + throw error; + } + } } diff --git a/packages/teams-js/src/private/interfaces.ts b/packages/teams-js/src/private/interfaces.ts index 5c3d5c2072..9840924cc4 100644 --- a/packages/teams-js/src/private/interfaces.ts +++ b/packages/teams-js/src/private/interfaces.ts @@ -533,3 +533,12 @@ export interface InvokeError { export enum InvokeErrorCode { INTERNAL_ERROR = 'INTERNAL_ERROR', // Generic error } + +/** + * @hidden + * Wrapper to differentiate between InvokeError and IInvokeResponse response from host + * @internal + * Limited to Microsoft-internal use + */ +export type InvokeErrorWrapper = InvokeError & { responseType: undefined }; +export const ActionExecuteInvokeRequestType = 'Action.Execute'; From 79aebfa71f108fe20d191785a684f4e662611279 Mon Sep 17 00:00:00 2001 From: Lakhveer Kaur Date: Mon, 9 Sep 2024 13:27:23 -0700 Subject: [PATCH 3/9] Adding support or Action Execute Auth --- .../ExternalAppAuthenticationCECAPIs.tsx | 120 +++++++++++++++++- .../externalAppAuthenticationForCEC.ts | 9 +- 2 files changed, 120 insertions(+), 9 deletions(-) diff --git a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx index 9b6b0c07c7..92cc4852ff 100644 --- a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx +++ b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx @@ -1,16 +1,20 @@ -import { AuthTokenRequestParameters, externalAppAuthenticationForCEC } from '@microsoft/teams-js'; +import { + AuthTokenRequestParameters, + externalAppAuthenticationForCEC, + IActionExecuteInvokeRequest, +} from '@microsoft/teams-js'; import React from 'react'; import { ApiWithoutInput } from '../utils/ApiWithoutInput'; import { ApiWithTextInput } from '../utils/ApiWithTextInput'; import { ModuleWrapper } from '../utils/ModuleWrapper'; -const CheckExternalAppAuthenticationCapability = (): React.ReactElement => +const CheckExternalAppAuthenticationForCECCapability = (): React.ReactElement => ApiWithoutInput({ - name: 'checkExternalAppAuthenticationCapability', - title: 'Check External App Authentication Capability', + name: 'checkExternalAppAuthenticationCECCapability', + title: 'Check External App Authentication CEC Capability', onClick: async () => - `External App Authentication module ${externalAppAuthenticationForCEC.isSupported() ? 'is' : 'is not'} supported`, + `External App Authentication CEC module ${externalAppAuthenticationForCEC.isSupported() ? 'is' : 'is not'} supported`, }); const AuthenticateWithOAuth = (): React.ReactElement => @@ -102,11 +106,115 @@ const AuthenticateWithSSO = (): React.ReactElement => }), }); +const AuthenticateAndResendRequest = (): React.ReactElement => + ApiWithTextInput<{ + appId: string; + conversationId: string; + authenticateParameters: { + url: string; + width?: number; + height?: number; + isExternal?: boolean; + }; + originalRequestInfo: IActionExecuteInvokeRequest; + }>({ + name: 'authenticateAndResendRequest', + title: 'Authenticate And Resend Request', + onClick: { + validateInput: (input) => { + if (!input.appId) { + throw new Error('appId is required'); + } + if (!input.authenticateParameters) { + throw new Error('authenticateParameters is required'); + } + if (!input.originalRequestInfo) { + throw new Error('originalRequestInfo is required'); + } + }, + submit: async (input) => { + const result = await externalAppAuthenticationForCEC.authenticateAndResendRequest( + input.appId, + input.conversationId, + { ...input.authenticateParameters, url: new URL(input.authenticateParameters.url) }, + input.originalRequestInfo, + ); + return JSON.stringify(result); + }, + }, + defaultInput: JSON.stringify({ + appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a', + conversationId: 'testConversationId', + authenticateParameters: { + url: 'https://localhost:4000', + width: 100, + height: 100, + isExternal: true, + }, + originalRequestInfo: { + requestType: 'ActionExecuteInvokeRequest', + type: 'Action.Execute', + id: 'id1', + verb: 'verb1', + data: 'data1', + }, + }), + }); +const AuthenticateWithSSOAndResendRequest = (): React.ReactElement => + ApiWithTextInput<{ + appId: string; + conversationId: string; + authTokenRequest: AuthTokenRequestParameters; + originalRequestInfo: IActionExecuteInvokeRequest; + }>({ + name: 'authenticateWithSSOAndResendRequest', + title: 'Authenticate With SSO And Resend Request', + onClick: { + validateInput: (input) => { + if (!input.appId) { + throw new Error('appId is required'); + } + if (!input.authTokenRequest) { + throw new Error('authTokenRequest is required'); + } + if (!input.originalRequestInfo) { + throw new Error('originalRequestInfo is required'); + } + }, + submit: async (input) => { + const result = await externalAppAuthenticationForCEC.authenticateWithSSOAndResendRequest( + input.appId, + input.conversationId, + input.authTokenRequest, + input.originalRequestInfo, + ); + return JSON.stringify(result); + }, + }, + defaultInput: JSON.stringify({ + appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a', + conversationId: 'testConversationId', + authTokenRequest: { + claims: ['https://graph.microsoft.com'], + silent: true, + }, + originalRequestInfo: { + requestType: 'ActionExecuteInvokeRequest', + type: 'Action.Execute', + id: 'id1', + verb: 'verb1', + data: 'data1', + }, + }), + }); + const ExternalAppAuthenticationForCECAPIs = (): React.ReactElement => ( - + + + ); diff --git a/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts b/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts index 5f4a2f4a1c..ba992607f3 100644 --- a/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts +++ b/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts @@ -95,6 +95,7 @@ export namespace externalAppAuthenticationForCEC { export function authenticateAndResendRequest( appId: string, + conversationId: string, authenticateParameters: AuthenticatePopUpParameters, originalRequestInfo: IActionExecuteInvokeRequest, ): Promise { @@ -112,9 +113,10 @@ export namespace externalAppAuthenticationForCEC { externalAppAuthenticationTelemetryVersionNumber, ApiName.ExternalAppAuthentication_AuthenticateAndResendRequest, ), - 'externalAppAuthentication.authenticateAndResendRequest', + 'externalAppAuthenticationForCEC.authenticateAndResendRequest', [ appId, + conversationId, originalRequestInfo, authenticateParameters.url.href, authenticateParameters.width, @@ -132,6 +134,7 @@ export namespace externalAppAuthenticationForCEC { } export function authenticateWithSSOAndResendRequest( appId: string, + conversationId: string, authTokenRequest: AuthTokenRequestParameters, originalRequestInfo: IActionExecuteInvokeRequest, ): Promise { @@ -149,8 +152,8 @@ export namespace externalAppAuthenticationForCEC { externalAppAuthenticationTelemetryVersionNumber, ApiName.ExternalAppAuthentication_AuthenticateWithSSOAndResendRequest, ), - 'externalAppAuthentication.authenticateWithSSOAndResendRequest', - [appId, originalRequestInfo, authTokenRequest.claims, authTokenRequest.silent], + 'externalAppAuthenticationForCEC.authenticateWithSSOAndResendRequest', + [appId, conversationId, originalRequestInfo, authTokenRequest.claims, authTokenRequest.silent], ).then(([wasSuccessful, response]: [boolean, IActionExecuteResponse | InvokeErrorWrapper]) => { if (wasSuccessful && response.responseType != null) { return response as IActionExecuteResponse; From 8c2b73db66733cddf798160f691b8bca1317f0bd Mon Sep 17 00:00:00 2001 From: Lakhveer Kaur Date: Thu, 12 Sep 2024 10:20:21 -0700 Subject: [PATCH 4/9] taking off callback from CEC APIs --- .../ExternalAppAuthenticationCECAPIs.tsx | 18 ++++----------- packages/teams-js/src/internal/handlers.ts | 1 - .../externalAppAuthenticationForCEC.ts | 22 +++++-------------- 3 files changed, 10 insertions(+), 31 deletions(-) diff --git a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx index 92cc4852ff..2b6b55b72f 100644 --- a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx +++ b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx @@ -40,15 +40,10 @@ const AuthenticateWithOAuth = (): React.ReactElement => } }, submit: async (input) => { - const oAuthcallback = () => { - console.log('callback received'); - }; - const result = await externalAppAuthenticationForCEC.authenticateWithOAuth( - input.appId, - input.conversationId, - { ...input.authenticateParameters, url: new URL(input.authenticateParameters.url) }, - oAuthcallback, - ); + const result = await externalAppAuthenticationForCEC.authenticateWithOAuth(input.appId, input.conversationId, { + ...input.authenticateParameters, + url: new URL(input.authenticateParameters.url), + }); return 'Completed'; }, }, @@ -82,15 +77,10 @@ const AuthenticateWithSSO = (): React.ReactElement => } }, submit: async (input, setResult) => { - const ssoCallback = () => { - console.log('callback received'); - setResult('callback received'); - }; await externalAppAuthenticationForCEC.authenticateWithSSO( input.appId, input.conversationId, input.authTokenRequest, - ssoCallback, ); console.log('completed'); return 'Completed'; diff --git a/packages/teams-js/src/internal/handlers.ts b/packages/teams-js/src/internal/handlers.ts index 3affa66e7c..e8e1d8935b 100644 --- a/packages/teams-js/src/internal/handlers.ts +++ b/packages/teams-js/src/internal/handlers.ts @@ -119,7 +119,6 @@ export function registerHandler( * Limited to Microsoft-internal use */ export function removeHandler(name: string): void { - console.log(name + ' handler is removed ' + JSON.stringify(HandlersPrivate.handlers)); delete HandlersPrivate.handlers[name]; } diff --git a/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts b/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts index ba992607f3..23bf6c4497 100644 --- a/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts +++ b/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts @@ -1,5 +1,4 @@ import { sendMessageToParentAsync } from '../internal/communication'; -import { registerHandler, removeHandler } from '../internal/handlers'; import { ensureInitialized } from '../internal/internalAPIs'; import { ApiName, ApiVersionNumber, getApiVersionTag } from '../internal/telemetry'; import { validateId } from '../internal/utils'; @@ -23,7 +22,6 @@ export namespace externalAppAuthenticationForCEC { appId: string, conversationId: string, authTokenRequest: AuthTokenRequestParameters, - SSOAuthCompletedCallback: () => void, ): Promise { ensureInitialized(runtime, FrameContexts.content); @@ -31,11 +29,8 @@ export namespace externalAppAuthenticationForCEC { throw errorNotSupportedOnPlatform; } validateId(appId, new Error('App id is not valid.')); - registerHandler( - getApiVersionTag(ApiVersionNumber.V_3, ApiName.ExternalAppAuthenticationForCEC_SSOAuthCompleted), - 'ssoAuthCompleted', - SSOAuthCompletedCallback, - ); + validateId(conversationId, new Error('conversation id is not valid.')); + return sendMessageToParentAsync( getApiVersionTag( externalAppAuthenticationTelemetryVersionNumber, @@ -44,7 +39,6 @@ export namespace externalAppAuthenticationForCEC { 'externalAppAuthenticationForCEC.authenticateWithSSO', [appId, conversationId, authTokenRequest.claims, authTokenRequest.silent], ).then(([wasSuccessful, error]: [boolean, InvokeError]) => { - removeHandler('ssoAuthCompleted'); if (!wasSuccessful) { throw error; } @@ -55,8 +49,6 @@ export namespace externalAppAuthenticationForCEC { appId: string, conversationId: string, authenticateParameters: AuthenticatePopUpParameters, - // callback that will be called when hubsdk is done with Authentication - OAuthCompletedCallback: () => void, ): Promise { ensureInitialized(runtime, FrameContexts.content); @@ -64,11 +56,7 @@ export namespace externalAppAuthenticationForCEC { throw errorNotSupportedOnPlatform; } validateId(appId, new Error('App id is not valid.')); - registerHandler( - getApiVersionTag(ApiVersionNumber.V_3, ApiName.ExternalAppAuthenticationForCEC_SSOAuthCompleted), - 'oAuthCompleted', - OAuthCompletedCallback, - ); + validateId(conversationId, new Error('conversation id is not valid.')); // Ask the parent window to open an authentication window with the parameters provided by the caller. return sendMessageToParentAsync( @@ -86,7 +74,6 @@ export namespace externalAppAuthenticationForCEC { authenticateParameters.isExternal, ], ).then(([wasSuccessful, error]: [boolean, InvokeError]) => { - removeHandler('oAuthCompleted'); if (!wasSuccessful) { throw error; } @@ -105,6 +92,8 @@ export namespace externalAppAuthenticationForCEC { throw errorNotSupportedOnPlatform; } validateId(appId, new Error('App id is not valid.')); + validateId(conversationId, new Error('conversation id is not valid.')); + validateOriginalRequestInfo(originalRequestInfo); // Ask the parent window to open an authentication window with the parameters provided by the caller. @@ -144,6 +133,7 @@ export namespace externalAppAuthenticationForCEC { throw errorNotSupportedOnPlatform; } validateId(appId, new Error('App id is not valid.')); + validateId(conversationId, new Error('conversation id is not valid.')); validateOriginalRequestInfo(originalRequestInfo); From a623f8848113ce24e7a268cc03abb6025b9ca09e Mon Sep 17 00:00:00 2001 From: Lakhveer Kaur Date: Fri, 13 Sep 2024 16:21:34 -0700 Subject: [PATCH 5/9] fixing default value for external-app-auth input --- .../components/privateApis/ExternalAppAuthenticationAPIs.tsx | 2 +- .../privateApis/ExternalAppAuthenticationCECAPIs.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationAPIs.tsx b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationAPIs.tsx index 3c6ea173d0..b1cae2675a 100644 --- a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationAPIs.tsx +++ b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationAPIs.tsx @@ -50,7 +50,7 @@ const AuthenticateAndResendRequest = (): React.ReactElement => defaultInput: JSON.stringify({ appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a', authenticateParameters: { - url: 'https://www.example.com', + url: 'https://localhost:4000', width: 100, height: 100, isExternal: true, diff --git a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx index 2b6b55b72f..5ada651f7c 100644 --- a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx +++ b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx @@ -40,7 +40,7 @@ const AuthenticateWithOAuth = (): React.ReactElement => } }, submit: async (input) => { - const result = await externalAppAuthenticationForCEC.authenticateWithOAuth(input.appId, input.conversationId, { + await externalAppAuthenticationForCEC.authenticateWithOAuth(input.appId, input.conversationId, { ...input.authenticateParameters, url: new URL(input.authenticateParameters.url), }); @@ -51,7 +51,7 @@ const AuthenticateWithOAuth = (): React.ReactElement => appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a', conversationId: 'testConversationId', authenticateParameters: { - url: 'https://www.example.com', + url: 'https://localhost:4000', width: 100, height: 100, isExternal: true, From 0096e3cf7112cb33ef5c02a6de15e7be0556d781 Mon Sep 17 00:00:00 2001 From: Lakhveer Kaur Date: Fri, 20 Sep 2024 17:13:04 -0700 Subject: [PATCH 6/9] cec name updated to cea --- ...> ExternalAppAuthenticationForCEAAPIs.tsx} | 26 +++++++++---------- apps/teams-test-app/src/pages/TestApp.tsx | 4 +-- packages/teams-js/src/internal/telemetry.ts | 8 +++--- ....ts => externalAppAuthenticationForCEA.ts} | 16 ++++++------ packages/teams-js/src/private/index.ts | 2 +- packages/teams-js/src/public/runtime.ts | 2 +- 6 files changed, 29 insertions(+), 29 deletions(-) rename apps/teams-test-app/src/components/privateApis/{ExternalAppAuthenticationCECAPIs.tsx => ExternalAppAuthenticationForCEAAPIs.tsx} (87%) rename packages/teams-js/src/private/{externalAppAuthenticationForCEC.ts => externalAppAuthenticationForCEA.ts} (92%) diff --git a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationForCEAAPIs.tsx similarity index 87% rename from apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx rename to apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationForCEAAPIs.tsx index 5ada651f7c..1889e405ac 100644 --- a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationCECAPIs.tsx +++ b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationForCEAAPIs.tsx @@ -1,6 +1,6 @@ import { AuthTokenRequestParameters, - externalAppAuthenticationForCEC, + externalAppAuthenticationForCEA, IActionExecuteInvokeRequest, } from '@microsoft/teams-js'; import React from 'react'; @@ -9,12 +9,12 @@ import { ApiWithoutInput } from '../utils/ApiWithoutInput'; import { ApiWithTextInput } from '../utils/ApiWithTextInput'; import { ModuleWrapper } from '../utils/ModuleWrapper'; -const CheckExternalAppAuthenticationForCECCapability = (): React.ReactElement => +const CheckExternalAppAuthenticationForCEACapability = (): React.ReactElement => ApiWithoutInput({ - name: 'checkExternalAppAuthenticationCECCapability', - title: 'Check External App Authentication CEC Capability', + name: 'checkExternalAppAuthenticationCEACapability', + title: 'Check External App Authentication CEA Capability', onClick: async () => - `External App Authentication CEC module ${externalAppAuthenticationForCEC.isSupported() ? 'is' : 'is not'} supported`, + `External App Authentication CEA module ${externalAppAuthenticationForCEA.isSupported() ? 'is' : 'is not'} supported`, }); const AuthenticateWithOAuth = (): React.ReactElement => @@ -40,7 +40,7 @@ const AuthenticateWithOAuth = (): React.ReactElement => } }, submit: async (input) => { - await externalAppAuthenticationForCEC.authenticateWithOAuth(input.appId, input.conversationId, { + await externalAppAuthenticationForCEA.authenticateWithOAuth(input.appId, input.conversationId, { ...input.authenticateParameters, url: new URL(input.authenticateParameters.url), }); @@ -77,7 +77,7 @@ const AuthenticateWithSSO = (): React.ReactElement => } }, submit: async (input, setResult) => { - await externalAppAuthenticationForCEC.authenticateWithSSO( + await externalAppAuthenticationForCEA.authenticateWithSSO( input.appId, input.conversationId, input.authTokenRequest, @@ -123,7 +123,7 @@ const AuthenticateAndResendRequest = (): React.ReactElement => } }, submit: async (input) => { - const result = await externalAppAuthenticationForCEC.authenticateAndResendRequest( + const result = await externalAppAuthenticationForCEA.authenticateAndResendRequest( input.appId, input.conversationId, { ...input.authenticateParameters, url: new URL(input.authenticateParameters.url) }, @@ -172,7 +172,7 @@ const AuthenticateWithSSOAndResendRequest = (): React.ReactElement => } }, submit: async (input) => { - const result = await externalAppAuthenticationForCEC.authenticateWithSSOAndResendRequest( + const result = await externalAppAuthenticationForCEA.authenticateWithSSOAndResendRequest( input.appId, input.conversationId, input.authTokenRequest, @@ -198,9 +198,9 @@ const AuthenticateWithSSOAndResendRequest = (): React.ReactElement => }), }); -const ExternalAppAuthenticationForCECAPIs = (): React.ReactElement => ( - - +const ExternalAppAuthenticationForCEAAPIs = (): React.ReactElement => ( + + @@ -208,4 +208,4 @@ const ExternalAppAuthenticationForCECAPIs = (): React.ReactElement => ( ); -export default ExternalAppAuthenticationForCECAPIs; +export default ExternalAppAuthenticationForCEAAPIs; diff --git a/apps/teams-test-app/src/pages/TestApp.tsx b/apps/teams-test-app/src/pages/TestApp.tsx index d9a5751e74..b8a1dc83df 100644 --- a/apps/teams-test-app/src/pages/TestApp.tsx +++ b/apps/teams-test-app/src/pages/TestApp.tsx @@ -40,7 +40,7 @@ import PeopleAPIs from '../components/PeopleAPIs'; import ChatAPIs from '../components/privateApis/ChatAPIs'; import CopilotAPIs from '../components/privateApis/CopilotAPIs'; import ExternalAppAuthenticationAPIs from '../components/privateApis/ExternalAppAuthenticationAPIs'; -import ExternalAppAuthenticationForCECAPIs from '../components/privateApis/ExternalAppAuthenticationCECAPIs'; +import ExternalAppAuthenticationForCEAAPIs from '../components/privateApis/ExternalAppAuthenticationForCEAAPIs'; import ExternalAppCardActionsAPIs from '../components/privateApis/ExternalAppCardActionsAPIs'; import ExternalAppCommandsAPIs from '../components/privateApis/ExternalAppCommandsAPIs'; import FilesAPIs from '../components/privateApis/FilesAPIs'; @@ -94,7 +94,7 @@ export const TestApp: React.FC = () => { - + diff --git a/packages/teams-js/src/internal/telemetry.ts b/packages/teams-js/src/internal/telemetry.ts index 32bff22c69..1451c84c9f 100644 --- a/packages/teams-js/src/internal/telemetry.ts +++ b/packages/teams-js/src/internal/telemetry.ts @@ -111,10 +111,10 @@ export const enum ApiName { ExternalAppAuthentication_AuthenticateWithSSOAndResendRequest = 'externalAppAuthentication.authenticateWithSSOAndResendRequest', ExternalAppAuthentication_AuthenticateWithOauth2 = 'externalAppAuthentication.authenticateWithOauth2', ExternalAppAuthentication_AuthenticateWithPowerPlatformConnectorPlugins = 'externalAppAuthentication.authenticateWithPowerPlatformConnectorPlugins', - ExternalAppAuthenticationForCEC_AuthenticateWithOauth = 'externalAppAuthentication.cec.authenticateWithOauth', - ExternalAppAuthenticationForCEC_AuthenticateWithSSO = 'externalAppAuthentication.cec.AuthenticateWithSSO', - ExternalAppAuthenticationForCEC_SSOAuthCompleted = 'externalAppAuthentication.cec.SSOAuthCompleted', - ExternalAppAuthenticationForCEC_OAuthCompleted = 'externalAppAuthentication.cec.OAuthCompleted', + ExternalAppAuthenticationForCEA_AuthenticateWithOauth = 'externalAppAuthentication.cea.authenticateWithOauth', + ExternalAppAuthenticationForCEA_AuthenticateWithSSO = 'externalAppAuthentication.cea.AuthenticateWithSSO', + ExternalAppAuthenticationForCEA_SSOAuthCompleted = 'externalAppAuthentication.cea.SSOAuthCompleted', + ExternalAppAuthenticationForCEA_OAuthCompleted = 'externalAppAuthentication.cea.OAuthCompleted', ExternalAppCardActions_ProcessActionOpenUrl = 'externalAppCardActions.processActionOpenUrl', ExternalAppCardActions_ProcessActionSubmit = 'externalAppCardActions.processActionSubmit', ExternalAppCommands_ProcessActionCommands = 'externalAppCommands.processActionCommand', diff --git a/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts b/packages/teams-js/src/private/externalAppAuthenticationForCEA.ts similarity index 92% rename from packages/teams-js/src/private/externalAppAuthenticationForCEC.ts rename to packages/teams-js/src/private/externalAppAuthenticationForCEA.ts index 23bf6c4497..116f9bee09 100644 --- a/packages/teams-js/src/private/externalAppAuthenticationForCEC.ts +++ b/packages/teams-js/src/private/externalAppAuthenticationForCEA.ts @@ -17,7 +17,7 @@ import { const externalAppAuthenticationTelemetryVersionNumber: ApiVersionNumber = ApiVersionNumber.V_3; -export namespace externalAppAuthenticationForCEC { +export namespace externalAppAuthenticationForCEA { export function authenticateWithSSO( appId: string, conversationId: string, @@ -34,9 +34,9 @@ export namespace externalAppAuthenticationForCEC { return sendMessageToParentAsync( getApiVersionTag( externalAppAuthenticationTelemetryVersionNumber, - ApiName.ExternalAppAuthenticationForCEC_AuthenticateWithSSO, + ApiName.ExternalAppAuthenticationForCEA_AuthenticateWithSSO, ), - 'externalAppAuthenticationForCEC.authenticateWithSSO', + 'externalAppAuthenticationForCEA.authenticateWithSSO', [appId, conversationId, authTokenRequest.claims, authTokenRequest.silent], ).then(([wasSuccessful, error]: [boolean, InvokeError]) => { if (!wasSuccessful) { @@ -62,9 +62,9 @@ export namespace externalAppAuthenticationForCEC { return sendMessageToParentAsync( getApiVersionTag( externalAppAuthenticationTelemetryVersionNumber, - ApiName.ExternalAppAuthenticationForCEC_AuthenticateWithOauth, + ApiName.ExternalAppAuthenticationForCEA_AuthenticateWithOauth, ), - 'externalAppAuthenticationForCEC.authenticateWithOauth', + 'externalAppAuthenticationForCEA.authenticateWithOauth', [ appId, conversationId, @@ -102,7 +102,7 @@ export namespace externalAppAuthenticationForCEC { externalAppAuthenticationTelemetryVersionNumber, ApiName.ExternalAppAuthentication_AuthenticateAndResendRequest, ), - 'externalAppAuthenticationForCEC.authenticateAndResendRequest', + 'externalAppAuthenticationForCEA.authenticateAndResendRequest', [ appId, conversationId, @@ -142,7 +142,7 @@ export namespace externalAppAuthenticationForCEC { externalAppAuthenticationTelemetryVersionNumber, ApiName.ExternalAppAuthentication_AuthenticateWithSSOAndResendRequest, ), - 'externalAppAuthenticationForCEC.authenticateWithSSOAndResendRequest', + 'externalAppAuthenticationForCEA.authenticateWithSSOAndResendRequest', [appId, conversationId, originalRequestInfo, authTokenRequest.claims, authTokenRequest.silent], ).then(([wasSuccessful, response]: [boolean, IActionExecuteResponse | InvokeErrorWrapper]) => { if (wasSuccessful && response.responseType != null) { @@ -154,7 +154,7 @@ export namespace externalAppAuthenticationForCEC { }); } export function isSupported(): boolean { - return ensureInitialized(runtime) && runtime.supports.externalAppAuthenticationForCEC ? true : false; + return ensureInitialized(runtime) && runtime.supports.externalAppAuthenticationForCEA ? true : false; } function validateOriginalRequestInfo(actionExecuteRequest: IActionExecuteInvokeRequest): void { diff --git a/packages/teams-js/src/private/index.ts b/packages/teams-js/src/private/index.ts index 3238bbef84..98d537276f 100644 --- a/packages/teams-js/src/private/index.ts +++ b/packages/teams-js/src/private/index.ts @@ -21,7 +21,7 @@ export { export { conversations } from './conversations'; export { copilot } from './copilot'; export { externalAppAuthentication } from './externalAppAuthentication'; -export { externalAppAuthenticationForCEC } from './externalAppAuthenticationForCEC'; +export { externalAppAuthenticationForCEA } from './externalAppAuthenticationForCEA'; export { externalAppCardActions } from './externalAppCardActions'; export { externalAppCommands } from './externalAppCommands'; export { files } from './files'; diff --git a/packages/teams-js/src/public/runtime.ts b/packages/teams-js/src/public/runtime.ts index d51ec9aceb..d05d7567dd 100644 --- a/packages/teams-js/src/public/runtime.ts +++ b/packages/teams-js/src/public/runtime.ts @@ -242,7 +242,7 @@ interface IRuntimeV4 extends IBaseRuntime { readonly update?: {}; }; readonly externalAppAuthentication?: {}; - readonly externalAppAuthenticationForCEC?: {}; + readonly externalAppAuthenticationForCEA?: {}; readonly externalAppCardActions?: {}; readonly externalAppCommands?: {}; readonly geoLocation?: { From 270d7b7aeebaf959dbe20175d1773c96acecc225 Mon Sep 17 00:00:00 2001 From: Lakhveer Kaur Date: Wed, 25 Sep 2024 13:32:09 -0700 Subject: [PATCH 7/9] adding documentation for new CEA APIs --- .../externalAppAuthenticationForCEA.ts | 94 +++++- packages/teams-js/src/private/interfaces.ts | 271 ++++++++++++++++++ 2 files changed, 357 insertions(+), 8 deletions(-) diff --git a/packages/teams-js/src/private/externalAppAuthenticationForCEA.ts b/packages/teams-js/src/private/externalAppAuthenticationForCEA.ts index 116f9bee09..623ff83a57 100644 --- a/packages/teams-js/src/private/externalAppAuthenticationForCEA.ts +++ b/packages/teams-js/src/private/externalAppAuthenticationForCEA.ts @@ -2,6 +2,7 @@ import { sendMessageToParentAsync } from '../internal/communication'; import { ensureInitialized } from '../internal/internalAPIs'; import { ApiName, ApiVersionNumber, getApiVersionTag } from '../internal/telemetry'; import { validateId } from '../internal/utils'; +import { AppId } from '../public'; import { errorNotSupportedOnPlatform, FrameContexts } from '../public/constants'; import { runtime } from '../public/runtime'; import { @@ -17,9 +18,28 @@ import { const externalAppAuthenticationTelemetryVersionNumber: ApiVersionNumber = ApiVersionNumber.V_3; +/** + * @hidden + * Namespace to delegate authentication requests to the host for custom engine agents + * @internal + * Limited to Microsoft-internal use + * @beta + */ export namespace externalAppAuthenticationForCEA { + /** + * @beta + * @hidden + * Signals to the host to perform SSO authentication for the application specified by the app ID, and then send the authResult to the application backend. + * @internal + * Limited to Microsoft-internal use + * @param appId Id of the application backend for which the host should attempt SSO authentication. + * @param conversationId ConversationId To tell the bot what conversation the calls are coming from + * @param authTokenRequest Parameters for SSO authentication + * @throws InvokeError if the host encounters an error while authenticating + * @returns A promise that resolves when authentication succeeds and rejects with InvokeError on failure + */ export function authenticateWithSSO( - appId: string, + appId: AppId, conversationId: string, authTokenRequest: AuthTokenRequestParameters, ): Promise { @@ -28,7 +48,7 @@ export namespace externalAppAuthenticationForCEA { if (!isSupported()) { throw errorNotSupportedOnPlatform; } - validateId(appId, new Error('App id is not valid.')); + validateId(conversationId, new Error('conversation id is not valid.')); return sendMessageToParentAsync( @@ -45,8 +65,20 @@ export namespace externalAppAuthenticationForCEA { }); } + /** + * @beta + * @hidden + * Signals to the host to perform authentication using the given authentication parameters and then send the auth result to the application backend. + * @internal + * Limited to Microsoft-internal use + * @param appId ID of the application backend to which the request and authentication response should be sent. This must be a UUID + * @param conversationId ConversationId To tell the bot what conversation the calls are coming from + * @param authenticateParameters Parameters for the authentication pop-up + * @throws InvokeError if the host encounters an error while authenticating + * @returns A promise that resolves from the application backend and rejects with InvokeError if the host encounters an error while authenticating + */ export function authenticateWithOAuth( - appId: string, + appId: AppId, conversationId: string, authenticateParameters: AuthenticatePopUpParameters, ): Promise { @@ -55,7 +87,7 @@ export namespace externalAppAuthenticationForCEA { if (!isSupported()) { throw errorNotSupportedOnPlatform; } - validateId(appId, new Error('App id is not valid.')); + validateId(conversationId, new Error('conversation id is not valid.')); // Ask the parent window to open an authentication window with the parameters provided by the caller. @@ -80,8 +112,21 @@ export namespace externalAppAuthenticationForCEA { }); } + /** + * @beta + * @hidden + * Signals to the host to perform authentication using the given authentication parameters and then resend the request to the application backend with the authentication result. + * @internal + * Limited to Microsoft-internal use + * @param appId ID of the application backend to which the request and authentication response should be sent. This must be a UUID + * @param conversationId ConversationId To tell the bot what conversation the calls are coming from + * @param authenticateParameters Parameters for the authentication pop-up + * @param originalRequestInfo Information about the original request that should be resent + * @throws InvokeError if the host encounters an error while authenticating or resending the request + * @returns A promise that resolves to the IActionExecuteResponse from the application backend and rejects with InvokeError if the host encounters an error while authenticating or resending the request + */ export function authenticateAndResendRequest( - appId: string, + appId: AppId, conversationId: string, authenticateParameters: AuthenticatePopUpParameters, originalRequestInfo: IActionExecuteInvokeRequest, @@ -91,7 +136,7 @@ export namespace externalAppAuthenticationForCEA { if (!isSupported()) { throw errorNotSupportedOnPlatform; } - validateId(appId, new Error('App id is not valid.')); + validateId(conversationId, new Error('conversation id is not valid.')); validateOriginalRequestInfo(originalRequestInfo); @@ -121,8 +166,22 @@ export namespace externalAppAuthenticationForCEA { } }); } + + /** + * @beta + * @hidden + * Signals to the host to perform SSO authentication for the application specified by the app ID and then resend the request to the application backend with the authentication result and originalRequestInfo + * @internal + * Limited to Microsoft-internal use + * @param appId ID of the application backend for which the host should attempt SSO authentication and resend the request and authentication response. This must be a UUID. + * @param conversationId ConversationId To tell the bot what conversation the calls are coming from + * @param authTokenRequest Parameters for SSO authentication + * @param originalRequestInfo Information about the original request that should be resent + * @throws InvokeError if the host encounters an error while authenticating or resending the request + * @returns A promise that resolves to the IActionExecuteResponse from the application backend and rejects with InvokeError if the host encounters an error while authenticating or resending the request + */ export function authenticateWithSSOAndResendRequest( - appId: string, + appId: AppId, conversationId: string, authTokenRequest: AuthTokenRequestParameters, originalRequestInfo: IActionExecuteInvokeRequest, @@ -132,7 +191,7 @@ export namespace externalAppAuthenticationForCEA { if (!isSupported()) { throw errorNotSupportedOnPlatform; } - validateId(appId, new Error('App id is not valid.')); + validateId(conversationId, new Error('conversation id is not valid.')); validateOriginalRequestInfo(originalRequestInfo); @@ -153,10 +212,29 @@ export namespace externalAppAuthenticationForCEA { } }); } + + /** + * @beta + * @hidden + * Checks if the externalAppAuthenticationForCEA capability is supported by the host + * @returns boolean to represent whether externalAppAuthenticationForCEA capability is supported + * + * @throws Error if {@linkcode app.initialize} has not successfully completed + * + * @internal + * Limited to Microsoft-internal use + */ export function isSupported(): boolean { return ensureInitialized(runtime) && runtime.supports.externalAppAuthenticationForCEA ? true : false; } + /** + * @hidden + * @internal + * Limited to Microsoft-internal use + * @beta + */ + function validateOriginalRequestInfo(actionExecuteRequest: IActionExecuteInvokeRequest): void { if (actionExecuteRequest.type !== ActionExecuteInvokeRequestType) { const error: InvokeError = { diff --git a/packages/teams-js/src/private/interfaces.ts b/packages/teams-js/src/private/interfaces.ts index ecc807f72e..f70e5e5bb4 100644 --- a/packages/teams-js/src/private/interfaces.ts +++ b/packages/teams-js/src/private/interfaces.ts @@ -343,3 +343,274 @@ export interface ActionSubmitError { errorCode: ExternalAppErrorCode; message?: string; } + +/** + * @hidden + * Information about the bot request that should be resent by the host + * @internal + * Limited to Microsoft-internal use + */ +export type IOriginalRequestInfo = IQueryMessageExtensionRequest | IActionExecuteInvokeRequest; + +/** + * @hidden + * Parameters OauthWindow + * @internal + * Limited to Microsoft-internal use + */ +export type OauthWindowProperties = { + /** + * The preferred width for the pop-up. This value can be ignored if outside the acceptable bounds. + */ + width?: number; + /** + * The preferred height for the pop-up. This value can be ignored if outside the acceptable bounds. + */ + height?: number; + /** + * Some identity providers restrict their authentication pages from being displayed in embedded browsers (e.g., a web view inside of a native application) + * If the identity provider you are using prevents embedded browser usage, this flag should be set to `true` to enable the authentication page + * to be opened in an external browser. If this flag is `false`, the page will be opened directly within the current hosting application. + * + * This flag is ignored when the host for the application is a web app (as opposed to a native application) as the behavior is unnecessary in a web-only + * environment without an embedded browser. + */ + isExternal?: boolean; +}; +/** + * @hidden + * Parameters for the authentication pop-up. This interface is used exclusively with the externalAppAuthentication APIs + * @internal + * Limited to Microsoft-internal use + */ +export type AuthenticatePopUpParameters = { + /** + * The URL for the authentication pop-up. + */ + url: URL; + /** + * The preferred width for the pop-up. This value can be ignored if outside the acceptable bounds. + */ + width?: number; + /** + * The preferred height for the pop-up. This value can be ignored if outside the acceptable bounds. + */ + height?: number; + /** + * Some identity providers restrict their authentication pages from being displayed in embedded browsers (e.g., a web view inside of a native application) + * If the identity provider you are using prevents embedded browser usage, this flag should be set to `true` to enable the authentication page specified in + * the {@link url} property to be opened in an external browser. + * If this flag is `false`, the page will be opened directly within the current hosting application. + * + * This flag is ignored when the host for the application is a web app (as opposed to a native application) as the behavior is unnecessary in a web-only + * environment without an embedded browser. + */ + isExternal?: boolean; +}; + +/** + * @hidden + * Parameters for SSO authentication. This interface is used exclusively with the externalAppAuthentication APIs + * @internal + * Limited to Microsoft-internal use + */ +export type AuthTokenRequestParameters = { + /** + * An optional list of claims which to pass to Microsoft Entra when requesting the access token. + */ + claims?: string[]; + /** + * An optional flag indicating whether to attempt the token acquisition silently or allow a prompt to be shown. + */ + silent?: boolean; +}; + +/** + * @hidden + * Information about the message extension request that should be resent by the host. Corresponds to request schema in https://learn.microsoft.com/microsoftteams/platform/resources/messaging-extension-v3/search-extensions#receive-user-requests + * @internal + * Limited to Microsoft-internal use + */ +export interface IQueryMessageExtensionRequest { + requestType: OriginalRequestType.QueryMessageExtensionRequest; + commandId: string; + parameters: { + name: string; + value: string; + }[]; + queryOptions?: { + count: number; + skip: number; + }; +} + +/** + * @hidden + * Information about the Action.Execute request that should be resent by the host. Corresponds to schema in https://adaptivecards.io/explorer/Action.Execute.html + * @internal + * Limited to Microsoft-internal use + */ +export interface IActionExecuteInvokeRequest { + requestType: OriginalRequestType.ActionExecuteInvokeRequest; + type: string; // This must be "Action.Execute" + id: string; // The unique identifier associated with the action + verb: string; // The card author defined verb associated with the action + data: string | Record; +} + +/** + * @hidden + * Used to differentiate between IOriginalRequestInfo types + * @internal + * Limited to Microsoft-internal use + */ +export enum OriginalRequestType { + ActionExecuteInvokeRequest = 'ActionExecuteInvokeRequest', + QueryMessageExtensionRequest = 'QueryMessageExtensionRequest', +} +/*********** END REQUEST TYPE ************/ + +/*********** BEGIN RESPONSE TYPE ************/ +/** + * @hidden + * The response from the bot returned via the host + * @internal + * Limited to Microsoft-internal use + */ +export type IInvokeResponse = IQueryMessageExtensionResponse | IActionExecuteResponse; + +/** + * @hidden + * Used to differentiate between IInvokeResponse types + * @internal + * Limited to Microsoft-internal use + */ +export enum InvokeResponseType { + ActionExecuteInvokeResponse = 'ActionExecuteInvokeResponse', + QueryMessageExtensionResponse = 'QueryMessageExtensionResponse', +} + +/** + * @hidden + * The response from the bot returned via the host for a message extension query request. + * @internal + * Limited to Microsoft-internal use + */ +export interface IQueryMessageExtensionResponse { + responseType: InvokeResponseType.QueryMessageExtensionResponse; + composeExtension?: ComposeExtensionResponse; +} + +/** + * @hidden + * The response from the bot returned via the host for an Action.Execute request. + * @internal + * Limited to Microsoft-internal use + */ +export interface IActionExecuteResponse { + responseType: InvokeResponseType.ActionExecuteInvokeResponse; + value: Record; + signature?: string; + statusCode: number; + type: string; +} + +/** + * @hidden + * The compose extension response returned for a message extension query request. `suggestedActions` will be present only when the type is is 'config' or 'auth'. + * @internal + * Limited to Microsoft-internal use + */ +export type ComposeExtensionResponse = { + attachmentLayout: AttachmentLayout; + type: ComposeResultTypes; + attachments?: QueryMessageExtensionAttachment[]; + suggestedActions?: QueryMessageExtensionSuggestedActions; + text?: string; +}; + +/** + * @hidden + * + * @internal + * Limited to Microsoft-internal use + */ +export type QueryMessageExtensionSuggestedActions = { + actions: Action[]; +}; + +/** + * @hidden + * + * @internal + * Limited to Microsoft-internal use + */ +export type Action = { + type: string; + title: string; + value: string; +}; + +/** + * @hidden + * + * @internal + * Limited to Microsoft-internal use + */ +export type QueryMessageExtensionCard = { + contentType: string; + content: Record; + fallbackHtml?: string; + signature?: string; +}; + +/** + * @hidden + * + * @internal + * Limited to Microsoft-internal use + */ +export type QueryMessageExtensionAttachment = QueryMessageExtensionCard & { + preview?: QueryMessageExtensionCard; +}; + +/** + * @hidden + * + * @internal + * Limited to Microsoft-internal use + */ +export type AttachmentLayout = 'grid' | 'list'; +/** + * @hidden + * + * @internal + * Limited to Microsoft-internal use + */ +export type ComposeResultTypes = 'auth' | 'config' | 'message' | 'result' | 'silentAuth'; +/*********** END RESPONSE TYPE ************/ + +/*********** BEGIN ERROR TYPE ***********/ +export interface InvokeError { + errorCode: InvokeErrorCode; + message?: string; +} + +/** + * @hidden + * + * @internal + * Limited to Microsoft-internal use + */ +export enum InvokeErrorCode { + INTERNAL_ERROR = 'INTERNAL_ERROR', // Generic error +} + +/** + * @hidden + * Wrapper to differentiate between InvokeError and IInvokeResponse response from host + * @internal + * Limited to Microsoft-internal use + */ +export type InvokeErrorWrapper = InvokeError & { responseType: undefined }; +export const ActionExecuteInvokeRequestType = 'Action.Execute'; From cb946e6e8d3680b916045e6aa7cc40b7eef027e7 Mon Sep 17 00:00:00 2001 From: Lakhveer Kaur Date: Thu, 26 Sep 2024 14:40:09 -0700 Subject: [PATCH 8/9] changing .then logic to async/await --- .../ExternalAppAuthenticationForCEAAPIs.tsx | 4 +- .../externalAppAuthenticationForCEA.ts | 66 +++++++++---------- 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationForCEAAPIs.tsx b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationForCEAAPIs.tsx index 1889e405ac..015cf9950b 100644 --- a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationForCEAAPIs.tsx +++ b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationForCEAAPIs.tsx @@ -76,13 +76,13 @@ const AuthenticateWithSSO = (): React.ReactElement => throw new Error('authTokenRequest is required'); } }, - submit: async (input, setResult) => { + submit: async (input) => { await externalAppAuthenticationForCEA.authenticateWithSSO( input.appId, input.conversationId, input.authTokenRequest, ); - console.log('completed'); + return 'Completed'; }, }, diff --git a/packages/teams-js/src/private/externalAppAuthenticationForCEA.ts b/packages/teams-js/src/private/externalAppAuthenticationForCEA.ts index 623ff83a57..d519caa83c 100644 --- a/packages/teams-js/src/private/externalAppAuthenticationForCEA.ts +++ b/packages/teams-js/src/private/externalAppAuthenticationForCEA.ts @@ -16,7 +16,7 @@ import { InvokeErrorWrapper, } from './interfaces'; -const externalAppAuthenticationTelemetryVersionNumber: ApiVersionNumber = ApiVersionNumber.V_3; +const externalAppAuthenticationTelemetryVersionNumber: ApiVersionNumber = ApiVersionNumber.V_2; /** * @hidden @@ -38,7 +38,7 @@ export namespace externalAppAuthenticationForCEA { * @throws InvokeError if the host encounters an error while authenticating * @returns A promise that resolves when authentication succeeds and rejects with InvokeError on failure */ - export function authenticateWithSSO( + export async function authenticateWithSSO( appId: AppId, conversationId: string, authTokenRequest: AuthTokenRequestParameters, @@ -51,18 +51,17 @@ export namespace externalAppAuthenticationForCEA { validateId(conversationId, new Error('conversation id is not valid.')); - return sendMessageToParentAsync( + const [error] = await sendMessageToParentAsync<[InvokeError]>( getApiVersionTag( externalAppAuthenticationTelemetryVersionNumber, ApiName.ExternalAppAuthenticationForCEA_AuthenticateWithSSO, ), 'externalAppAuthenticationForCEA.authenticateWithSSO', [appId, conversationId, authTokenRequest.claims, authTokenRequest.silent], - ).then(([wasSuccessful, error]: [boolean, InvokeError]) => { - if (!wasSuccessful) { - throw error; - } - }); + ); + if (error) { + throw error; + } } /** @@ -77,7 +76,7 @@ export namespace externalAppAuthenticationForCEA { * @throws InvokeError if the host encounters an error while authenticating * @returns A promise that resolves from the application backend and rejects with InvokeError if the host encounters an error while authenticating */ - export function authenticateWithOAuth( + export async function authenticateWithOAuth( appId: AppId, conversationId: string, authenticateParameters: AuthenticatePopUpParameters, @@ -91,7 +90,7 @@ export namespace externalAppAuthenticationForCEA { validateId(conversationId, new Error('conversation id is not valid.')); // Ask the parent window to open an authentication window with the parameters provided by the caller. - return sendMessageToParentAsync( + const [error] = await sendMessageToParentAsync<[InvokeError]>( getApiVersionTag( externalAppAuthenticationTelemetryVersionNumber, ApiName.ExternalAppAuthenticationForCEA_AuthenticateWithOauth, @@ -105,11 +104,10 @@ export namespace externalAppAuthenticationForCEA { authenticateParameters.height, authenticateParameters.isExternal, ], - ).then(([wasSuccessful, error]: [boolean, InvokeError]) => { - if (!wasSuccessful) { - throw error; - } - }); + ); + if (error) { + throw error; + } } /** @@ -125,7 +123,7 @@ export namespace externalAppAuthenticationForCEA { * @throws InvokeError if the host encounters an error while authenticating or resending the request * @returns A promise that resolves to the IActionExecuteResponse from the application backend and rejects with InvokeError if the host encounters an error while authenticating or resending the request */ - export function authenticateAndResendRequest( + export async function authenticateAndResendRequest( appId: AppId, conversationId: string, authenticateParameters: AuthenticatePopUpParameters, @@ -142,7 +140,7 @@ export namespace externalAppAuthenticationForCEA { validateOriginalRequestInfo(originalRequestInfo); // Ask the parent window to open an authentication window with the parameters provided by the caller. - return sendMessageToParentAsync<[boolean, IActionExecuteResponse | InvokeErrorWrapper]>( + const [error, response] = await sendMessageToParentAsync<[InvokeErrorWrapper, IActionExecuteResponse]>( getApiVersionTag( externalAppAuthenticationTelemetryVersionNumber, ApiName.ExternalAppAuthentication_AuthenticateAndResendRequest, @@ -157,14 +155,12 @@ export namespace externalAppAuthenticationForCEA { authenticateParameters.height, authenticateParameters.isExternal, ], - ).then(([wasSuccessful, response]: [boolean, IActionExecuteResponse | InvokeErrorWrapper]) => { - if (wasSuccessful && response.responseType != null) { - return response as IActionExecuteResponse; - } else { - const error = response as InvokeError; - throw error; - } - }); + ); + if (response && response.responseType != null) { + return response as IActionExecuteResponse; + } else { + throw error; + } } /** @@ -180,7 +176,7 @@ export namespace externalAppAuthenticationForCEA { * @throws InvokeError if the host encounters an error while authenticating or resending the request * @returns A promise that resolves to the IActionExecuteResponse from the application backend and rejects with InvokeError if the host encounters an error while authenticating or resending the request */ - export function authenticateWithSSOAndResendRequest( + export async function authenticateWithSSOAndResendRequest( appId: AppId, conversationId: string, authTokenRequest: AuthTokenRequestParameters, @@ -196,21 +192,21 @@ export namespace externalAppAuthenticationForCEA { validateOriginalRequestInfo(originalRequestInfo); - return sendMessageToParentAsync<[boolean, IActionExecuteResponse | InvokeErrorWrapper]>( + const [error, response] = await sendMessageToParentAsync< + [InvokeErrorWrapper, IActionExecuteResponse | InvokeErrorWrapper] + >( getApiVersionTag( externalAppAuthenticationTelemetryVersionNumber, ApiName.ExternalAppAuthentication_AuthenticateWithSSOAndResendRequest, ), 'externalAppAuthenticationForCEA.authenticateWithSSOAndResendRequest', [appId, conversationId, originalRequestInfo, authTokenRequest.claims, authTokenRequest.silent], - ).then(([wasSuccessful, response]: [boolean, IActionExecuteResponse | InvokeErrorWrapper]) => { - if (wasSuccessful && response.responseType != null) { - return response as IActionExecuteResponse; - } else { - const error = response as InvokeError; - throw error; - } - }); + ); + if (response && response.responseType != null) { + return response as IActionExecuteResponse; + } else { + throw error; + } } /** From 6a08b1c02263a344533521cc89da4d20f7009ae7 Mon Sep 17 00:00:00 2001 From: Lakhveer Kaur Date: Mon, 30 Sep 2024 11:57:41 -0700 Subject: [PATCH 9/9] changing incoming response from hubsdk --- .../ExternalAppAuthenticationForCEAAPIs.tsx | 9 ++-- .../externalAppAuthenticationForCEA.ts | 44 +++++++++++++------ packages/teams-js/src/private/interfaces.ts | 19 ++++++++ 3 files changed, 54 insertions(+), 18 deletions(-) diff --git a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationForCEAAPIs.tsx b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationForCEAAPIs.tsx index 015cf9950b..6140091509 100644 --- a/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationForCEAAPIs.tsx +++ b/apps/teams-test-app/src/components/privateApis/ExternalAppAuthenticationForCEAAPIs.tsx @@ -1,4 +1,5 @@ import { + AppId, AuthTokenRequestParameters, externalAppAuthenticationForCEA, IActionExecuteInvokeRequest, @@ -40,7 +41,7 @@ const AuthenticateWithOAuth = (): React.ReactElement => } }, submit: async (input) => { - await externalAppAuthenticationForCEA.authenticateWithOAuth(input.appId, input.conversationId, { + await externalAppAuthenticationForCEA.authenticateWithOAuth(new AppId(input.appId), input.conversationId, { ...input.authenticateParameters, url: new URL(input.authenticateParameters.url), }); @@ -78,7 +79,7 @@ const AuthenticateWithSSO = (): React.ReactElement => }, submit: async (input) => { await externalAppAuthenticationForCEA.authenticateWithSSO( - input.appId, + new AppId(input.appId), input.conversationId, input.authTokenRequest, ); @@ -124,7 +125,7 @@ const AuthenticateAndResendRequest = (): React.ReactElement => }, submit: async (input) => { const result = await externalAppAuthenticationForCEA.authenticateAndResendRequest( - input.appId, + new AppId(input.appId), input.conversationId, { ...input.authenticateParameters, url: new URL(input.authenticateParameters.url) }, input.originalRequestInfo, @@ -173,7 +174,7 @@ const AuthenticateWithSSOAndResendRequest = (): React.ReactElement => }, submit: async (input) => { const result = await externalAppAuthenticationForCEA.authenticateWithSSOAndResendRequest( - input.appId, + new AppId(input.appId), input.conversationId, input.authTokenRequest, input.originalRequestInfo, diff --git a/packages/teams-js/src/private/externalAppAuthenticationForCEA.ts b/packages/teams-js/src/private/externalAppAuthenticationForCEA.ts index d519caa83c..93247d93ef 100644 --- a/packages/teams-js/src/private/externalAppAuthenticationForCEA.ts +++ b/packages/teams-js/src/private/externalAppAuthenticationForCEA.ts @@ -9,11 +9,14 @@ import { ActionExecuteInvokeRequestType, AuthenticatePopUpParameters, AuthTokenRequestParameters, + defaultExternalAppError, IActionExecuteInvokeRequest, IActionExecuteResponse, InvokeError, InvokeErrorCode, InvokeErrorWrapper, + InvokeResponseType, + isInvokeErrorWrapper, } from './interfaces'; const externalAppAuthenticationTelemetryVersionNumber: ApiVersionNumber = ApiVersionNumber.V_2; @@ -57,7 +60,7 @@ export namespace externalAppAuthenticationForCEA { ApiName.ExternalAppAuthenticationForCEA_AuthenticateWithSSO, ), 'externalAppAuthenticationForCEA.authenticateWithSSO', - [appId, conversationId, authTokenRequest.claims, authTokenRequest.silent], + [appId.toString(), conversationId, authTokenRequest.claims, authTokenRequest.silent], ); if (error) { throw error; @@ -97,7 +100,7 @@ export namespace externalAppAuthenticationForCEA { ), 'externalAppAuthenticationForCEA.authenticateWithOauth', [ - appId, + appId.toString(), conversationId, authenticateParameters.url.href, authenticateParameters.width, @@ -140,14 +143,14 @@ export namespace externalAppAuthenticationForCEA { validateOriginalRequestInfo(originalRequestInfo); // Ask the parent window to open an authentication window with the parameters provided by the caller. - const [error, response] = await sendMessageToParentAsync<[InvokeErrorWrapper, IActionExecuteResponse]>( + const [response] = await sendMessageToParentAsync<[InvokeErrorWrapper | IActionExecuteResponse]>( getApiVersionTag( externalAppAuthenticationTelemetryVersionNumber, ApiName.ExternalAppAuthentication_AuthenticateAndResendRequest, ), 'externalAppAuthenticationForCEA.authenticateAndResendRequest', [ - appId, + appId.toString(), conversationId, originalRequestInfo, authenticateParameters.url.href, @@ -156,13 +159,26 @@ export namespace externalAppAuthenticationForCEA { authenticateParameters.isExternal, ], ); - if (response && response.responseType != null) { - return response as IActionExecuteResponse; + if (isActionExecuteResponse(response)) { + return response; + } else if (isInvokeErrorWrapper(response)) { + throw response; } else { - throw error; + throw defaultExternalAppError; } } + function isActionExecuteResponse(response: unknown): response is IActionExecuteResponse { + const actionResponse = response as IActionExecuteResponse; + + return ( + actionResponse.responseType === InvokeResponseType.ActionExecuteInvokeResponse && + actionResponse.value !== undefined && + actionResponse.statusCode !== undefined && + actionResponse.type !== undefined + ); + } + /** * @beta * @hidden @@ -192,20 +208,20 @@ export namespace externalAppAuthenticationForCEA { validateOriginalRequestInfo(originalRequestInfo); - const [error, response] = await sendMessageToParentAsync< - [InvokeErrorWrapper, IActionExecuteResponse | InvokeErrorWrapper] - >( + const [response] = await sendMessageToParentAsync<[IActionExecuteResponse | InvokeErrorWrapper]>( getApiVersionTag( externalAppAuthenticationTelemetryVersionNumber, ApiName.ExternalAppAuthentication_AuthenticateWithSSOAndResendRequest, ), 'externalAppAuthenticationForCEA.authenticateWithSSOAndResendRequest', - [appId, conversationId, originalRequestInfo, authTokenRequest.claims, authTokenRequest.silent], + [appId.toString(), conversationId, originalRequestInfo, authTokenRequest.claims, authTokenRequest.silent], ); - if (response && response.responseType != null) { - return response as IActionExecuteResponse; + if (isActionExecuteResponse(response)) { + return response; + } else if (isInvokeErrorWrapper(response)) { + throw response; } else { - throw error; + throw defaultExternalAppError; } } diff --git a/packages/teams-js/src/private/interfaces.ts b/packages/teams-js/src/private/interfaces.ts index f70e5e5bb4..8d991707d8 100644 --- a/packages/teams-js/src/private/interfaces.ts +++ b/packages/teams-js/src/private/interfaces.ts @@ -614,3 +614,22 @@ export enum InvokeErrorCode { */ export type InvokeErrorWrapper = InvokeError & { responseType: undefined }; export const ActionExecuteInvokeRequestType = 'Action.Execute'; + +export function isInvokeErrorWrapper(err: unknown): err is InvokeErrorWrapper { + if (typeof err !== 'object' || err === null) { + return false; + } + + const errorWrapper = err as InvokeErrorWrapper; + + return ( + errorWrapper?.errorCode === InvokeErrorCode.INTERNAL_ERROR && + (errorWrapper.message === undefined || typeof errorWrapper.message === 'string') && + errorWrapper.responseType === undefined + ); +} + +export const defaultExternalAppError = { + errorCode: ExternalAppErrorCode.INTERNAL_ERROR, + message: 'An internal error occurred', +};