Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added externalAppCardActionsForCEA APIs #2507

Merged
merged 11 commits into from
Sep 25, 2024
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { externalAppCardActions } from '@microsoft/teams-js';
import { externalAppCardActions, IAdaptiveCardActionSubmit } from '@microsoft/teams-js';
import React from 'react';

import { ApiWithoutInput, ApiWithTextInput } from '../utils';
Expand All @@ -15,7 +15,7 @@ const CheckExternalAppCardActionsCapability = (): React.ReactElement =>
const ProcessActionSubmit = (): React.ReactElement =>
ApiWithTextInput<{
appId: string;
actionSubmitPayload: externalAppCardActions.IAdaptiveCardActionSubmit;
actionSubmitPayload: IAdaptiveCardActionSubmit;
}>({
name: 'processActionSubmit',
title: 'Process Action Submit',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { externalAppCardActions, IAdaptiveCardActionSubmit } from '@microsoft/teams-js';
import React from 'react';

import { ApiWithoutInput, ApiWithTextInput } from '../utils';
import { ModuleWrapper } from '../utils/ModuleWrapper';

const CheckExternalAppCardActionsForCEACapability = (): React.ReactElement =>
ApiWithoutInput({
name: 'checkExternalAppCardActionsForCEACapability',
title: 'Check External App Card Actions For CEA Capability',
onClick: async () =>
`External App Card Actions For CEA module ${externalAppCardActions.isSupported() ? 'is' : 'is not'} supported`,
MengyiGong marked this conversation as resolved.
Show resolved Hide resolved
});

const CECProcessActionSubmit = (): React.ReactElement =>
ApiWithTextInput<{
appId: string;
actionSubmitPayload: IAdaptiveCardActionSubmit;
}>({
name: 'processActionSubmitForCEA',
title: 'Process Action Submit For CEA',
onClick: {
validateInput: (input) => {
if (!input.appId) {
throw new Error('appId is required');
}
if (!input.actionSubmitPayload) {
throw new Error('actionSubmitPayload is required');
}
},
submit: async (input) => {
await externalAppCardActions.processActionSubmit(input.appId, input.actionSubmitPayload);
MengyiGong marked this conversation as resolved.
Show resolved Hide resolved
return 'Completed';
},
},
defaultInput: JSON.stringify({
appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a',
actionSubmitPayload: {
id: 'submitId',
data: 'data1',
},
}),
});

const CECProcessActionOpenUrl = (): React.ReactElement =>
ApiWithTextInput<{
appId: string;
url: string;
fromElement?: { name: 'composeExtensions' | 'plugins' };
}>({
name: 'processActionOpenUrlForCEA',
title: 'Process Action Open Url For CEA',
onClick: {
validateInput: (input) => {
if (!input.appId) {
throw new Error('appId is required');
}
if (!input.url) {
throw new Error('url is required');
}
},
submit: async (input) => {
const result = await externalAppCardActions.processActionOpenUrl(
MengyiGong marked this conversation as resolved.
Show resolved Hide resolved
input.appId,
new URL(input.url),
input.fromElement,
);
return JSON.stringify(result);
},
},
defaultInput: JSON.stringify({
appId: 'b7f8c0a0-6c1d-4a9a-9c0a-2c3f1c0a3b0a',
url: 'https://www.example.com',
}),
});

const ExternalAppCardActionsForCEAAPIs = (): React.ReactElement => (
<ModuleWrapper title="External App Card Actions For CEA">
<CheckExternalAppCardActionsForCEACapability />
<CECProcessActionSubmit />
<CECProcessActionOpenUrl />
</ModuleWrapper>
);

export default ExternalAppCardActionsForCEAAPIs;
2 changes: 2 additions & 0 deletions apps/teams-test-app/src/pages/TestApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import ChatAPIs from '../components/privateApis/ChatAPIs';
import CopilotAPIs from '../components/privateApis/CopilotAPIs';
import ExternalAppAuthenticationAPIs from '../components/privateApis/ExternalAppAuthenticationAPIs';
import ExternalAppCardActionsAPIs from '../components/privateApis/ExternalAppCardActionsAPIs';
import ExternalAppCardActionsForCEAAPIs from '../components/privateApis/ExternalAppCardActionsForCEAAPIs';
import ExternalAppCommandsAPIs from '../components/privateApis/ExternalAppCommandsAPIs';
import FilesAPIs from '../components/privateApis/FilesAPIs';
import FullTrustAPIs from '../components/privateApis/FullTrustAPIs';
Expand Down Expand Up @@ -114,6 +115,7 @@ export const TestApp: React.FC = () => {
<DialogUrlParentCommunicationAPIs childWindowRef={dialogWindowRef} />
<ExternalAppAuthenticationAPIs />
<ExternalAppCardActionsAPIs />
<ExternalAppCardActionsForCEAAPIs />
<ExternalAppCommandsAPIs />
<FilesAPIs />
<FullTrustAPIs />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Added APIs for externalAppCardActionForCEA capability.",
MengyiGong marked this conversation as resolved.
Show resolved Hide resolved
"packageName": "@microsoft/teams-js",
"email": "[email protected]",
"dependentChangeType": "patch"
}
2 changes: 2 additions & 0 deletions packages/teams-js/src/internal/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ export const enum ApiName {
ExternalAppAuthentication_AuthenticateWithPowerPlatformConnectorPlugins = 'externalAppAuthentication.authenticateWithPowerPlatformConnectorPlugins',
ExternalAppCardActions_ProcessActionOpenUrl = 'externalAppCardActions.processActionOpenUrl',
ExternalAppCardActions_ProcessActionSubmit = 'externalAppCardActions.processActionSubmit',
ExternalAppCardActionsForCEA_ProcessActionOpenUrl = 'externalAppCardActionsForCEA.processActionOpenUrl',
ExternalAppCardActionsForCEA_ProcessActionSubmit = 'externalAppCardActionsForCEA.processActionSubmit',
ExternalAppCommands_ProcessActionCommands = 'externalAppCommands.processActionCommand',
Files_AddCloudStorageFolder = 'files.addCloudStorageFolder',
Files_AddCloudStorageProvider = 'files.addCloudStorageProvider',
Expand Down
50 changes: 1 addition & 49 deletions packages/teams-js/src/private/externalAppCardActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ApiName, ApiVersionNumber, getApiVersionTag } from '../internal/telemet
import { validateId } from '../internal/utils';
import { errorNotSupportedOnPlatform, FrameContexts } from '../public/constants';
import { runtime } from '../public/runtime';
import { ExternalAppErrorCode } from './constants';
import { ActionOpenUrlError, ActionSubmitError, IAdaptiveCardActionSubmit } from './interfaces';

/**
* v2 APIs telemetry file: All of APIs in this capability file should send out API version v2 ONLY
Expand All @@ -31,54 +31,6 @@ export namespace externalAppCardActions {
GenericUrl = 'GenericUrl',
}

/**
* @hidden
* Error that can be thrown from IExternalAppCardActionService.handleActionOpenUrl
*
* @internal
* Limited to Microsoft-internal use
*/
export interface ActionOpenUrlError {
errorCode: ActionOpenUrlErrorCode;
message?: string;
}

/**
* @hidden
* Error codes that can be thrown from IExternalAppCardActionService.handleActionOpenUrl
* @internal
* Limited to Microsoft-internal use
*/
export enum ActionOpenUrlErrorCode {
INTERNAL_ERROR = 'INTERNAL_ERROR', // Generic error
INVALID_LINK = 'INVALID_LINK', // Deep link is invalid
NOT_SUPPORTED = 'NOT_SUPPORTED', // Deep link is not supported
}

/**
* @hidden
* The payload that is used when executing an Adaptive Card Action.Submit
* @internal
* Limited to Microsoft-internal use
*/
export interface IAdaptiveCardActionSubmit {
id: string;
data: string | Record<string, unknown>;
}

/**
*
* @hidden
* Error that can be thrown from IExternalAppCardActionService.handleActionSubmit
*
* @internal
* Limited to Microsoft-internal use
*/
export interface ActionSubmitError {
errorCode: ExternalAppErrorCode;
message?: string;
}

/**
* @beta
* @hidden
Expand Down
99 changes: 99 additions & 0 deletions packages/teams-js/src/private/externalAppCardActionsForCEA.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { sendMessageToParentAsync } from '../internal/communication';
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 { ActionOpenUrlError, ActionOpenUrlType, ActionSubmitError, IAdaptiveCardActionSubmit } from './interfaces';

/**
* Updated to constants file: v2 APIs telemetry file: All of APIs in this capability file should send out API version v2 ONLY
MengyiGong marked this conversation as resolved.
Show resolved Hide resolved
*/
const externalAppCardActionsTelemetryVersionNumber: ApiVersionNumber = ApiVersionNumber.V_2;
export namespace externalAppCardActionsForCEA {
MengyiGong marked this conversation as resolved.
Show resolved Hide resolved
/**
* @beta
* @hidden
* Delegates an Adaptive Card Action.OpenUrl request to the host for the application with the provided app ID.
* @internal
* Limited to Microsoft-internal use
* @param appId ID of the application the request is intended for. This must be a UUID
* @param conversationId To tell the bot what conversation the calls are coming from
* @param url The URL to open
* @returns Promise that resolves to ActionOpenUrlType indicating the type of URL that was opened on success and rejects with ActionOpenUrlError if the request fails
MengyiGong marked this conversation as resolved.
Show resolved Hide resolved
*/
export function processActionOpenUrl(appId: string, conversationId: string, url: URL): Promise<ActionOpenUrlType> {
MengyiGong marked this conversation as resolved.
Show resolved Hide resolved
ensureInitialized(runtime, FrameContexts.content);

if (!isSupported()) {
throw errorNotSupportedOnPlatform;
}
validateId(appId, new Error('App id is not valid.'));
MengyiGong marked this conversation as resolved.
Show resolved Hide resolved
validateId(conversationId, new Error('conversation id is not valid.'));
return sendMessageToParentAsync<[ActionOpenUrlError, ActionOpenUrlType]>(
getApiVersionTag(
externalAppCardActionsTelemetryVersionNumber,
ApiName.ExternalAppCardActionsForCEA_ProcessActionOpenUrl,
),
'externalAppCardActionsForCEA.processActionOpenUrl',
MengyiGong marked this conversation as resolved.
Show resolved Hide resolved
[appId, url.href, conversationId],
).then(([error, response]: [ActionOpenUrlError, ActionOpenUrlType]) => {
if (error) {
MengyiGong marked this conversation as resolved.
Show resolved Hide resolved
throw error;
} else {
return response;
}
});
}

/**
* @beta
* @hidden
* Delegates an Adaptive Card Action.Submit request to the host for the application with the provided app ID
* @internal
* Limited to Microsoft-internal use
* @param appId ID of the application the request is intended for. This must be a UUID
* @param conversationId To tell the bot what conversation the calls are coming from
* @param actionSubmitPayload The Adaptive Card Action.Submit payload
* @returns Promise that resolves when the request is completed and rejects with ActionSubmitError if the request fails
*/
export function processActionSubmit(
appId: string,
conversationId: string,
actionSubmitPayload: IAdaptiveCardActionSubmit,
): Promise<void> {
ensureInitialized(runtime, FrameContexts.content);

if (!isSupported()) {
throw errorNotSupportedOnPlatform;
}
validateId(appId, new Error('App id is not valid.'));
validateId(conversationId, new Error('conversation id is not valid.'));
return sendMessageToParentAsync<[boolean, ActionSubmitError]>(
getApiVersionTag(
externalAppCardActionsTelemetryVersionNumber,
ApiName.ExternalAppCardActionsForCEA_ProcessActionSubmit,
),
'externalAppCardActionsForCEA.processActionSubmit',
[appId, conversationId, actionSubmitPayload],
).then(([wasSuccessful, error]: [boolean, ActionSubmitError]) => {
MengyiGong marked this conversation as resolved.
Show resolved Hide resolved
if (!wasSuccessful) {
throw error;
}
});
}

/**
* @hidden
* Checks if the externalAppCardActionsForCEA capability is supported by the host
* @returns boolean to represent whether externalAppCardActions capability is supported
*
* @throws Error if {@linkcode app.initialize} has not successfully completed
*
* @internal
MengyiGong marked this conversation as resolved.
Show resolved Hide resolved
* Limited to Microsoft-internal use
*/
export function isSupported(): boolean {
return ensureInitialized(runtime) && runtime.supports.externalAppCardActionsForCEA ? true : false;
}
}
1 change: 1 addition & 0 deletions packages/teams-js/src/private/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export { conversations } from './conversations';
export { copilot } from './copilot';
export { externalAppAuthentication } from './externalAppAuthentication';
export { externalAppCardActions } from './externalAppCardActions';
export { externalAppCardActionsForCEA } from './externalAppCardActionsForCEA';
export { externalAppCommands } from './externalAppCommands';
export { files } from './files';
export { meetingRoom } from './meetingRoom';
Expand Down
58 changes: 58 additions & 0 deletions packages/teams-js/src/private/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FileOpenPreference, TeamInformation } from '../public/interfaces';
import { ExternalAppErrorCode } from './constants';

/**
* @hidden
Expand Down Expand Up @@ -271,3 +272,60 @@ export interface UserJoinedTeamsInformation {
*/
userJoinedTeams: TeamInformation[];
}

