From ea6fc11db74ff3cb26768f1a41804a455ee26da3 Mon Sep 17 00:00:00 2001 From: Shivam Kumar Date: Sun, 24 May 2026 22:13:39 +0530 Subject: [PATCH 1/4] SCAL-314421 Added overrideHistoryState flag to control iframe history behavior --- src/embed/ts-embed.spec.ts | 40 ++++++++++++++++++++++++++++++++++++++ src/embed/ts-embed.ts | 4 ++++ src/types.ts | 21 ++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/src/embed/ts-embed.spec.ts b/src/embed/ts-embed.spec.ts index 63625024..72df141d 100644 --- a/src/embed/ts-embed.spec.ts +++ b/src/embed/ts-embed.spec.ts @@ -2281,6 +2281,46 @@ describe('Unit test case for ts embed', () => { }); }); + it('Sets the overrideHistoryState param', async () => { + const appEmbed = new AppEmbed(getRootEl(), { + frameParams: { + width: '100%', + height: '100%', + }, + overrideHistoryState: true, + }); + await appEmbed.render(); + expectUrlToHaveParamsWithValues(getIFrameSrc(), { + overrideHistoryState: true, + }); + }); + + it('Sets the overrideHistoryState param to false', async () => { + const appEmbed = new AppEmbed(getRootEl(), { + frameParams: { + width: '100%', + height: '100%', + }, + overrideHistoryState: false, + }); + await appEmbed.render(); + expectUrlToHaveParamsWithValues(getIFrameSrc(), { + overrideHistoryState: false, + }); + }); + + it('Should not add overrideHistoryState param when it is undefined', async () => { + const appEmbed = new AppEmbed(getRootEl(), { + frameParams: { + width: '100%', + height: '100%', + }, + }); + await appEmbed.render(); + const url = getIFrameSrc(); + expect(url).not.toContain('overrideHistoryState'); + }); + it('Should not add contextMenuEnabledOnWhichClick flag to the iframe src when it is not passed', async () => { const liveboardEmbed = new LiveboardEmbed(getRootEl(), { ...defaultViewConfig, diff --git a/src/embed/ts-embed.ts b/src/embed/ts-embed.ts index 96d834a2..b1fc0ac1 100644 --- a/src/embed/ts-embed.ts +++ b/src/embed/ts-embed.ts @@ -758,6 +758,7 @@ export class TsEmbed { insertInToSlide, disableRedirectionLinksInNewTab, overrideOrgId, + overrideHistoryState, exposeTranslationIDs, primaryAction, } = this.viewConfig; @@ -856,6 +857,9 @@ export class TsEmbed { if (overrideOrgId !== undefined) { queryParams[Param.OverrideOrgId] = overrideOrgId; } + if (overrideHistoryState !== undefined) { + queryParams[Param.OverrideHistoryState] = overrideHistoryState; + } if (this.isPreAuthCacheEnabled()) { queryParams[Param.preAuthCache] = true; diff --git a/src/types.ts b/src/types.ts index 4400136f..c039863f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1179,6 +1179,26 @@ export interface BaseViewConfig extends ApiInterceptFlags { * ``` */ overrideOrgId?: number; + /** + * Overrides the browser history behavior for embedding application users. + * This parameter changes standard history navigation (`pushState`) into a + * state replacement (`replaceState`), preventing users from getting trapped in + * back-button loops inside the embedded iframe environment. + * The `overrideHistoryState` setting is honoured only if the + * application is running within an embedded context. + * + * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed` + * @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl + * @example + * ```js + * // Replace with embed component name. For example, AppEmbed, SearchEmbed, or LiveboardEmbed + * const embed = new ('#tsEmbed', { + * ... // other embed view config + * overrideHistoryState: true, + * }); + * ``` + */ + overrideHistoryState?: boolean; /** * Flag to override the *Open Link in New Tab* context * menu option. @@ -6111,6 +6131,7 @@ export enum Param { SpotterEnabled = 'isSpotterExperienceEnabled', IsUnifiedSearchExperienceEnabled = 'isUnifiedSearchExperienceEnabled', OverrideOrgId = 'orgId', + OverrideHistoryState = 'overrideHistoryState', OauthPollingInterval = 'oAuthPollingInterval', IsForceRedirect = 'isForceRedirect', DataSourceId = 'dataSourceId', From b3e8c8e73a5d3684cecd25e71d72e3cb7ed2483e Mon Sep 17 00:00:00 2001 From: Shivam Kumar Date: Sun, 24 May 2026 23:57:56 +0530 Subject: [PATCH 2/4] SCAL-311941 fix the docs --- src/types.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/types.ts b/src/types.ts index c039863f..7991493d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1181,10 +1181,10 @@ export interface BaseViewConfig extends ApiInterceptFlags { overrideOrgId?: number; /** * Overrides the browser history behavior for embedding application users. - * This parameter changes standard history navigation (`pushState`) into a - * state replacement (`replaceState`), preventing users from getting trapped in + * This parameter changes standard history navigation (pushState) into a + * state replacement (replaceState), preventing users from getting trapped in * back-button loops inside the embedded iframe environment. - * The `overrideHistoryState` setting is honoured only if the + * The overrideHistoryState setting is honored only if the * application is running within an embedded context. * * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed` @@ -1193,8 +1193,8 @@ export interface BaseViewConfig extends ApiInterceptFlags { * ```js * // Replace with embed component name. For example, AppEmbed, SearchEmbed, or LiveboardEmbed * const embed = new ('#tsEmbed', { - * ... // other embed view config - * overrideHistoryState: true, + * // ... other embed view config + * overrideHistoryState: true, * }); * ``` */ From 7a60d5a8604a70378b9dca86cfca8a3d476f5848 Mon Sep 17 00:00:00 2001 From: Shivam Kumar Date: Sun, 31 May 2026 19:50:40 +0530 Subject: [PATCH 3/4] SCAL-314421 added the overrideHistoryState for navigateToPage --- src/embed/app.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/embed/app.ts b/src/embed/app.ts index b68e0fb1..5f2715cb 100644 --- a/src/embed/app.ts +++ b/src/embed/app.ts @@ -1335,7 +1335,8 @@ export class AppEmbed extends V1Embed { logger.log('Please call render before invoking this method'); return; } - if (noReload) { + const overrideHistoryState = this.viewConfig?.overrideHistoryState; + if (noReload || overrideHistoryState) { this.trigger(HostEvent.Navigate, path); } else { if (typeof path !== 'string') { From 1e8bf3b000cb54b1693eca01d209825dad27956e Mon Sep 17 00:00:00 2001 From: Shivam Kumar Date: Sun, 31 May 2026 21:14:06 +0530 Subject: [PATCH 4/4] SCAL-314421 added test --- src/embed/app.spec.ts | 110 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/src/embed/app.spec.ts b/src/embed/app.spec.ts index 954785fb..d4ccfa62 100644 --- a/src/embed/app.spec.ts +++ b/src/embed/app.spec.ts @@ -1709,6 +1709,116 @@ describe('App embed tests', () => { 'Please call render before invoking this method', ); }); + + describe('overrideHistoryState flag', () => { + test('navigateToPage with overrideHistoryState=true should trigger HostEvent.Navigate', async () => { + mockMessageChannel(); + const appEmbed = new AppEmbed(getRootEl(), { + frameParams: { + width: '100%', + height: '100%', + }, + overrideHistoryState: true, + }); + await appEmbed.render(); + + const iframe = getIFrameEl(); + iframe.contentWindow.postMessage = jest.fn(); + appEmbed.navigateToPage(path, false); + + expect(iframe.contentWindow.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ + type: HostEvent.Navigate, + data: path, + }), + `http://${thoughtSpotHost}`, + expect.anything(), + ); + }); + + test('navigateToPage with overrideHistoryState=true and path as number should trigger HostEvent.Navigate', async () => { + mockMessageChannel(); + const appEmbed = new AppEmbed(getRootEl(), { + frameParams: { + width: '100%', + height: '100%', + }, + overrideHistoryState: true, + }); + await appEmbed.render(); + + const iframe = getIFrameEl(); + iframe.contentWindow.postMessage = jest.fn(); + appEmbed.navigateToPage(-1, false); + + expect(iframe.contentWindow.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ + type: HostEvent.Navigate, + data: -1, + }), + `http://${thoughtSpotHost}`, + expect.anything(), + ); + }); + + test('navigateToPage with overrideHistoryState=false and noReload=false should update iframe src', async () => { + const appEmbed = new AppEmbed(getRootEl(), { + frameParams: { + width: '100%', + height: '100%', + }, + overrideHistoryState: false, + }); + await appEmbed.render(); + appEmbed.navigateToPage(path, false); + + expectUrlMatchesWithParams( + getIFrameSrc(), + `http://${thoughtSpotHost}/?embedApp=true&primaryNavHidden=true&profileAndHelpInNavBarHidden=false&${defaultParamsForPinboardEmbed}${defaultParamsPost}#/${path}`, + ); + }); + + test('navigateToPage with overrideHistoryState=undefined and noReload=false should update iframe src', async () => { + const appEmbed = new AppEmbed(getRootEl(), { + frameParams: { + width: '100%', + height: '100%', + }, + }); + await appEmbed.render(); + appEmbed.navigateToPage(path, false); + + expectUrlMatchesWithParams( + getIFrameSrc(), + `http://${thoughtSpotHost}/?embedApp=true&primaryNavHidden=true&profileAndHelpInNavBarHidden=false&${defaultParamsForPinboardEmbed}${defaultParamsPost}#/${path}`, + ); + }); + + test('navigateToPage respects noReload priority over overrideHistoryState', async () => { + mockMessageChannel(); + const appEmbed = new AppEmbed(getRootEl(), { + frameParams: { + width: '100%', + height: '100%', + }, + overrideHistoryState: false, + }); + await appEmbed.render(); + + const iframe = getIFrameEl(); + iframe.contentWindow.postMessage = jest.fn(); + appEmbed.navigateToPage(path, true); + + expect(iframe.contentWindow.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ + type: HostEvent.Navigate, + data: path, + }), + `http://${thoughtSpotHost}`, + expect.anything(), + ); + }); + }); }); describe('LazyLoadingForFullHeight functionality', () => {