From c3addae4d73b9a08971d501981a46142e941aea8 Mon Sep 17 00:00:00 2001 From: Jay Dahiya Date: Thu, 2 Jan 2025 11:16:24 -0800 Subject: [PATCH 01/14] Moving prefetch of CDN resources to earlier in the pipeline and adding timeout logic in case it takes too long to respond --- packages/teams-js/src/internal/validOrigins.ts | 9 +++++++-- packages/teams-js/src/public/app/app.ts | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/teams-js/src/internal/validOrigins.ts b/packages/teams-js/src/internal/validOrigins.ts index ebec2773c7..b379509fa8 100644 --- a/packages/teams-js/src/internal/validOrigins.ts +++ b/packages/teams-js/src/internal/validOrigins.ts @@ -5,6 +5,7 @@ import { inServerSideRenderingEnvironment, isValidHttpsURL } from './utils'; let validOriginsCache: string[] = []; const validateOriginLogger = getLogger('validateOrigin'); +const ORIGIN_LIST_TIMEOUT = 1500; export async function prefetchOriginsFromCDN(): Promise { await getValidOriginsListFromCDN(); @@ -19,7 +20,7 @@ async function getValidOriginsListFromCDN(): Promise { return validOriginsCache; } if (!inServerSideRenderingEnvironment()) { - return fetch(validOriginsCdnEndpoint) + return fetch(validOriginsCdnEndpoint, { signal: AbortSignal.timeout(ORIGIN_LIST_TIMEOUT) }) .then((response) => { if (!response.ok) { throw new Error('Invalid Response from Fetch Call'); @@ -34,7 +35,11 @@ async function getValidOriginsListFromCDN(): Promise { }); }) .catch((e) => { - validateOriginLogger('validOrigins fetch call to CDN failed with error: %s. Defaulting to fallback list', e); + if (e.name === 'TimeoutError') { + validateOriginLogger('validOrigins fetch call to CDN failed due to Timeout. Defaulting to fallback list', e); + } else { + validateOriginLogger('validOrigins fetch call to CDN failed with error: %s. Defaulting to fallback list', e); + } validOriginsCache = validOriginsFallback; return validOriginsCache; }); diff --git a/packages/teams-js/src/public/app/app.ts b/packages/teams-js/src/public/app/app.ts index 3d6b2ad3f2..ec72b2992c 100644 --- a/packages/teams-js/src/public/app/app.ts +++ b/packages/teams-js/src/public/app/app.ts @@ -27,6 +27,8 @@ import { import { version } from '../version'; import * as lifecycle from './lifecycle'; +prefetchOriginsFromCDN(); + /** * v2 APIs telemetry file: All of APIs in this capability file should send out API version v2 ONLY */ @@ -615,7 +617,6 @@ logWhereTeamsJsIsBeingUsed(); * @returns Promise that will be fulfilled when initialization has completed, or rejected if the initialization fails or times out */ export function initialize(validMessageOrigins?: string[]): Promise { - prefetchOriginsFromCDN(); return appHelpers.appInitializeHelper( getApiVersionTag(appTelemetryVersionNumber, ApiName.App_Initialize), validMessageOrigins, From 5f2faab2625ef59b4c8adc218e4dc019e7ded767 Mon Sep 17 00:00:00 2001 From: Jay Dahiya Date: Thu, 2 Jan 2025 11:29:52 -0800 Subject: [PATCH 02/14] Added in changefile --- ...soft-teams-js-ceda76cc-75d3-40de-8b4b-cb392a240254.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@microsoft-teams-js-ceda76cc-75d3-40de-8b4b-cb392a240254.json diff --git a/change/@microsoft-teams-js-ceda76cc-75d3-40de-8b4b-cb392a240254.json b/change/@microsoft-teams-js-ceda76cc-75d3-40de-8b4b-cb392a240254.json new file mode 100644 index 0000000000..26774a2204 --- /dev/null +++ b/change/@microsoft-teams-js-ceda76cc-75d3-40de-8b4b-cb392a240254.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Moved prefetch of CDN resources out of initialization workflow and added in timeout based logic", + "packageName": "@microsoft/teams-js", + "email": "jadahiya@microsoft.com", + "dependentChangeType": "patch" +} From a2a0ae10dae195e839f580c01ad8fe60d1e9b989 Mon Sep 17 00:00:00 2001 From: Jay Dahiya Date: Thu, 2 Jan 2025 12:35:06 -0800 Subject: [PATCH 03/14] Added in prefetch file to minimize side effects --- packages/teams-js/package.json | 1 + packages/teams-js/src/internal/prefetch.ts | 3 +++ packages/teams-js/src/public/app/app.ts | 3 --- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 packages/teams-js/src/internal/prefetch.ts diff --git a/packages/teams-js/package.json b/packages/teams-js/package.json index a93788b204..5f70e6a5a8 100644 --- a/packages/teams-js/package.json +++ b/packages/teams-js/package.json @@ -33,6 +33,7 @@ "sideEffects": [ "src/internal/communication.ts", "src/internal/nestedAppAuthUtils.ts", + "src/internal/prefetch.ts", "src/internal/utils.ts", "src/internal/videoEffectsUtils.ts", "src/private/constants.ts", diff --git a/packages/teams-js/src/internal/prefetch.ts b/packages/teams-js/src/internal/prefetch.ts new file mode 100644 index 0000000000..4d123f8ea0 --- /dev/null +++ b/packages/teams-js/src/internal/prefetch.ts @@ -0,0 +1,3 @@ +import { prefetchOriginsFromCDN } from './validOrigins'; + +prefetchOriginsFromCDN(); diff --git a/packages/teams-js/src/public/app/app.ts b/packages/teams-js/src/public/app/app.ts index ec72b2992c..bbf2ca878d 100644 --- a/packages/teams-js/src/public/app/app.ts +++ b/packages/teams-js/src/public/app/app.ts @@ -14,7 +14,6 @@ import * as Handlers from '../../internal/handlers'; // Conflict with some names import { ensureInitializeCalled } from '../../internal/internalAPIs'; import { ApiName, ApiVersionNumber, getApiVersionTag, getLogger } from '../../internal/telemetry'; import { inServerSideRenderingEnvironment } from '../../internal/utils'; -import { prefetchOriginsFromCDN } from '../../internal/validOrigins'; import * as messageChannels from '../../private/messageChannels/messageChannels'; import { ChannelType, FrameContexts, HostClientType, HostName, TeamType, UserTeamRole } from '../constants'; import { @@ -27,8 +26,6 @@ import { import { version } from '../version'; import * as lifecycle from './lifecycle'; -prefetchOriginsFromCDN(); - /** * v2 APIs telemetry file: All of APIs in this capability file should send out API version v2 ONLY */ From 0d919f7d66ab38ad051b91f349061e100d4346d7 Mon Sep 17 00:00:00 2001 From: Jay Dahiya Date: Thu, 2 Jan 2025 12:57:25 -0800 Subject: [PATCH 04/14] Updated changelog --- ...microsoft-teams-js-ceda76cc-75d3-40de-8b4b-cb392a240254.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/change/@microsoft-teams-js-ceda76cc-75d3-40de-8b4b-cb392a240254.json b/change/@microsoft-teams-js-ceda76cc-75d3-40de-8b4b-cb392a240254.json index 26774a2204..5591a79a41 100644 --- a/change/@microsoft-teams-js-ceda76cc-75d3-40de-8b4b-cb392a240254.json +++ b/change/@microsoft-teams-js-ceda76cc-75d3-40de-8b4b-cb392a240254.json @@ -1,6 +1,6 @@ { "type": "patch", - "comment": "Moved prefetch of CDN resources out of initialization workflow and added in timeout based logic", + "comment": "Decoupled prefetch of CDN resources from the initialization workflow and added timeout based fail and fallback path", "packageName": "@microsoft/teams-js", "email": "jadahiya@microsoft.com", "dependentChangeType": "patch" From 0f6483acbcf2bf3c5b34bed58cff34bba7be09b4 Mon Sep 17 00:00:00 2001 From: Jay Dahiya Date: Thu, 2 Jan 2025 14:27:37 -0800 Subject: [PATCH 05/14] Moved prefetch to public --- packages/teams-js/package.json | 4 ++-- packages/teams-js/src/internal/prefetch.ts | 3 --- packages/teams-js/src/public/app/prefetch.ts | 3 +++ packages/teams-js/src/public/index.ts | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) delete mode 100644 packages/teams-js/src/internal/prefetch.ts create mode 100644 packages/teams-js/src/public/app/prefetch.ts diff --git a/packages/teams-js/package.json b/packages/teams-js/package.json index 5f70e6a5a8..de5b0bd2b9 100644 --- a/packages/teams-js/package.json +++ b/packages/teams-js/package.json @@ -33,14 +33,14 @@ "sideEffects": [ "src/internal/communication.ts", "src/internal/nestedAppAuthUtils.ts", - "src/internal/prefetch.ts", "src/internal/utils.ts", "src/internal/videoEffectsUtils.ts", "src/private/constants.ts", "src/private/interfaces.ts", "src/public/constants.ts", "src/public/handlers.ts", - "src/public/interfaces.ts" + "src/public/interfaces.ts", + "src/public/app/prefetch.ts" ], "license": "MIT", "files": [ diff --git a/packages/teams-js/src/internal/prefetch.ts b/packages/teams-js/src/internal/prefetch.ts deleted file mode 100644 index 4d123f8ea0..0000000000 --- a/packages/teams-js/src/internal/prefetch.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { prefetchOriginsFromCDN } from './validOrigins'; - -prefetchOriginsFromCDN(); diff --git a/packages/teams-js/src/public/app/prefetch.ts b/packages/teams-js/src/public/app/prefetch.ts new file mode 100644 index 0000000000..4d1a6cac95 --- /dev/null +++ b/packages/teams-js/src/public/app/prefetch.ts @@ -0,0 +1,3 @@ +import { prefetchOriginsFromCDN } from '../../internal/validOrigins'; + +prefetchOriginsFromCDN(); diff --git a/packages/teams-js/src/public/index.ts b/packages/teams-js/src/public/index.ts index 74358939b7..d15786bbd7 100644 --- a/packages/teams-js/src/public/index.ts +++ b/packages/teams-js/src/public/index.ts @@ -56,6 +56,7 @@ export * as dialog from './dialog/dialog'; export * as nestedAppAuth from './nestedAppAuth'; export * as geoLocation from './geoLocation/geoLocation'; export { getAdaptiveCardSchemaVersion } from './adaptiveCards'; +export * as prefetchOrigins from './app/prefetch'; export * as pages from './pages/pages'; export { addEventListnerFunctionType, From 2beeffc157723c7770df606136d4341b07078406 Mon Sep 17 00:00:00 2001 From: Jay Dahiya Date: Fri, 3 Jan 2025 12:04:37 -0800 Subject: [PATCH 06/14] Added setupTest file to add global fetch polyfill to Jest tests and moved that logic out of utils.ts --- packages/teams-js/jest-setup.cjs | 2 +- packages/teams-js/jest.config.cjs | 5 +++-- packages/teams-js/test/setupTest.ts | 11 +++++++++++ packages/teams-js/test/utils.ts | 10 ---------- 4 files changed, 15 insertions(+), 13 deletions(-) create mode 100644 packages/teams-js/test/setupTest.ts diff --git a/packages/teams-js/jest-setup.cjs b/packages/teams-js/jest-setup.cjs index c29acb64c6..ce22bc1d0f 100644 --- a/packages/teams-js/jest-setup.cjs +++ b/packages/teams-js/jest-setup.cjs @@ -3,7 +3,7 @@ /** * This while TextDecoder is supported in both browser and Node environments, it is not supported in jsdom, which we use for our jest environment. - * To resolve this issue, we polyfill TextDecoder with the node implementation prior to rujnning the tests. + * To resolve this issue, we polyfill TextDecoder with the node implementation prior to running the tests. */ const TextDecoder = require('util').TextDecoder; diff --git a/packages/teams-js/jest.config.cjs b/packages/teams-js/jest.config.cjs index 10bba745cf..4e4a82d6fd 100644 --- a/packages/teams-js/jest.config.cjs +++ b/packages/teams-js/jest.config.cjs @@ -1,4 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires +/* eslint-disable @typescript-eslint/no-var-requires */ const commonSettings = require('../../jest.config.common.js'); const packageVersion = require('./package.json').version; @@ -6,6 +6,7 @@ module.exports = { ...commonSettings, globals: { PACKAGE_VERSION: packageVersion, + fetch: global.fetch, }, - //setupFilesAfterEnv: ['./jest-setup.cjs'], + setupFilesAfterEnv: ['./test/setupTest.ts'], }; diff --git a/packages/teams-js/test/setupTest.ts b/packages/teams-js/test/setupTest.ts new file mode 100644 index 0000000000..945292d3e8 --- /dev/null +++ b/packages/teams-js/test/setupTest.ts @@ -0,0 +1,11 @@ +import { validOriginsFallback } from '../src/internal/constants'; + +global.fetch = jest.fn(() => + Promise.resolve({ + status: 200, + ok: true, + json: async () => { + return { validOriginsFallback }; + }, + } as Response), +); diff --git a/packages/teams-js/test/utils.ts b/packages/teams-js/test/utils.ts index e24cd3bead..f25ceebab1 100644 --- a/packages/teams-js/test/utils.ts +++ b/packages/teams-js/test/utils.ts @@ -1,4 +1,3 @@ -import { validOriginsFallback as validOrigins } from '../src/internal/constants'; import { defaultSDKVersionForCompatCheck } from '../src/internal/constants'; import { GlobalVars } from '../src/internal/globalVars'; import { DOMMessageEvent, ExtendedWindow } from '../src/internal/interfaces'; @@ -138,15 +137,6 @@ export class Utils { }, closed: false, }; - global.fetch = jest.fn(() => - Promise.resolve({ - status: 200, - ok: true, - json: async () => { - return { validOrigins }; - }, - } as Response), - ); } public processMessage: null | ((ev: MessageEvent) => Promise); From b21fc8db86deda71a57533bd084e6dd7405ad67f Mon Sep 17 00:00:00 2001 From: Jay Dahiya Date: Fri, 3 Jan 2025 13:42:57 -0800 Subject: [PATCH 07/14] Added comment describing why fetch is polyfilled --- packages/teams-js/test/setupTest.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/teams-js/test/setupTest.ts b/packages/teams-js/test/setupTest.ts index 945292d3e8..249b5c6c33 100644 --- a/packages/teams-js/test/setupTest.ts +++ b/packages/teams-js/test/setupTest.ts @@ -1,5 +1,10 @@ import { validOriginsFallback } from '../src/internal/constants'; +/** + * We currently run a fetch call to acquire CDN assets as soon as TeamsJS is loaded. + * Since fetch is supported in both browser and Node environments, but not supported in jest/jsdom, + * we polyfill fetch with a mock implementation that acquires the fallback domain list prior to running the tests. + */ global.fetch = jest.fn(() => Promise.resolve({ status: 200, From 74a6e89d8f652d51f393b5320ff0a58ef8975106 Mon Sep 17 00:00:00 2001 From: Jay Dahiya Date: Mon, 6 Jan 2025 09:51:32 -0800 Subject: [PATCH 08/14] Moved prefetch logic into validOrigins logic since validOrigins file should not be treeshaken --- ...soft-teams-js-b7743f88-f4a7-4485-a6b8-2a7512bb9d83.json | 7 +++++++ packages/teams-js/package.json | 4 ++-- packages/teams-js/src/internal/validOrigins.ts | 2 ++ packages/teams-js/src/public/app/prefetch.ts | 3 --- packages/teams-js/src/public/index.ts | 1 - 5 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 change/@microsoft-teams-js-b7743f88-f4a7-4485-a6b8-2a7512bb9d83.json delete mode 100644 packages/teams-js/src/public/app/prefetch.ts diff --git a/change/@microsoft-teams-js-b7743f88-f4a7-4485-a6b8-2a7512bb9d83.json b/change/@microsoft-teams-js-b7743f88-f4a7-4485-a6b8-2a7512bb9d83.json new file mode 100644 index 0000000000..81c9c9776d --- /dev/null +++ b/change/@microsoft-teams-js-b7743f88-f4a7-4485-a6b8-2a7512bb9d83.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "Updated various package versions", + "packageName": "@microsoft/teams-js", + "email": "jadahiya@microsoft.com", + "dependentChangeType": "none" +} diff --git a/packages/teams-js/package.json b/packages/teams-js/package.json index de5b0bd2b9..d48350c9a0 100644 --- a/packages/teams-js/package.json +++ b/packages/teams-js/package.json @@ -34,13 +34,13 @@ "src/internal/communication.ts", "src/internal/nestedAppAuthUtils.ts", "src/internal/utils.ts", + "src/internal/validOrigins.ts", "src/internal/videoEffectsUtils.ts", "src/private/constants.ts", "src/private/interfaces.ts", "src/public/constants.ts", "src/public/handlers.ts", - "src/public/interfaces.ts", - "src/public/app/prefetch.ts" + "src/public/interfaces.ts" ], "license": "MIT", "files": [ diff --git a/packages/teams-js/src/internal/validOrigins.ts b/packages/teams-js/src/internal/validOrigins.ts index b379509fa8..0a8bf4186a 100644 --- a/packages/teams-js/src/internal/validOrigins.ts +++ b/packages/teams-js/src/internal/validOrigins.ts @@ -134,3 +134,5 @@ export function validateOrigin(messageOrigin: URL): Promise { return false; }); } + +prefetchOriginsFromCDN(); diff --git a/packages/teams-js/src/public/app/prefetch.ts b/packages/teams-js/src/public/app/prefetch.ts deleted file mode 100644 index 4d1a6cac95..0000000000 --- a/packages/teams-js/src/public/app/prefetch.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { prefetchOriginsFromCDN } from '../../internal/validOrigins'; - -prefetchOriginsFromCDN(); diff --git a/packages/teams-js/src/public/index.ts b/packages/teams-js/src/public/index.ts index d15786bbd7..74358939b7 100644 --- a/packages/teams-js/src/public/index.ts +++ b/packages/teams-js/src/public/index.ts @@ -56,7 +56,6 @@ export * as dialog from './dialog/dialog'; export * as nestedAppAuth from './nestedAppAuth'; export * as geoLocation from './geoLocation/geoLocation'; export { getAdaptiveCardSchemaVersion } from './adaptiveCards'; -export * as prefetchOrigins from './app/prefetch'; export * as pages from './pages/pages'; export { addEventListnerFunctionType, From 3e85df877cc768f69f87eaadccc7b7bfafb524a6 Mon Sep 17 00:00:00 2001 From: Jay Dahiya Date: Mon, 6 Jan 2025 16:15:00 -0800 Subject: [PATCH 09/14] Created disableCache mechanism so that validOrigins unit tests will go through the entire flow rather than the cached flow --- ...-b7743f88-f4a7-4485-a6b8-2a7512bb9d83.json | 7 - ...-ceda76cc-75d3-40de-8b4b-cb392a240254.json | 2 +- .../teams-js/src/internal/validOrigins.ts | 28 +++- .../test/internal/validOrigins.spec.ts | 153 +++++++++--------- packages/teams-js/test/setupTest.ts | 4 +- 5 files changed, 101 insertions(+), 93 deletions(-) delete mode 100644 change/@microsoft-teams-js-b7743f88-f4a7-4485-a6b8-2a7512bb9d83.json diff --git a/change/@microsoft-teams-js-b7743f88-f4a7-4485-a6b8-2a7512bb9d83.json b/change/@microsoft-teams-js-b7743f88-f4a7-4485-a6b8-2a7512bb9d83.json deleted file mode 100644 index 81c9c9776d..0000000000 --- a/change/@microsoft-teams-js-b7743f88-f4a7-4485-a6b8-2a7512bb9d83.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "none", - "comment": "Updated various package versions", - "packageName": "@microsoft/teams-js", - "email": "jadahiya@microsoft.com", - "dependentChangeType": "none" -} diff --git a/change/@microsoft-teams-js-ceda76cc-75d3-40de-8b4b-cb392a240254.json b/change/@microsoft-teams-js-ceda76cc-75d3-40de-8b4b-cb392a240254.json index 5591a79a41..6cddd0e14a 100644 --- a/change/@microsoft-teams-js-ceda76cc-75d3-40de-8b4b-cb392a240254.json +++ b/change/@microsoft-teams-js-ceda76cc-75d3-40de-8b4b-cb392a240254.json @@ -1,6 +1,6 @@ { "type": "patch", - "comment": "Decoupled prefetch of CDN resources from the initialization workflow and added timeout based fail and fallback path", + "comment": "Decoupled prefetch of CDN resources from the initialization workflow and added fallback when resources could not be fetched in 1.5 seconds", "packageName": "@microsoft/teams-js", "email": "jadahiya@microsoft.com", "dependentChangeType": "patch" diff --git a/packages/teams-js/src/internal/validOrigins.ts b/packages/teams-js/src/internal/validOrigins.ts index 0a8bf4186a..a8b138d71d 100644 --- a/packages/teams-js/src/internal/validOrigins.ts +++ b/packages/teams-js/src/internal/validOrigins.ts @@ -5,7 +5,7 @@ import { inServerSideRenderingEnvironment, isValidHttpsURL } from './utils'; let validOriginsCache: string[] = []; const validateOriginLogger = getLogger('validateOrigin'); -const ORIGIN_LIST_TIMEOUT = 1500; +const ORIGIN_LIST_FETCH_TIMEOUT_IN_MS: number = 1500; export async function prefetchOriginsFromCDN(): Promise { await getValidOriginsListFromCDN(); @@ -15,28 +15,40 @@ function isValidOriginsCacheEmpty(): boolean { return validOriginsCache.length === 0; } -async function getValidOriginsListFromCDN(): Promise { - if (!isValidOriginsCacheEmpty()) { +async function getValidOriginsListFromCDN(disableCache?: boolean): Promise { + if (!isValidOriginsCacheEmpty() && !disableCache) { return validOriginsCache; } if (!inServerSideRenderingEnvironment()) { - return fetch(validOriginsCdnEndpoint, { signal: AbortSignal.timeout(ORIGIN_LIST_TIMEOUT) }) + validateOriginLogger('Initiating fetch call to acquire valid origins list from CDN'); + if (disableCache) { + console.log('Starting fetch'); + } + return fetch(validOriginsCdnEndpoint, { signal: AbortSignal.timeout(ORIGIN_LIST_FETCH_TIMEOUT_IN_MS) }) .then((response) => { + if (disableCache) { + console.log('retrieved fetch'); + } if (!response.ok) { throw new Error('Invalid Response from Fetch Call'); } + validateOriginLogger('Fetch call completed and retrieved valid origins list from CDN'); return response.json().then((validOriginsCDN) => { if (isValidOriginsJSONValid(JSON.stringify(validOriginsCDN))) { validOriginsCache = validOriginsCDN.validOrigins; return validOriginsCache; } else { - throw new Error('Valid Origins List Is Invalid'); + console.log('Hi'); + throw new Error('Valid origins list retrieved from CDN is invalid'); } }); }) .catch((e) => { if (e.name === 'TimeoutError') { - validateOriginLogger('validOrigins fetch call to CDN failed due to Timeout. Defaulting to fallback list', e); + validateOriginLogger( + `validOrigins fetch call to CDN failed due to Timeout of ${ORIGIN_LIST_FETCH_TIMEOUT_IN_MS} ms. Defaulting to fallback list`, + e, + ); } else { validateOriginLogger('validOrigins fetch call to CDN failed with error: %s. Defaulting to fallback list', e); } @@ -102,8 +114,8 @@ function validateHostAgainstPattern(pattern: string, host: string): boolean { * @internal * Limited to Microsoft-internal use */ -export function validateOrigin(messageOrigin: URL): Promise { - return getValidOriginsListFromCDN().then((validOriginsList) => { +export function validateOrigin(messageOrigin: URL, disableCache?: boolean): Promise { + return getValidOriginsListFromCDN(disableCache).then((validOriginsList) => { // Check whether the url is in the pre-known allowlist or supplied by user if (!isValidHttpsURL(messageOrigin)) { validateOriginLogger( diff --git a/packages/teams-js/test/internal/validOrigins.spec.ts b/packages/teams-js/test/internal/validOrigins.spec.ts index 8793637266..af31f20786 100644 --- a/packages/teams-js/test/internal/validOrigins.spec.ts +++ b/packages/teams-js/test/internal/validOrigins.spec.ts @@ -3,6 +3,9 @@ import { validateOrigin } from '../../src/internal/validOrigins'; import * as app from '../../src/public/app/app'; import { _minRuntimeConfigToUninitialize } from '../../src/public/runtime'; import { Utils } from '../utils'; +//We need this now because our code prefetches the CDN url and caches the response. This has the side effect of bypassing all future fetch calls. +const disableCache = true; + describe('validOrigins', () => { describe('testing main validOrigins flow', () => { let utils: Utils = new Utils(); @@ -26,114 +29,114 @@ describe('validOrigins', () => { }); it('validateOrigin returns true if origin is in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://teams.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns true if origin for subdomains in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://test.www.office.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns false if origin is not in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://badorigin.example.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if origin is not an exact match in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://team.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns true if origin is valid origin supplied by user ', async () => { const messageOrigin = new URL('https://testorigin.example.com'); GlobalVars.additionalValidOrigins = [messageOrigin.origin]; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns false if origin is not supplied by user', async () => { const messageOrigin = new URL('https://badorigin.example.com'); GlobalVars.additionalValidOrigins = ['https://testorigin.example.com']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns true if origin for subdomains is in the user supplied list', async () => { const messageOrigin = new URL('https://subdomain.badorigin.example.com'); GlobalVars.additionalValidOrigins = ['https://*.badorigin.example.com']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns false if origin for subdomains is not in the user supplied list', async () => { const messageOrigin = new URL('https://subdomain.badorigin.example.com'); GlobalVars.additionalValidOrigins = ['https://*.testorigin.example.com']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if the port number of valid origin is not in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://local.teams.live.com:4000'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if the port number of valid origin is not in the user supplied list', async () => { const messageOrigin = new URL('https://testorigin.example.com:4000'); GlobalVars.additionalValidOrigins = ['https://testorigin.example.com:8080']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns true if the port number of valid origin is in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://local.teams.live.com:8080'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns true if the port number of valid origin is in the user supplied list', async () => { const messageOrigin = new URL('https://testorigin.example.com:8080'); GlobalVars.additionalValidOrigins = ['https://testorigin.example.com:8080']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns false if origin has extra appended', async () => { const messageOrigin = new URL('https://teams.microsoft.com.evil.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it("validateOrigin returns false if the protocol of origin is not 'https:'", async () => { /* eslint-disable-next-line @microsoft/sdl/no-insecure-url */ /* Intentionally using http here because of what it is testing */ const messageOrigin = new URL('http://teams.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if first end of origin is not matched valid subdomains in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://myteams.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if first end of origin is not matched valid subdomains in the user supplied list', async () => { const messageOrigin = new URL('https://myteams.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); GlobalVars.additionalValidOrigins = ['https://*.teams.microsoft.com']; expect(result).toBe(false); }); it('validateOrigin returns false if origin for subdomains does not match in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://a.b.sharepoint.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if origin for subdomains does not match in the user supplied list', async () => { const messageOrigin = new URL('https://a.b.testdomain.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); GlobalVars.additionalValidOrigins = ['https://*.testdomain.com']; expect(result).toBe(false); }); it('validateOrigin returns true for high-profile *.cloud.microsoft origins', async () => { let messageOrigin = new URL('https://teams.cloud.microsoft'); - let result = await validateOrigin(messageOrigin); + let result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); messageOrigin = new URL('https://outlook.cloud.microsoft'); - result = await validateOrigin(messageOrigin); + result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); messageOrigin = new URL('https://m365.cloud.microsoft'); - result = await validateOrigin(messageOrigin); + result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); }); @@ -167,100 +170,100 @@ describe('validOrigins', () => { }); it('validateOrigin returns true if origin is in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://teams.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns true if origin for subdomains in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://test.www.office.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns false if origin is not in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://badorigin.example.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if origin is not an exact match in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://team.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns true if origin is valid origin supplied by user ', async () => { const messageOrigin = new URL('https://testorigin.example.com'); GlobalVars.additionalValidOrigins = [messageOrigin.origin]; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns false if origin is not supplied by user', async () => { const messageOrigin = new URL('https://badorigin.example.com'); GlobalVars.additionalValidOrigins = ['https://testorigin.example.com']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns true if origin for subdomains is in the user supplied list', async () => { const messageOrigin = new URL('https://subdomain.badorigin.example.com'); GlobalVars.additionalValidOrigins = ['https://*.badorigin.example.com']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns false if origin for subdomains is not in the user supplied list', async () => { const messageOrigin = new URL('https://subdomain.badorigin.example.com'); GlobalVars.additionalValidOrigins = ['https://*.testorigin.example.com']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if the port number of valid origin is not in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://local.teams.live.com:4000'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if the port number of valid origin is not in the user supplied list', async () => { const messageOrigin = new URL('https://testorigin.example.com:4000'); GlobalVars.additionalValidOrigins = ['https://testorigin.example.com:8080']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns true if the port number of valid origin is in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://local.teams.live.com:8080'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns true if the port number of valid origin is in the user supplied list', async () => { const messageOrigin = new URL('https://testorigin.example.com:8080'); GlobalVars.additionalValidOrigins = ['https://testorigin.example.com:8080']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns false if origin has extra appended', async () => { const messageOrigin = new URL('https://teams.microsoft.com.evil.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it("validateOrigin returns false if the protocol of origin is not 'https:'", async () => { /* eslint-disable-next-line @microsoft/sdl/no-insecure-url */ /* Intentionally using http here because of what it is testing */ const messageOrigin = new URL('http://teams.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if first end of origin is not matched valid subdomains in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://myteams.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if first end of origin is not matched valid subdomains in the user supplied list', async () => { const messageOrigin = new URL('https://myteams.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); GlobalVars.additionalValidOrigins = ['https://*.teams.microsoft.com']; expect(result).toBe(false); }); it('validateOrigin returns false if origin for subdomains does not match in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://a.b.sharepoint.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if origin for subdomains does not match in the user supplied list', async () => { const messageOrigin = new URL('https://a.b.testdomain.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); GlobalVars.additionalValidOrigins = ['https://*.testdomain.com']; expect(result).toBe(false); }); @@ -288,100 +291,100 @@ describe('validOrigins', () => { }); it('validateOrigin returns true if origin is in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://teams.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns true if origin for subdomains in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://test.www.office.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns false if origin is not in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://badorigin.example.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if origin is not an exact match in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://team.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns true if origin is valid origin supplied by user ', async () => { const messageOrigin = new URL('https://testorigin.example.com'); GlobalVars.additionalValidOrigins = [messageOrigin.origin]; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns false if origin is not supplied by user', async () => { const messageOrigin = new URL('https://badorigin.example.com'); GlobalVars.additionalValidOrigins = ['https://testorigin.example.com']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns true if origin for subdomains is in the user supplied list', async () => { const messageOrigin = new URL('https://subdomain.badorigin.example.com'); GlobalVars.additionalValidOrigins = ['https://*.badorigin.example.com']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns false if origin for subdomains is not in the user supplied list', async () => { const messageOrigin = new URL('https://subdomain.badorigin.example.com'); GlobalVars.additionalValidOrigins = ['https://*.testorigin.example.com']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if the port number of valid origin is not in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://local.teams.live.com:4000'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if the port number of valid origin is not in the user supplied list', async () => { const messageOrigin = new URL('https://testorigin.example.com:4000'); GlobalVars.additionalValidOrigins = ['https://testorigin.example.com:8080']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns true if the port number of valid origin is in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://local.teams.live.com:8080'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns true if the port number of valid origin is in the user supplied list', async () => { const messageOrigin = new URL('https://testorigin.example.com:8080'); GlobalVars.additionalValidOrigins = ['https://testorigin.example.com:8080']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns false if origin has extra appended', async () => { const messageOrigin = new URL('https://teams.microsoft.com.evil.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it("validateOrigin returns false if the protocol of origin is not 'https:'", async () => { /* eslint-disable-next-line @microsoft/sdl/no-insecure-url */ /* Intentionally using http here because of what it is testing */ const messageOrigin = new URL('http://teams.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if first end of origin is not matched valid subdomains in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://myteams.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if first end of origin is not matched valid subdomains in the user supplied list', async () => { const messageOrigin = new URL('https://myteams.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); GlobalVars.additionalValidOrigins = ['https://*.teams.microsoft.com']; expect(result).toBe(false); }); it('validateOrigin returns false if origin for subdomains does not match in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://a.b.sharepoint.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if origin for subdomains does not match in the user supplied list', async () => { const messageOrigin = new URL('https://a.b.testdomain.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); GlobalVars.additionalValidOrigins = ['https://*.testdomain.com']; expect(result).toBe(false); }); @@ -409,100 +412,100 @@ describe('validOrigins', () => { }); it('validateOrigin returns true if origin is in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://teams.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns true if origin for subdomains in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://test.www.office.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns false if origin is not in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://badorigin.example.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if origin is not an exact match in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://team.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns true if origin is valid origin supplied by user ', async () => { const messageOrigin = new URL('https://testorigin.example.com'); GlobalVars.additionalValidOrigins = [messageOrigin.origin]; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns false if origin is not supplied by user', async () => { const messageOrigin = new URL('https://badorigin.example.com'); GlobalVars.additionalValidOrigins = ['https://testorigin.example.com']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns true if origin for subdomains is in the user supplied list', async () => { const messageOrigin = new URL('https://subdomain.badorigin.example.com'); GlobalVars.additionalValidOrigins = ['https://*.badorigin.example.com']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns false if origin for subdomains is not in the user supplied list', async () => { const messageOrigin = new URL('https://subdomain.badorigin.example.com'); GlobalVars.additionalValidOrigins = ['https://*.testorigin.example.com']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if the port number of valid origin is not in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://local.teams.live.com:4000'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if the port number of valid origin is not in the user supplied list', async () => { const messageOrigin = new URL('https://testorigin.example.com:4000'); GlobalVars.additionalValidOrigins = ['https://testorigin.example.com:8080']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns true if the port number of valid origin is in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://local.teams.live.com:8080'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns true if the port number of valid origin is in the user supplied list', async () => { const messageOrigin = new URL('https://testorigin.example.com:8080'); GlobalVars.additionalValidOrigins = ['https://testorigin.example.com:8080']; - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(true); }); it('validateOrigin returns false if origin has extra appended', async () => { const messageOrigin = new URL('https://teams.microsoft.com.evil.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it("validateOrigin returns false if the protocol of origin is not 'https:'", async () => { /* eslint-disable-next-line @microsoft/sdl/no-insecure-url */ /* Intentionally using http here because of what it is testing */ const messageOrigin = new URL('http://teams.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if first end of origin is not matched valid subdomains in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://myteams.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if first end of origin is not matched valid subdomains in the user supplied list', async () => { const messageOrigin = new URL('https://myteams.microsoft.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); GlobalVars.additionalValidOrigins = ['https://*.teams.microsoft.com']; expect(result).toBe(false); }); it('validateOrigin returns false if origin for subdomains does not match in teams pre-known allowlist', async () => { const messageOrigin = new URL('https://a.b.sharepoint.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); expect(result).toBe(false); }); it('validateOrigin returns false if origin for subdomains does not match in the user supplied list', async () => { const messageOrigin = new URL('https://a.b.testdomain.com'); - const result = await validateOrigin(messageOrigin); + const result = await validateOrigin(messageOrigin, disableCache); GlobalVars.additionalValidOrigins = ['https://*.testdomain.com']; expect(result).toBe(false); }); diff --git a/packages/teams-js/test/setupTest.ts b/packages/teams-js/test/setupTest.ts index 249b5c6c33..2f671db12a 100644 --- a/packages/teams-js/test/setupTest.ts +++ b/packages/teams-js/test/setupTest.ts @@ -2,7 +2,7 @@ import { validOriginsFallback } from '../src/internal/constants'; /** * We currently run a fetch call to acquire CDN assets as soon as TeamsJS is loaded. - * Since fetch is supported in both browser and Node environments, but not supported in jest/jsdom, + * Since fet ch is supported in both browser and Node environments, but not supported in jest/jsdom, * we polyfill fetch with a mock implementation that acquires the fallback domain list prior to running the tests. */ global.fetch = jest.fn(() => @@ -10,7 +10,7 @@ global.fetch = jest.fn(() => status: 200, ok: true, json: async () => { - return { validOriginsFallback }; + return { validOrigins: validOriginsFallback }; }, } as Response), ); From 2e94704ff1d88e21d22d94cc4389d52e3e5bf7c8 Mon Sep 17 00:00:00 2001 From: Jay Dahiya Date: Mon, 6 Jan 2025 16:15:49 -0800 Subject: [PATCH 10/14] Created disableCache mechanism so that validOrigins unit tests will go through the entire flow rather than the cached flow --- packages/teams-js/src/internal/validOrigins.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/teams-js/src/internal/validOrigins.ts b/packages/teams-js/src/internal/validOrigins.ts index a8b138d71d..24ffbe19da 100644 --- a/packages/teams-js/src/internal/validOrigins.ts +++ b/packages/teams-js/src/internal/validOrigins.ts @@ -21,14 +21,9 @@ async function getValidOriginsListFromCDN(disableCache?: boolean): Promise { - if (disableCache) { - console.log('retrieved fetch'); - } if (!response.ok) { throw new Error('Invalid Response from Fetch Call'); } @@ -38,7 +33,6 @@ async function getValidOriginsListFromCDN(disableCache?: boolean): Promise Date: Mon, 6 Jan 2025 16:19:02 -0800 Subject: [PATCH 11/14] Removed unneeded error variable in logger call --- packages/teams-js/src/internal/validOrigins.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/teams-js/src/internal/validOrigins.ts b/packages/teams-js/src/internal/validOrigins.ts index 24ffbe19da..be2cf10fa3 100644 --- a/packages/teams-js/src/internal/validOrigins.ts +++ b/packages/teams-js/src/internal/validOrigins.ts @@ -41,7 +41,6 @@ async function getValidOriginsListFromCDN(disableCache?: boolean): Promise Date: Tue, 7 Jan 2025 16:03:32 -0800 Subject: [PATCH 12/14] Added in unit tests --- .../teams-js/src/internal/validOrigins.ts | 8 +- .../test/internal/validOrigins.spec.ts | 83 +++++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/packages/teams-js/src/internal/validOrigins.ts b/packages/teams-js/src/internal/validOrigins.ts index be2cf10fa3..98b64917f6 100644 --- a/packages/teams-js/src/internal/validOrigins.ts +++ b/packages/teams-js/src/internal/validOrigins.ts @@ -22,8 +22,12 @@ async function getValidOriginsListFromCDN(disableCache?: boolean): Promise controller.abort(), ORIGIN_LIST_FETCH_TIMEOUT_IN_MS); + + return fetch(validOriginsCdnEndpoint, { signal: controller.signal }) .then((response) => { + clearTimeout(timeoutId); if (!response.ok) { throw new Error('Invalid Response from Fetch Call'); } @@ -38,7 +42,7 @@ async function getValidOriginsListFromCDN(disableCache?: boolean): Promise { - if (e.name === 'TimeoutError') { + if (e.name === 'AbortError') { validateOriginLogger( `validOrigins fetch call to CDN failed due to Timeout of ${ORIGIN_LIST_FETCH_TIMEOUT_IN_MS} ms. Defaulting to fallback list`, ); diff --git a/packages/teams-js/test/internal/validOrigins.spec.ts b/packages/teams-js/test/internal/validOrigins.spec.ts index af31f20786..d5f122fe0b 100644 --- a/packages/teams-js/test/internal/validOrigins.spec.ts +++ b/packages/teams-js/test/internal/validOrigins.spec.ts @@ -510,4 +510,87 @@ describe('validOrigins', () => { expect(result).toBe(false); }); }); + describe('testing fetch timeout flow', () => { + let utils: Utils = new Utils(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let timeoutSpy; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let abortSpy; + beforeEach(() => { + // Set a mock window for testing + utils = new Utils(); + utils.mockWindow.parent = undefined; + app._initialize(utils.mockWindow); + GlobalVars.isFramelessWindow = false; + jest.useFakeTimers(); + + global.AbortController.prototype.abort = jest.fn(() => { + throw new Error('AbortError'); + }); + + timeoutSpy = jest.spyOn(global, 'setTimeout'); + abortSpy = jest.spyOn(AbortController.prototype, 'abort'); + + global.fetch = jest.fn( + () => + new Promise((resolve) => { + jest.advanceTimersByTime(1600); + resolve({ + status: 200, + ok: true, + json: async () => { + return { validOrigins: ['example.com'] }; + }, + } as Response); + }), + ); + }); + + afterAll(() => { + GlobalVars.isFramelessWindow = false; + }); + afterEach(() => { + // Reset the object since it's a singleton + if (app._uninitialize) { + utils.setRuntimeConfig(_minRuntimeConfigToUninitialize); + app._uninitialize(); + } + jest.restoreAllMocks(); + jest.clearAllTimers(); + }); + it('validateOrigin returns true if fetch call times out and domain is in fallback list', async () => { + const timedOutOrigin = new URL('https://example.com'); + const timedOutResult = await validateOrigin(timedOutOrigin, disableCache); + expect(abortSpy).toBeCalledTimes(1); + expect(timedOutResult).toBe(false); + const messageOrigin = new URL('https://teams.microsoft.com'); + const fallbackResult = await validateOrigin(messageOrigin, disableCache); + expect(fallbackResult).toBe(true); + }); + it('validateOrigin returns true if fetch call does not time out', async () => { + global.fetch = jest.fn( + () => + new Promise((resolve) => { + resolve({ + status: 200, + ok: true, + json: async () => { + return { validOrigins: ['example.com'] }; + }, + } as Response); + }), + ); + + const messageOrigin = new URL('https://example.com'); + const result = await validateOrigin(messageOrigin, disableCache); + expect(abortSpy).toBeCalledTimes(0); + expect(result).toBe(true); + }); + it('validateOrigin returns false if fetch call times out and domain is not in fallback list', async () => { + const messageOrigin = new URL('https://example.com'); + const result = await validateOrigin(messageOrigin, disableCache); + expect(abortSpy).toBeCalledTimes(1); + expect(result).toBe(false); + }); + }); }); From 34bde9df20cc0cd34faa947e6b0b3d18649a2719 Mon Sep 17 00:00:00 2001 From: Jay Dahiya Date: Thu, 23 Jan 2025 16:27:06 -0800 Subject: [PATCH 13/14] Fixed typo and renamed disableCache to shouldDisableCache --- packages/teams-js/src/internal/validOrigins.ts | 4 ++-- packages/teams-js/test/setupTest.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/teams-js/src/internal/validOrigins.ts b/packages/teams-js/src/internal/validOrigins.ts index 98b64917f6..c76d46d42e 100644 --- a/packages/teams-js/src/internal/validOrigins.ts +++ b/packages/teams-js/src/internal/validOrigins.ts @@ -15,8 +15,8 @@ function isValidOriginsCacheEmpty(): boolean { return validOriginsCache.length === 0; } -async function getValidOriginsListFromCDN(disableCache?: boolean): Promise { - if (!isValidOriginsCacheEmpty() && !disableCache) { +async function getValidOriginsListFromCDN(shouldDisableCache: boolean = false): Promise { + if (!isValidOriginsCacheEmpty() && !shouldDisableCache) { return validOriginsCache; } if (!inServerSideRenderingEnvironment()) { diff --git a/packages/teams-js/test/setupTest.ts b/packages/teams-js/test/setupTest.ts index 2f671db12a..78939c87c0 100644 --- a/packages/teams-js/test/setupTest.ts +++ b/packages/teams-js/test/setupTest.ts @@ -2,7 +2,7 @@ import { validOriginsFallback } from '../src/internal/constants'; /** * We currently run a fetch call to acquire CDN assets as soon as TeamsJS is loaded. - * Since fet ch is supported in both browser and Node environments, but not supported in jest/jsdom, + * Since fetch is supported in both browser and Node environments, but not supported in jest/jsdom, * we polyfill fetch with a mock implementation that acquires the fallback domain list prior to running the tests. */ global.fetch = jest.fn(() => From c3097b431bfa9fbc7f3f5fedf3989a9dea3ff5d3 Mon Sep 17 00:00:00 2001 From: Jay Dahiya Date: Thu, 23 Jan 2025 16:45:54 -0800 Subject: [PATCH 14/14] Moving timeout to constants file and exporting it --- packages/teams-js/src/internal/constants.ts | 9 +++++++++ packages/teams-js/src/internal/validOrigins.ts | 3 +-- packages/teams-js/test/internal/validOrigins.spec.ts | 3 ++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/teams-js/src/internal/constants.ts b/packages/teams-js/src/internal/constants.ts index b3679e7ac7..5b54504add 100644 --- a/packages/teams-js/src/internal/constants.ts +++ b/packages/teams-js/src/internal/constants.ts @@ -128,6 +128,15 @@ const validOriginsLocal = validOriginsJSON; */ export const validOriginsFallback = validOriginsLocal.validOrigins; +/** + * @hidden + * Timeout length for Fetch Call for Valid Origins + * + * @internal + * Limited to Microsoft-internal use + */ +export const ORIGIN_LIST_FETCH_TIMEOUT_IN_MS: number = 1500; + /** * @hidden * CDN endpoint of the list of valid origins diff --git a/packages/teams-js/src/internal/validOrigins.ts b/packages/teams-js/src/internal/validOrigins.ts index c76d46d42e..6b3b3c5810 100644 --- a/packages/teams-js/src/internal/validOrigins.ts +++ b/packages/teams-js/src/internal/validOrigins.ts @@ -1,11 +1,10 @@ -import { validOriginsCdnEndpoint, validOriginsFallback } from './constants'; +import { ORIGIN_LIST_FETCH_TIMEOUT_IN_MS, validOriginsCdnEndpoint, validOriginsFallback } from './constants'; import { GlobalVars } from './globalVars'; import { getLogger } from './telemetry'; import { inServerSideRenderingEnvironment, isValidHttpsURL } from './utils'; let validOriginsCache: string[] = []; const validateOriginLogger = getLogger('validateOrigin'); -const ORIGIN_LIST_FETCH_TIMEOUT_IN_MS: number = 1500; export async function prefetchOriginsFromCDN(): Promise { await getValidOriginsListFromCDN(); diff --git a/packages/teams-js/test/internal/validOrigins.spec.ts b/packages/teams-js/test/internal/validOrigins.spec.ts index d5f122fe0b..d8ba996126 100644 --- a/packages/teams-js/test/internal/validOrigins.spec.ts +++ b/packages/teams-js/test/internal/validOrigins.spec.ts @@ -1,3 +1,4 @@ +import { ORIGIN_LIST_FETCH_TIMEOUT_IN_MS } from '../../src/internal/constants'; import { GlobalVars } from '../../src/internal/globalVars'; import { validateOrigin } from '../../src/internal/validOrigins'; import * as app from '../../src/public/app/app'; @@ -534,7 +535,7 @@ describe('validOrigins', () => { global.fetch = jest.fn( () => new Promise((resolve) => { - jest.advanceTimersByTime(1600); + jest.advanceTimersByTime(ORIGIN_LIST_FETCH_TIMEOUT_IN_MS); resolve({ status: 200, ok: true,