// externalAppCardActionsInterfaces.ts
MengyiGong marked this conversation as resolved.
Show resolved Hide resolved

MengyiGong marked this conversation as resolved.
Show resolved Hide resolved
export enum ActionOpenUrlType {
DeepLinkDialog = 'DeepLinkDialog',
DeepLinkOther = 'DeepLinkOther',
DeepLinkStageView = 'DeepLinkStageView',
GenericUrl = 'GenericUrl',
}

/**
* @hidden
* Error that can be thrown from IExternalAppCardActionService.handleActionOpenUrl
*
* @internal
* Limited to Microsoft-internal use
*/
export interface ActionOpenUrlError {
errorCode: ActionOpenUrlErrorCode;
message?: string;
}

/**
* @hidden
* Error codes that can be thrown from IExternalAppCardActionService.handleActionOpenUrl
* @internal
* Limited to Microsoft-internal use
*/
export enum ActionOpenUrlErrorCode {
INTERNAL_ERROR = 'INTERNAL_ERROR', // Generic error
INVALID_LINK = 'INVALID_LINK', // Deep link is invalid
NOT_SUPPORTED = 'NOT_SUPPORTED', // Deep link is not supported
}

/**
* @hidden
* The payload that is used when executing an Adaptive Card Action.Submit
* @internal
* Limited to Microsoft-internal use
*/
export interface IAdaptiveCardActionSubmit {
id: string;
data: string | Record<string, unknown>;
}

