Skip to content

Commit

Permalink
Merge branch 'main' into maggieg/add_card_cec
Browse files Browse the repository at this point in the history
  • Loading branch information
AE-MS committed Sep 23, 2024
2 parents e584119 + d169af3 commit 2de3058
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"cSpell.words": ["adal", "frameless", "ipados", "teamspace", "uninitialize", "xvfb"],
"cSpell.words": ["adal", "frameless", "ipados", "teamsjs", "teamspace", "uninitialize", "xvfb"],
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.workingDirectories": [
"./apps/ssr-test-app/",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Added logging for version on startup",
"packageName": "@microsoft/teams-js",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Updated `pages.navigateToApp` to now optionally accept a more type-safe input object",
"packageName": "@microsoft/teams-js",
"email": "[email protected]",
"dependentChangeType": "patch"
}
6 changes: 3 additions & 3 deletions packages/teams-js/src/internal/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,16 +272,16 @@ export function runWithTimeout<TResult, TError>(
* @internal
* Limited to Microsoft-internal use
*/
export function createTeamsAppLink(params: pages.NavigateToAppParams): string {
export function createTeamsAppLink(params: pages.AppNavigationParameters): string {
const url = new URL(
'https://teams.microsoft.com/l/entity/' +
encodeURIComponent(params.appId) +
encodeURIComponent(params.appId.toString()) +
'/' +
encodeURIComponent(params.pageId),
);

if (params.webUrl) {
url.searchParams.append('webUrl', params.webUrl);
url.searchParams.append('webUrl', params.webUrl.toString());
}
if (params.chatId || params.channelId || params.subPageId) {
url.searchParams.append(
Expand Down
8 changes: 6 additions & 2 deletions packages/teams-js/src/public/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -750,9 +750,13 @@ export namespace app {
const scriptUsageWarning =
'Today, teamsjs can only be used from a single script or you may see undefined behavior. This log line is used to help detect cases where teamsjs is loaded multiple times -- it is always written. The presence of the log itself does not indicate a multi-load situation, but multiples of these log lines will. If you would like to use teamjs from more than one script at the same time, please open an issue at https://github.com/OfficeDev/microsoft-teams-library-js/issues';
if (!currentScriptSrc || currentScriptSrc.length === 0) {
appLogger('teamsjs is being used from a script tag embedded directly in your html. %s', scriptUsageWarning);
appLogger(
'teamsjs version %s is being used from a script tag embedded directly in your html. %s',
version,
scriptUsageWarning,
);
} else {
appLogger('teamsjs is being used from %s. %s', currentScriptSrc, scriptUsageWarning);
appLogger('teamsjs version %s is being used from %s. %s', version, currentScriptSrc, scriptUsageWarning);
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/teams-js/src/public/appId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { validateStringAsAppId } from '../internal/appIdValidation';
* However, there are some older internal/hard-coded apps which violate this schema and use names like
* com.microsoft.teamspace.tab.youtube. For compatibility with these legacy apps, we unfortunately cannot
* securely and completely validate app ids as UUIDs. Based on this, the validation is limited to checking
* for script tags, length, and non-printable characters.
* for script tags, length, and non-printable characters. Validation will be updated in the future to ensure
* the app id is a valid UUID as legacy apps update.
*/
export class AppId {
/**
Expand Down
84 changes: 80 additions & 4 deletions packages/teams-js/src/public/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ApiName, ApiVersionNumber, getApiVersionTag } from '../internal/telemet
import { isNullOrUndefined } from '../internal/typeCheckUtilities';
import { createTeamsAppLink } from '../internal/utils';
import { prefetchOriginsFromCDN } from '../internal/validOrigins';
import { AppId } from '../public/appId';
import { appInitializeHelper } from './app';
import { errorNotSupportedOnPlatform, FrameContexts } from './constants';
import { FrameInfo, ShareDeepLinkParameters, TabInformation, TabInstance, TabInstanceParameters } from './interfaces';
Expand Down Expand Up @@ -383,8 +384,9 @@ export namespace pages {
*
* @param params Parameters for the navigation
* @returns a `Promise` that will resolve if the navigation was successful or reject if it was not
* @throws `Error` if the app ID is not valid or `params.webUrl` is defined but not a valid URL
*/
export function navigateToApp(params: NavigateToAppParams): Promise<void> {
export function navigateToApp(params: AppNavigationParameters | NavigateToAppParams): Promise<void> {
return new Promise<void>((resolve) => {
ensureInitialized(
runtime,
Expand All @@ -399,10 +401,17 @@ export namespace pages {
throw errorNotSupportedOnPlatform;
}
const apiVersionTag: string = getApiVersionTag(pagesTelemetryVersionNumber, ApiName.Pages_NavigateToApp);

if (runtime.isLegacyTeams) {
resolve(sendAndHandleStatusAndReason(apiVersionTag, 'executeDeepLink', createTeamsAppLink(params)));
const typeSafeParameters: AppNavigationParameters = !isAppNavigationParametersObject(params)
? convertNavigateToAppParamsToAppNavigationParameters(params)
: params;
resolve(sendAndHandleStatusAndReason(apiVersionTag, 'executeDeepLink', createTeamsAppLink(typeSafeParameters)));
} else {
resolve(sendAndHandleStatusAndReason(apiVersionTag, 'pages.navigateToApp', params));
const serializedParameters: NavigateToAppParams = isAppNavigationParametersObject(params)
? convertAppNavigationParametersToNavigateToAppParams(params)
: params;
resolve(sendAndHandleStatusAndReason(apiVersionTag, 'pages.navigateToApp', serializedParameters));
}
});
}
Expand Down Expand Up @@ -452,7 +461,10 @@ export namespace pages {
}

/**
* Parameters for the NavigateToApp API
* @deprecated
* This interface has been deprecated in favor of a more type-safe interface using {@link pages.AppNavigationParameters}
*
* Parameters for the {@link pages.navigateToApp} function
*/
export interface NavigateToAppParams {
/**
Expand Down Expand Up @@ -488,6 +500,44 @@ export namespace pages {
chatId?: string;
}

/**
* Type-safer version of parameters for the {@link pages.navigateToApp} function
*/
export interface AppNavigationParameters {
/**
* ID of the app to navigate to
*/
appId: AppId;

/**
* Developer-defined ID of the page to navigate to within the app (formerly called `entityId`)
*/
pageId: string;

/**
* Fallback URL to open if the navigation cannot be completed within the host (e.g., if the target app is not installed)
*/
webUrl?: URL;

/**
* Developer-defined ID describing the content to navigate to within the page. This ID is passed to the application
* via the {@link app.PageInfo.subPageId} property on the {@link app.Context} object (retrieved by calling {@link app.getContext})
*/
subPageId?: string;

/**
* For apps installed as a channel tab, this ID can be supplied to indicate in which Teams channel the app should be opened
* This property has no effect in hosts where apps cannot be opened in channels
*/
channelId?: string;

/**
* Optional ID of the chat or meeting where the app should be opened
* This property has no effect in hosts where apps cannot be opened in chats or meetings
*/
chatId?: string;
}

/**
* Provides APIs for querying and navigating between contextual tabs of an application. Unlike personal tabs,
* contextual tabs are pages associated with a specific context, such as channel or chat.
Expand Down Expand Up @@ -1197,3 +1247,29 @@ export namespace pages {
}
}
}

export function isAppNavigationParametersObject(
obj: pages.AppNavigationParameters | pages.NavigateToAppParams,
): obj is pages.AppNavigationParameters {
return obj.appId instanceof AppId;
}

export function convertNavigateToAppParamsToAppNavigationParameters(
params: pages.NavigateToAppParams,
): pages.AppNavigationParameters {
return {
...params,
appId: new AppId(params.appId),
webUrl: params.webUrl ? new URL(params.webUrl) : undefined,
};
}

export function convertAppNavigationParametersToNavigateToAppParams(
params: pages.AppNavigationParameters,
): pages.NavigateToAppParams {
return {
...params,
appId: params.appId.toString(),
webUrl: params.webUrl ? params.webUrl.toString() : undefined,
};
}
30 changes: 15 additions & 15 deletions packages/teams-js/test/internal/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
validateUuid,
} from '../../src/internal/utils';
import { UUID } from '../../src/internal/uuidObject';
import { pages } from '../../src/public';
import { AppId, pages } from '../../src/public';
import { ClipboardSupportedMimeType } from '../../src/public/interfaces';

describe('utils', () => {
Expand All @@ -24,26 +24,26 @@ describe('utils', () => {
});
describe('createTeamsAppLink', () => {
it('builds a basic URL with an appId and pageId', () => {
const params: pages.NavigateToAppParams = {
appId: 'fe4a8eba-2a31-4737-8e33-e5fae6fee194',
const params: pages.AppNavigationParameters = {
appId: new AppId('fe4a8eba-2a31-4737-8e33-e5fae6fee194'),
pageId: 'tasklist123',
};
const expected = 'https://teams.microsoft.com/l/entity/fe4a8eba-2a31-4737-8e33-e5fae6fee194/tasklist123';
expect(createTeamsAppLink(params)).toBe(expected);
});
it('builds a URL with a webUrl parameter', () => {
const params: pages.NavigateToAppParams = {
appId: 'fe4a8eba-2a31-4737-8e33-e5fae6fee194',
const params: pages.AppNavigationParameters = {
appId: new AppId('fe4a8eba-2a31-4737-8e33-e5fae6fee194'),
pageId: 'tasklist123',
webUrl: 'https://tasklist.example.com/123',
webUrl: new URL('https://tasklist.example.com/123'),
};
const expected =
'https://teams.microsoft.com/l/entity/fe4a8eba-2a31-4737-8e33-e5fae6fee194/tasklist123?webUrl=https%3A%2F%2Ftasklist.example.com%2F123';
expect(createTeamsAppLink(params)).toBe(expected);
});
it('builds a URL with a subPageUrl parameter', () => {
const params: pages.NavigateToAppParams = {
appId: 'fe4a8eba-2a31-4737-8e33-e5fae6fee194',
const params: pages.AppNavigationParameters = {
appId: new AppId('fe4a8eba-2a31-4737-8e33-e5fae6fee194'),
pageId: 'tasklist123',
subPageId: 'task456',
};
Expand All @@ -52,8 +52,8 @@ describe('utils', () => {
expect(createTeamsAppLink(params)).toBe(expected);
});
it('builds a URL with a channelId parameter', () => {
const params: pages.NavigateToAppParams = {
appId: 'fe4a8eba-2a31-4737-8e33-e5fae6fee194',
const params: pages.AppNavigationParameters = {
appId: new AppId('fe4a8eba-2a31-4737-8e33-e5fae6fee194'),
pageId: 'tasklist123',
channelId: '19:[email protected]',
};
Expand All @@ -63,8 +63,8 @@ describe('utils', () => {
});

it('builds a URL with a chatId parameter', () => {
const params: pages.NavigateToAppParams = {
appId: 'fe4a8eba-2a31-4737-8e33-e5fae6fee194',
const params: pages.AppNavigationParameters = {
appId: new AppId('fe4a8eba-2a31-4737-8e33-e5fae6fee194'),
pageId: 'tasklist123',
chatId: '19:[email protected]',
};
Expand All @@ -73,10 +73,10 @@ describe('utils', () => {
expect(createTeamsAppLink(params)).toBe(expected);
});
it('builds a URL with all optional properties', () => {
const params: pages.NavigateToAppParams = {
appId: 'fe4a8eba-2a31-4737-8e33-e5fae6fee194',
const params: pages.AppNavigationParameters = {
appId: new AppId('fe4a8eba-2a31-4737-8e33-e5fae6fee194'),
pageId: 'tasklist123',
webUrl: 'https://tasklist.example.com/123',
webUrl: new URL('https://tasklist.example.com/123'),
channelId: '19:[email protected]',
subPageId: 'task456',
};
Expand Down
51 changes: 46 additions & 5 deletions packages/teams-js/test/public/pages.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { getGenericOnCompleteHandler } from '../../src/internal/utils';
import { app } from '../../src/public/app';
import { errorNotSupportedOnPlatform, FrameContexts } from '../../src/public/constants';
import { FrameInfo, ShareDeepLinkParameters, TabInstance, TabInstanceParameters } from '../../src/public/interfaces';
import { pages } from '../../src/public/pages';
import {
convertAppNavigationParametersToNavigateToAppParams,
convertNavigateToAppParamsToAppNavigationParameters,
isAppNavigationParametersObject,
pages,
} from '../../src/public/pages';
import { latestRuntimeApiVersion } from '../../src/public/runtime';
import { version } from '../../src/public/version';
import {
Expand Down Expand Up @@ -451,6 +456,11 @@ describe('Testing pages module', () => {
subPageId: 'task456',
};

const typeSafeAppNavigationParams: pages.AppNavigationParameters =
convertNavigateToAppParamsToAppNavigationParameters(navigateToAppParams);
const typeSafeAppNavigationParamsWithChat: pages.AppNavigationParameters =
convertNavigateToAppParamsToAppNavigationParameters(navigateToAppParamsWithChat);

it('pages.navigateToApp should not allow calls before initialization', async () => {
await expect(pages.navigateToApp(navigateToAppParams)).rejects.toThrowError(
new Error(errorLibraryNotInitialized),
Expand Down Expand Up @@ -489,7 +499,9 @@ describe('Testing pages module', () => {
await expect(promise).resolves.toBe(undefined);
});

it('pages.navigateToApp should successfully send the navigateToApp message', async () => {
async function validateNavigateToAppMessage(
navigateToAppParams: pages.NavigateToAppParams | pages.AppNavigationParameters,
) {
await utils.initializeWithContext(context);
utils.setRuntimeConfig({ apiVersion: 1, supports: { pages: {} } });

Expand All @@ -500,14 +512,26 @@ describe('Testing pages module', () => {
navigateToAppMessage,
'pages.navigateToApp',
MatcherType.ToStrictEqual,
navigateToAppParams,
isAppNavigationParametersObject(navigateToAppParams)
? convertAppNavigationParametersToNavigateToAppParams(navigateToAppParams)
: navigateToAppParams,
);

await utils.respondToMessage(navigateToAppMessage!, true);
await promise;
}

it('pages.navigateToApp should successfully send the navigateToApp message using serialized parameter', async () => {
validateNavigateToAppMessage(navigateToAppParams);
});

it('pages.navigateToApp should successfully send an executeDeepLink message for legacy teams clients', async () => {
it('pages.navigateToApp should successfully send the navigateToApp message using type-safe parameter', async () => {
validateNavigateToAppMessage(typeSafeAppNavigationParams);
});

async function validateNavigateToAppMessageForLegacyTeams(
navigateToAppParams: pages.NavigateToAppParams | pages.AppNavigationParameters,
) {
await utils.initializeWithContext(context);
utils.setRuntimeConfig({
apiVersion: 1,
Expand All @@ -529,9 +553,19 @@ describe('Testing pages module', () => {

await utils.respondToMessage(executeDeepLinkMessage!, true);
await promise;
}

it('pages.navigateToApp should successfully send an executeDeepLink message for legacy teams clients using a serialized parameter', async () => {
validateNavigateToAppMessageForLegacyTeams(navigateToAppParams);
});

it('pages.navigateToApp should successfully send an executeDeepLink message for legacy teams clients using a type-safe parameter', async () => {
validateNavigateToAppMessageForLegacyTeams(typeSafeAppNavigationParams);
});

it('pages.navigateToApp should successfully send an executeDeepLink message with chat id for legacy teams clients', async () => {
async function validateNavigateToAppMessageForLegacyTeamsWithChat(
navigateToAppParamsWithChat: pages.NavigateToAppParams | pages.AppNavigationParameters,
) {
await utils.initializeWithContext(context);
utils.setRuntimeConfig({
apiVersion: 1,
Expand All @@ -553,6 +587,13 @@ describe('Testing pages module', () => {

await utils.respondToMessage(executeDeepLinkMessage!, true);
await promise;
}

it('pages.navigateToApp should successfully send an executeDeepLink message with chat id for legacy teams clients using serialized parameter', async () => {
validateNavigateToAppMessageForLegacyTeamsWithChat(navigateToAppParamsWithChat);
});
it('pages.navigateToApp should successfully send an executeDeepLink message with chat id for legacy teams clients using type-safe parameter', async () => {
validateNavigateToAppMessageForLegacyTeamsWithChat(typeSafeAppNavigationParamsWithChat);
});
} else {
it(`pages.navigateToApp should not allow calls from ${context} context`, async () => {
Expand Down

0 comments on commit 2de3058

Please sign in to comment.