/**
*
* @hidden
* Error that can be thrown from IExternalAppCardActionService.handleActionSubmit
*
* @internal
* Limited to Microsoft-internal use
*/
export interface ActionSubmitError {
errorCode: ExternalAppErrorCode;
message?: string;
}
1 change: 1 addition & 0 deletions packages/teams-js/src/public/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ interface IRuntimeV4 extends IBaseRuntime {
};
readonly externalAppAuthentication?: {};
readonly externalAppCardActions?: {};
readonly externalAppCardActionsForCEA?: {};
readonly externalAppCommands?: {};
readonly geoLocation?: {
readonly map?: {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { errorLibraryNotInitialized } from '../../src/internal/constants';
import { GlobalVars } from '../../src/internal/globalVars';
import { ExternalAppErrorCode } from '../../src/private/constants';
import { externalAppCardActions } from '../../src/private/externalAppCardActions';
import { ActionOpenUrlErrorCode } from '../../src/private/interfaces';
import { FrameContexts } from '../../src/public';
import { app } from '../../src/public/app';
import { errorNotSupportedOnPlatform } from '../../src/public/constants';
Expand Down Expand Up @@ -133,7 +134,7 @@ describe('externalAppCardActions', () => {
const allowedFrameContexts = [FrameContexts.content];
const testUrl = new URL('https://example.com');
const testError = {
errorCode: externalAppCardActions.ActionOpenUrlErrorCode.INTERNAL_ERROR,
errorCode: ActionOpenUrlErrorCode.INTERNAL_ERROR,
message: 'testMessage',
};
const testResponse = externalAppCardActions.ActionOpenUrlType.DeepLinkDialog;
Expand Down
Loading
Loading