@thoughtspot/visual-embed-sdk 1.47.3 → 1.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/cjs/package.json +1 -1
  2. package/cjs/src/auth.d.ts.map +1 -1
  3. package/cjs/src/auth.js +11 -1
  4. package/cjs/src/auth.js.map +1 -1
  5. package/cjs/src/auth.spec.js +38 -0
  6. package/cjs/src/auth.spec.js.map +1 -1
  7. package/cjs/src/authToken.d.ts +2 -0
  8. package/cjs/src/authToken.d.ts.map +1 -1
  9. package/cjs/src/authToken.js +7 -5
  10. package/cjs/src/authToken.js.map +1 -1
  11. package/cjs/src/embed/app.d.ts +1 -1
  12. package/cjs/src/embed/app.js +1 -1
  13. package/cjs/src/embed/app.js.map +1 -1
  14. package/cjs/src/embed/app.spec.js +9 -0
  15. package/cjs/src/embed/app.spec.js.map +1 -1
  16. package/cjs/src/embed/liveboard.d.ts +1 -1
  17. package/cjs/src/embed/liveboard.js +1 -1
  18. package/cjs/src/embed/liveboard.js.map +1 -1
  19. package/cjs/src/embed/liveboard.spec.js +10 -0
  20. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  21. package/cjs/src/embed/ts-embed.js +2 -2
  22. package/cjs/src/embed/ts-embed.js.map +1 -1
  23. package/cjs/src/embed/ts-embed.spec.js +29 -0
  24. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  25. package/cjs/src/tokenizedFetch.d.ts.map +1 -1
  26. package/cjs/src/tokenizedFetch.js +12 -9
  27. package/cjs/src/tokenizedFetch.js.map +1 -1
  28. package/cjs/src/tokenizedFetch.spec.d.ts +2 -0
  29. package/cjs/src/tokenizedFetch.spec.d.ts.map +1 -0
  30. package/cjs/src/tokenizedFetch.spec.js +68 -0
  31. package/cjs/src/tokenizedFetch.spec.js.map +1 -0
  32. package/cjs/src/types.d.ts +42 -13
  33. package/cjs/src/types.d.ts.map +1 -1
  34. package/cjs/src/types.js +28 -4
  35. package/cjs/src/types.js.map +1 -1
  36. package/dist/{index-DZq20cR6.js → index-Ck-r09gt.js} +1 -1
  37. package/dist/src/auth.d.ts.map +1 -1
  38. package/dist/src/authToken.d.ts +2 -0
  39. package/dist/src/authToken.d.ts.map +1 -1
  40. package/dist/src/embed/app.d.ts +1 -1
  41. package/dist/src/embed/liveboard.d.ts +1 -1
  42. package/dist/src/tokenizedFetch.d.ts.map +1 -1
  43. package/dist/src/tokenizedFetch.spec.d.ts +2 -0
  44. package/dist/src/tokenizedFetch.spec.d.ts.map +1 -0
  45. package/dist/src/types.d.ts +42 -13
  46. package/dist/src/types.d.ts.map +1 -1
  47. package/dist/tsembed-react.es.js +58 -21
  48. package/dist/tsembed-react.js +57 -20
  49. package/dist/tsembed.es.js +58 -21
  50. package/dist/tsembed.js +57 -20
  51. package/dist/visual-embed-sdk-react-full.d.ts +44 -15
  52. package/dist/visual-embed-sdk-react.d.ts +44 -15
  53. package/dist/visual-embed-sdk.d.ts +44 -15
  54. package/lib/package.json +1 -1
  55. package/lib/src/auth.d.ts.map +1 -1
  56. package/lib/src/auth.js +12 -2
  57. package/lib/src/auth.js.map +1 -1
  58. package/lib/src/auth.spec.js +38 -0
  59. package/lib/src/auth.spec.js.map +1 -1
  60. package/lib/src/authToken.d.ts +2 -0
  61. package/lib/src/authToken.d.ts.map +1 -1
  62. package/lib/src/authToken.js +2 -2
  63. package/lib/src/authToken.js.map +1 -1
  64. package/lib/src/embed/app.d.ts +1 -1
  65. package/lib/src/embed/app.js +1 -1
  66. package/lib/src/embed/app.js.map +1 -1
  67. package/lib/src/embed/app.spec.js +9 -0
  68. package/lib/src/embed/app.spec.js.map +1 -1
  69. package/lib/src/embed/liveboard.d.ts +1 -1
  70. package/lib/src/embed/liveboard.js +1 -1
  71. package/lib/src/embed/liveboard.js.map +1 -1
  72. package/lib/src/embed/liveboard.spec.js +10 -0
  73. package/lib/src/embed/liveboard.spec.js.map +1 -1
  74. package/lib/src/embed/ts-embed.js +2 -2
  75. package/lib/src/embed/ts-embed.js.map +1 -1
  76. package/lib/src/embed/ts-embed.spec.js +29 -0
  77. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  78. package/lib/src/tokenizedFetch.d.ts.map +1 -1
  79. package/lib/src/tokenizedFetch.js +13 -10
  80. package/lib/src/tokenizedFetch.js.map +1 -1
  81. package/lib/src/tokenizedFetch.spec.d.ts +2 -0
  82. package/lib/src/tokenizedFetch.spec.d.ts.map +1 -0
  83. package/lib/src/tokenizedFetch.spec.js +65 -0
  84. package/lib/src/tokenizedFetch.spec.js.map +1 -0
  85. package/lib/src/types.d.ts +42 -13
  86. package/lib/src/types.d.ts.map +1 -1
  87. package/lib/src/types.js +28 -4
  88. package/lib/src/types.js.map +1 -1
  89. package/lib/src/visual-embed-sdk.d.ts +44 -15
  90. package/package.json +1 -1
  91. package/src/auth.spec.ts +55 -1
  92. package/src/auth.ts +11 -2
  93. package/src/authToken.ts +2 -2
  94. package/src/embed/app.spec.ts +13 -0
  95. package/src/embed/app.ts +2 -2
  96. package/src/embed/liveboard.spec.ts +14 -0
  97. package/src/embed/liveboard.ts +4 -4
  98. package/src/embed/ts-embed.spec.ts +35 -0
  99. package/src/embed/ts-embed.ts +1 -1
  100. package/src/tokenizedFetch.spec.ts +81 -0
  101. package/src/tokenizedFetch.ts +14 -11
  102. package/src/types.ts +42 -13
@@ -1902,9 +1902,10 @@ export interface BaseViewConfig extends ApiInterceptFlags {
1902
1902
  styleSheet__unstable?: string;
1903
1903
  /**
1904
1904
  * The list of actions to disable from the primary menu, more menu
1905
- * (...), and the contextual menu. These actions will be disabled
1906
- * for the user.
1907
- * Use this to disable actions.
1905
+ * (...), and the contextual menu. Disabled actions are grayed out
1906
+ * and still visible to the user, but cannot be clicked.
1907
+ * Use this when you want to disable an action (keep it visible but non-interactive).
1908
+ * To completely remove an action from the UI, use {@link hiddenActions} instead.
1908
1909
  *
1909
1910
  * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed`
1910
1911
  * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw
@@ -1935,9 +1936,10 @@ export interface BaseViewConfig extends ApiInterceptFlags {
1935
1936
  */
1936
1937
  disabledActionReason?: string;
1937
1938
  /**
1938
- * The list of actions to hide from the embedded view.
1939
- * These actions will be hidden from the user.
1940
- * Use this to hide an action.
1939
+ * The list of actions to completely remove from the embedded view.
1940
+ * Hidden actions are not visible to the user at all (fully removed from the UI).
1941
+ * Use this when you want to remove an action entirely.
1942
+ * To keep an action visible but non-interactive (grayed out), use {@link disabledActions} instead.
1941
1943
  *
1942
1944
  * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed`
1943
1945
  * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw
@@ -1956,9 +1958,8 @@ export interface BaseViewConfig extends ApiInterceptFlags {
1956
1958
  * The list of actions to display from the primary menu, more menu
1957
1959
  * (...), and the contextual menu. These will be only actions that
1958
1960
  * are visible to the user.
1959
- * Use this to hide all actions except the ones you want to show.
1960
- *
1961
- * Use either this or hiddenActions.
1961
+ * Use this as an allowlist only the actions listed here will be shown.
1962
+ * All other actions will be hidden. Use either this or {@link hiddenActions}, not both.
1962
1963
  *
1963
1964
  * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed`
1964
1965
  * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw
@@ -2412,6 +2413,7 @@ export interface BaseViewConfig extends ApiInterceptFlags {
2412
2413
  /**
2413
2414
  * Refresh the auth token when the token is near expiry.
2414
2415
  * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl
2416
+ * @default true
2415
2417
  * @example
2416
2418
  * ```js
2417
2419
  * const embed = new AppEmbed('#tsEmbed', {
@@ -6719,10 +6721,14 @@ export enum DataSourceVisualMode {
6719
6721
  /**
6720
6722
  * ThoughtSpot application pages include actions and menu commands
6721
6723
  * for various user-initiated operations. These actions are represented
6722
- * as enumeration members in the SDK. To show, hide, or disable
6723
- * specific actions in the embedded view, define the Action
6724
- * enumeration members in the `disabledActions`, `visibleActions`,
6725
- * or `hiddenActions` array.
6724
+ * as enumeration members in the SDK. To control actions in the embedded view:
6725
+ * - disabledActions the action is grayed out and still visible, but non-interactive (user can see but not click).
6726
+ * - hiddenActions the action is completely removed from the UI (user cannot see it at all).
6727
+ * - visibleActions — allowlist, only these actions are shown; all others are hidden.
6728
+ *
6729
+ * Use disabledActions to disable (gray out) an action.
6730
+ * Use hiddenActions to hide (fully remove) an action.
6731
+ * Use visibleActions to show only specific actions.
6726
6732
  * @example
6727
6733
  * ```js
6728
6734
  * const embed = new LiveboardEmbed('#tsEmbed', {
@@ -7569,6 +7575,26 @@ export enum Action {
7569
7575
  * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw
7570
7576
  */
7571
7577
  AxisMenuRemove = "axisMenuRemove",
7578
+ /**
7579
+ * The **Compare with** action in the chart axis customization menu.
7580
+ * Allows comparing data across dimensions or measures.
7581
+ * @example
7582
+ * ```js
7583
+ * disabledActions: [Action.AxisMenuCompare]
7584
+ * ```
7585
+ * @version SDK: 1.50.0 | ThoughtSpot: 26.7.0.cl
7586
+ */
7587
+ AxisMenuCompare = "axisMenuCompare",
7588
+ /**
7589
+ * The **Merge with** action in the chart axis customization menu.
7590
+ * Allows merging data across dimensions or measures.
7591
+ * @example
7592
+ * ```js
7593
+ * disabledActions: [Action.AxisMenuMerge]
7594
+ * ```
7595
+ * @version SDK: 1.50.0 | ThoughtSpot: 26.7.0.cl
7596
+ */
7597
+ AxisMenuMerge = "axisMenuMerge",
7572
7598
  /**
7573
7599
  * @hidden
7574
7600
  */
@@ -8791,6 +8817,9 @@ export interface DefaultAppInitData {
8791
8817
  customActions: CustomAction[];
8792
8818
  interceptTimeout: number | undefined;
8793
8819
  interceptUrls: (string | InterceptedApiType)[];
8820
+ embedExpiryInAuthToken: boolean;
8821
+ shouldBypassPayloadValidation?: boolean;
8822
+ useHostEventsV2?: boolean;
8794
8823
  }
8795
8824
  /**
8796
8825
  * Enum for the type of API intercepted
@@ -11043,7 +11072,7 @@ export interface AppViewConfig extends AllEmbedViewConfig {
11043
11072
  /**
11044
11073
  * Enables the 'what you see is what you get' PDF export for Liveboards. Each tab is rendered on a single page
11045
11074
  * following the exact UI layout, instead of splitting visualizations across multiple A4 pages.
11046
- * This feature is GA from version 26.5.0.cl and is enabled by default on embed deployments.
11075
+ * This feature is GA from version 26.5.0.cl. It is disabled by default in embed deployments.
11047
11076
  *
11048
11077
  * Supported embed types: `AppEmbed`, `LiveboardEmbed`
11049
11078
  * @type {boolean}
@@ -11986,7 +12015,7 @@ export interface LiveboardViewConfig extends BaseViewConfig, SearchLiveboardComm
11986
12015
  /**
11987
12016
  * Enables the 'what you see is what you get' PDF export for Liveboards. Each tab is rendered on a single page
11988
12017
  * following the exact UI layout, instead of splitting visualizations across multiple A4 pages.
11989
- * This feature is GA from version 26.5.0.cl and is enabled by default on embed deployments.
12018
+ * This feature is GA from version 26.5.0.cl. It is disabled by default in embed deployments.
11990
12019
  *
11991
12020
  * Supported embed types: `AppEmbed`, `LiveboardEmbed`
11992
12021
  * @type {boolean}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thoughtspot/visual-embed-sdk",
3
- "version": "1.47.3",
3
+ "version": "1.48.0",
4
4
  "description": "ThoughtSpot Embed SDK",
5
5
  "module": "lib/src/index.js",
6
6
  "main": "dist/tsembed.js",
package/src/auth.spec.ts CHANGED
@@ -450,7 +450,7 @@ describe('Unit test for auth', () => {
450
450
 
451
451
  it('should support emitting SAML_POPUP_CLOSED_NO_AUTH event', () => {
452
452
  const emitSpy = jest.fn();
453
- const mockEventEmitter = {
453
+ const mockEventEmitter = {
454
454
  emit: emitSpy,
455
455
  once: jest.fn()
456
456
  };
@@ -460,6 +460,60 @@ describe('Unit test for auth', () => {
460
460
  authInstance.setAuthEE(null);
461
461
  });
462
462
 
463
+ it('should set loggedInStatus from cachedAuthToken without calling isLoggedIn again after popup flow', async () => {
464
+ Object.defineProperty(window, 'location', { value: { href: '', hash: '' } });
465
+ checkReleaseVersionInBetaInstance.storeValueInWindow('cachedAuthToken', 'test-cached-token');
466
+ (authInstance as any).samlCompletionPromise = Promise.resolve();
467
+ global.window.open = jest.fn();
468
+
469
+ jest.spyOn(tokenAuthService, 'isActiveService')
470
+ .mockReturnValueOnce(Promise.resolve(false));
471
+
472
+ await authInstance.doSamlAuth({ ...embedConfig.doSamlAuthNoRedirect });
473
+
474
+ expect(authInstance.loggedInStatus).toBe(true);
475
+ expect(tokenAuthService.isActiveService).toHaveBeenCalledTimes(1);
476
+ });
477
+
478
+ it('should store decoded accessToken in window when SAMLComplete event includes accessToken', async () => {
479
+ Object.defineProperty(window, 'location', { value: { href: '', hash: '' } });
480
+ global.window.open = jest.fn().mockReturnValue({ closed: false, focus: jest.fn(), close: jest.fn() });
481
+
482
+ (authInstance as any).samlCompletionPromise = null;
483
+
484
+ let capturedMessageHandler: ((e: any) => void) | null = null;
485
+ jest.spyOn(window, 'addEventListener').mockImplementation((type: any, handler: any) => {
486
+ if (type === 'message') {
487
+ capturedMessageHandler = handler;
488
+ }
489
+ });
490
+
491
+ jest.spyOn(tokenAuthService, 'isActiveService')
492
+ .mockReturnValueOnce(Promise.resolve(false));
493
+
494
+ const authPromise = authInstance.doSamlAuth({ ...embedConfig.doSamlAuthNoRedirect });
495
+
496
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
497
+
498
+ expect(capturedMessageHandler).not.toBeNull();
499
+
500
+ const accessToken = 'my-access-token';
501
+ capturedMessageHandler!({
502
+ data: {
503
+ type: EmbedEvent.SAMLComplete,
504
+ accessToken: encodeURIComponent(accessToken),
505
+ },
506
+ source: { close: jest.fn() },
507
+ });
508
+
509
+ await authPromise;
510
+
511
+ expect(
512
+ checkReleaseVersionInBetaInstance.getValueFromWindow('cachedAuthToken'),
513
+ ).toBe(accessToken);
514
+ expect(authInstance.loggedInStatus).toBe(true);
515
+ });
516
+
463
517
  });
464
518
 
465
519
  describe('doOIDCAuth', () => {
package/src/auth.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import EventEmitter from 'eventemitter3';
2
- import { getAuthenticationToken } from './authToken';
2
+ import { getAuthenticationToken, storeAuthTokenInCache, getCacheAuthToken } from './authToken';
3
3
  import { getEmbedConfig } from './embed/embedConfig';
4
4
  import { initMixpanel } from './mixpanel-service';
5
5
  import {
@@ -462,6 +462,10 @@ async function samlPopupFlow(ssoURL: string, triggerContainer: DOMSelector, trig
462
462
  samlCompletionPromise = samlCompletionPromise || new Promise<void>((resolve, reject) => {
463
463
  window.addEventListener('message', (e) => {
464
464
  if (e.data.type === EmbedEvent.SAMLComplete) {
465
+ if (e.data.accessToken) {
466
+ const decodedToken = decodeURIComponent(e.data.accessToken);
467
+ storeAuthTokenInCache(decodedToken);
468
+ }
465
469
  samlCompletionResolved = true;
466
470
  if (popupClosedCheck) {
467
471
  clearInterval(popupClosedCheck);
@@ -503,7 +507,12 @@ const doSSOAuth = async (embedConfig: EmbedConfig, ssoEndPoint: string): Promise
503
507
  const ssoURL = `${thoughtSpotHost}${ssoEndPoint}`;
504
508
  if (embedConfig.inPopup) {
505
509
  await samlPopupFlow(ssoURL, embedConfig.authTriggerContainer, embedConfig.authTriggerText);
506
- loggedInStatus = await isLoggedIn(thoughtSpotHost);
510
+ const cachedToken = getCacheAuthToken();
511
+ if (cachedToken) {
512
+ loggedInStatus = true;
513
+ } else {
514
+ loggedInStatus = await isLoggedIn(thoughtSpotHost);
515
+ }
507
516
  return;
508
517
  }
509
518
 
package/src/authToken.ts CHANGED
@@ -6,8 +6,8 @@ import { logger } from './utils/logger';
6
6
 
7
7
  const cacheAuthTokenKey = 'cachedAuthToken';
8
8
 
9
- const getCacheAuthToken = (): string | null => getValueFromWindow(cacheAuthTokenKey);
10
- const storeAuthTokenInCache = (token: string): void => {
9
+ export const getCacheAuthToken = (): string | null => getValueFromWindow(cacheAuthTokenKey);
10
+ export const storeAuthTokenInCache = (token: string): void => {
11
11
  storeValueInWindow(cacheAuthTokenKey, token);
12
12
  };
13
13
 
@@ -480,6 +480,19 @@ describe('App embed tests', () => {
480
480
  });
481
481
  });
482
482
 
483
+ test('should disable isWYSIWYGLiveboardPDFEnabled by default in url', async () => {
484
+ const appEmbed = new AppEmbed(getRootEl(), {
485
+ ...defaultViewConfig,
486
+ } as AppViewConfig);
487
+ appEmbed.render();
488
+ await executeAfterWait(() => {
489
+ expectUrlMatchesWithParams(
490
+ getIFrameSrc(),
491
+ `http://${thoughtSpotHost}/?embedApp=true&profileAndHelpInNavBarHidden=false&isWYSIWYGLiveboardPDFEnabled=false${defaultParamsPost}#/home`,
492
+ );
493
+ });
494
+ });
495
+
483
496
  test('should set isLinkParametersEnabled to true in url', async () => {
484
497
  const appEmbed = new AppEmbed(getRootEl(), {
485
498
  ...defaultViewConfig,
package/src/embed/app.ts CHANGED
@@ -590,7 +590,7 @@ export interface AppViewConfig extends AllEmbedViewConfig {
590
590
  /**
591
591
  * Enables the 'what you see is what you get' PDF export for Liveboards. Each tab is rendered on a single page
592
592
  * following the exact UI layout, instead of splitting visualizations across multiple A4 pages.
593
- * This feature is GA from version 26.5.0.cl and is enabled by default on embed deployments.
593
+ * This feature is GA from version 26.5.0.cl. It is disabled by default in embed deployments.
594
594
  *
595
595
  * Supported embed types: `AppEmbed`, `LiveboardEmbed`
596
596
  * @type {boolean}
@@ -914,7 +914,7 @@ export class AppEmbed extends V1Embed {
914
914
  minimumHeight,
915
915
  isThisPeriodInDateFiltersEnabled,
916
916
  enableHomepageAnnouncement = false,
917
- isContinuousLiveboardPDFEnabled,
917
+ isContinuousLiveboardPDFEnabled = false,
918
918
  enableLiveboardDataCache,
919
919
  } = this.viewConfig;
920
920
 
@@ -275,6 +275,20 @@ describe('Liveboard/viz embed tests', () => {
275
275
  });
276
276
  });
277
277
 
278
+ test('should disable isWYSIWYGLiveboardPDFEnabled by default in url', async () => {
279
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
280
+ ...defaultViewConfig,
281
+ liveboardId,
282
+ } as LiveboardViewConfig);
283
+ liveboardEmbed.render();
284
+ await executeAfterWait(() => {
285
+ expectUrlMatchesWithParams(
286
+ getIFrameSrc(),
287
+ `http://${thoughtSpotHost}/?embedApp=true${defaultParams}&isWYSIWYGLiveboardPDFEnabled=false${prefixParams}#/embed/viz/${liveboardId}`,
288
+ );
289
+ });
290
+ });
291
+
278
292
  test('should set isLiveboardXLSXCSVDownloadEnabled to true in url', async () => {
279
293
  const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
280
294
  isLiveboardXLSXCSVDownloadEnabled: true,
@@ -388,9 +388,9 @@ export interface LiveboardViewConfig extends BaseViewConfig, LiveboardOtherViewC
388
388
  */
389
389
  isPNGInScheduledEmailsEnabled?: boolean;
390
390
  /**
391
- * Enables the 'what you see is what you get' PDF export for Liveboards. Each tab is rendered on a single page
392
- * following the exact UI layout, instead of splitting visualizations across multiple A4 pages.
393
- * This feature is GA from version 26.5.0.cl and is enabled by default on embed deployments.
391
+ * Enables the 'what you see is what you get' PDF export for Liveboards. Each tab is rendered on a single page
392
+ * following the exact UI layout, instead of splitting visualizations across multiple A4 pages.
393
+ * This feature is GA from version 26.5.0.cl. It is disabled by default in embed deployments.
394
394
  *
395
395
  * Supported embed types: `AppEmbed`, `LiveboardEmbed`
396
396
  * @type {boolean}
@@ -640,7 +640,7 @@ export class LiveboardEmbed extends V1Embed {
640
640
  enableStopAnswerGenerationEmbed,
641
641
  spotterChatConfig,
642
642
  isThisPeriodInDateFiltersEnabled,
643
- isContinuousLiveboardPDFEnabled,
643
+ isContinuousLiveboardPDFEnabled = false,
644
644
  enableLiveboardDataCache,
645
645
  } = this.viewConfig;
646
646
 
@@ -97,6 +97,7 @@ beforeAll(() => {
97
97
  const customisations = {
98
98
  style: {
99
99
  customCSS: {},
100
+ customCSSUrl: undefined as string | undefined,
100
101
  },
101
102
  content: {},
102
103
  };
@@ -132,6 +133,9 @@ const getMockAppInitPayload = (data: any) => {
132
133
  customVariablesForThirdPartyTools,
133
134
  interceptTimeout: undefined,
134
135
  interceptUrls: [],
136
+ shouldBypassPayloadValidation:undefined,
137
+ useHostEventsV2:undefined,
138
+ embedExpiryInAuthToken:true
135
139
  };
136
140
  return {
137
141
  type: EmbedEvent.APP_INIT,
@@ -641,6 +645,37 @@ describe('Unit test case for ts embed', () => {
641
645
  });
642
646
  });
643
647
 
648
+ test.each([
649
+ ['not set', undefined, true],
650
+ ['false', false, false],
651
+ ['true', true, true],
652
+ ] as [string, boolean | undefined, boolean][])(
653
+ 'embedExpiryInAuthToken is %s when refreshAuthTokenOnNearExpiry is %s',
654
+ async (_label, refreshAuthTokenOnNearExpiry, expectedEmbedExpiry) => {
655
+ const mockEmbedEventPayload = {
656
+ type: EmbedEvent.APP_INIT,
657
+ data: {},
658
+ };
659
+ const searchEmbed = new AppEmbed(getRootEl(), {
660
+ ...defaultViewConfig,
661
+ refreshAuthTokenOnNearExpiry,
662
+ });
663
+ searchEmbed.render();
664
+ const mockPort: any = {
665
+ postMessage: jest.fn(),
666
+ };
667
+ await executeAfterWait(() => {
668
+ const iframe = getIFrameEl();
669
+ postMessageToParent(iframe.contentWindow, mockEmbedEventPayload, mockPort);
670
+ });
671
+ await executeAfterWait(() => {
672
+ expect(mockPort.postMessage).toHaveBeenCalledWith(
673
+ getMockAppInitPayload({ embedExpiryInAuthToken: expectedEmbedExpiry }),
674
+ );
675
+ });
676
+ },
677
+ );
678
+
644
679
  test('when Embed event status have start status', (done) => {
645
680
  const mockEmbedEventPayload = {
646
681
  type: EmbedEvent.Save,
@@ -481,7 +481,7 @@ export class TsEmbed {
481
481
  this.embedConfig.customVariablesForThirdPartyTools || {},
482
482
  hiddenListColumns: this.viewConfig.hiddenListColumns || [],
483
483
  customActions: customActionsResult.actions,
484
- embedExpiryInAuthToken: this.viewConfig.refreshAuthTokenOnNearExpiry,
484
+ embedExpiryInAuthToken: this.viewConfig.refreshAuthTokenOnNearExpiry ?? true,
485
485
  ...getInterceptInitData(this.viewConfig),
486
486
  ...getHostEventsConfig(this.viewConfig),
487
487
  };
@@ -0,0 +1,81 @@
1
+ import 'jest-fetch-mock';
2
+ import * as embedConfigModule from './embed/embedConfig';
3
+ import * as authTokenModule from './authToken';
4
+ import { AuthType } from './types';
5
+ import { tokenizedFetch } from './tokenizedFetch';
6
+
7
+ jest.mock('./embed/embedConfig');
8
+ jest.mock('./authToken');
9
+
10
+ const mockGetEmbedConfig = embedConfigModule.getEmbedConfig as jest.Mock;
11
+ const mockGetAuthenticationToken = authTokenModule.getAuthenticationToken as jest.Mock;
12
+ const mockGetCacheAuthToken = authTokenModule.getCacheAuthToken as jest.Mock;
13
+
14
+ describe('tokenizedFetch', () => {
15
+ beforeEach(() => {
16
+ jest.clearAllMocks();
17
+ fetchMock.resetMocks();
18
+ });
19
+
20
+ describe('non-cookieless auth', () => {
21
+ beforeEach(() => {
22
+ mockGetEmbedConfig.mockReturnValue({ authType: AuthType.TrustedAuthToken });
23
+ });
24
+
25
+ it('should add Authorization Bearer header when cachedAuthToken exists', async () => {
26
+ mockGetCacheAuthToken.mockReturnValue('my-cached-token');
27
+ fetchMock.mockResponseOnce(JSON.stringify({}));
28
+
29
+ await tokenizedFetch('https://example.com/api', { method: 'GET' });
30
+
31
+ expect(fetchMock).toHaveBeenCalledTimes(1);
32
+ const request = fetchMock.mock.calls[0][0] as Request;
33
+ expect(request).toBeInstanceOf(Request);
34
+ expect(request.headers.get('Authorization')).toBe('Bearer my-cached-token');
35
+ });
36
+
37
+ it('should fetch with credentials include when no cachedAuthToken', async () => {
38
+ mockGetCacheAuthToken.mockReturnValue(null);
39
+ fetchMock.mockResponseOnce(JSON.stringify({}));
40
+
41
+ await tokenizedFetch('https://example.com/api', { method: 'GET' });
42
+
43
+ expect(fetchMock).toHaveBeenCalledWith('https://example.com/api', {
44
+ credentials: 'include',
45
+ method: 'GET',
46
+ });
47
+ });
48
+ });
49
+
50
+ describe('cookieless auth (TrustedAuthTokenCookieless)', () => {
51
+ beforeEach(() => {
52
+ mockGetEmbedConfig.mockReturnValue({
53
+ authType: AuthType.TrustedAuthTokenCookieless,
54
+ thoughtSpotHost: 'https://example.com',
55
+ });
56
+ });
57
+
58
+ it('should add Authorization Bearer header from getAuthenticationToken', async () => {
59
+ mockGetAuthenticationToken.mockResolvedValue('cookieless-token');
60
+ fetchMock.mockResponseOnce(JSON.stringify({}));
61
+
62
+ await tokenizedFetch('https://example.com/api', { method: 'POST' });
63
+
64
+ expect(mockGetAuthenticationToken).toHaveBeenCalled();
65
+ const request = fetchMock.mock.calls[0][0] as Request;
66
+ expect(request).toBeInstanceOf(Request);
67
+ expect(request.headers.get('Authorization')).toBe('Bearer cookieless-token');
68
+ });
69
+
70
+ it('should not add Authorization header when getAuthenticationToken returns null', async () => {
71
+ mockGetAuthenticationToken.mockResolvedValue(null);
72
+ fetchMock.mockResponseOnce(JSON.stringify({}));
73
+
74
+ await tokenizedFetch('https://example.com/api', { method: 'POST' });
75
+
76
+ const request = fetchMock.mock.calls[0][0] as Request;
77
+ expect(request).toBeInstanceOf(Request);
78
+ expect(request.headers.get('Authorization')).toBeNull();
79
+ });
80
+ });
81
+ });
@@ -1,4 +1,4 @@
1
- import { getAuthenticationToken } from './authToken';
1
+ import { getAuthenticationToken, getCacheAuthToken } from './authToken';
2
2
  import { getEmbedConfig } from './embed/embedConfig';
3
3
 
4
4
  import { AuthType } from './types';
@@ -20,18 +20,21 @@ import { AuthType } from './types';
20
20
  */
21
21
  export const tokenizedFetch: typeof fetch = async (input, init): Promise<Response> => {
22
22
  const embedConfig = getEmbedConfig();
23
+ const options: RequestInit = { ...init };
24
+ let token: string | undefined;
23
25
  if (embedConfig.authType !== AuthType.TrustedAuthTokenCookieless) {
24
- return fetch(input, {
25
- // ensure cookies are included for the non cookie-less api calls.
26
- credentials: 'include',
27
- ...init,
28
- });
26
+ token = getCacheAuthToken();
27
+ if (!token) {
28
+ return fetch(input, { ...options, credentials: 'include' });
29
+ }
30
+ } else {
31
+ token = await getAuthenticationToken(embedConfig);
29
32
  }
33
+ const req = new Request(input, options);
30
34
 
31
- const req = new Request(input, init);
32
- const authToken = await getAuthenticationToken(embedConfig);
33
- if (authToken) {
34
- req.headers.append('Authorization', `Bearer ${authToken}`);
35
+ if (token) {
36
+ req.headers.append('Authorization', `Bearer ${token}`);
35
37
  }
38
+
36
39
  return fetch(req);
37
- };
40
+ };
package/src/types.ts CHANGED
@@ -926,9 +926,10 @@ export interface BaseViewConfig extends ApiInterceptFlags {
926
926
  styleSheet__unstable?: string;
927
927
  /**
928
928
  * The list of actions to disable from the primary menu, more menu
929
- * (...), and the contextual menu. These actions will be disabled
930
- * for the user.
931
- * Use this to disable actions.
929
+ * (...), and the contextual menu. Disabled actions are grayed out
930
+ * and still visible to the user, but cannot be clicked.
931
+ * Use this when you want to disable an action (keep it visible but non-interactive).
932
+ * To completely remove an action from the UI, use {@link hiddenActions} instead.
932
933
  *
933
934
  * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed`
934
935
  * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw
@@ -959,9 +960,10 @@ export interface BaseViewConfig extends ApiInterceptFlags {
959
960
  */
960
961
  disabledActionReason?: string;
961
962
  /**
962
- * The list of actions to hide from the embedded view.
963
- * These actions will be hidden from the user.
964
- * Use this to hide an action.
963
+ * The list of actions to completely remove from the embedded view.
964
+ * Hidden actions are not visible to the user at all (fully removed from the UI).
965
+ * Use this when you want to remove an action entirely.
966
+ * To keep an action visible but non-interactive (grayed out), use {@link disabledActions} instead.
965
967
  *
966
968
  * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed`
967
969
  * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw
@@ -980,9 +982,8 @@ export interface BaseViewConfig extends ApiInterceptFlags {
980
982
  * The list of actions to display from the primary menu, more menu
981
983
  * (...), and the contextual menu. These will be only actions that
982
984
  * are visible to the user.
983
- * Use this to hide all actions except the ones you want to show.
984
- *
985
- * Use either this or hiddenActions.
985
+ * Use this as an allowlist only the actions listed here will be shown.
986
+ * All other actions will be hidden. Use either this or {@link hiddenActions}, not both.
986
987
  *
987
988
  * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed`
988
989
  * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw
@@ -1437,6 +1438,7 @@ export interface BaseViewConfig extends ApiInterceptFlags {
1437
1438
  /**
1438
1439
  * Refresh the auth token when the token is near expiry.
1439
1440
  * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl
1441
+ * @default true
1440
1442
  * @example
1441
1443
  * ```js
1442
1444
  * const embed = new AppEmbed('#tsEmbed', {
@@ -5960,10 +5962,14 @@ export enum Param {
5960
5962
  /**
5961
5963
  * ThoughtSpot application pages include actions and menu commands
5962
5964
  * for various user-initiated operations. These actions are represented
5963
- * as enumeration members in the SDK. To show, hide, or disable
5964
- * specific actions in the embedded view, define the Action
5965
- * enumeration members in the `disabledActions`, `visibleActions`,
5966
- * or `hiddenActions` array.
5965
+ * as enumeration members in the SDK. To control actions in the embedded view:
5966
+ * - disabledActions the action is grayed out and still visible, but non-interactive (user can see but not click).
5967
+ * - hiddenActions the action is completely removed from the UI (user cannot see it at all).
5968
+ * - visibleActions — allowlist, only these actions are shown; all others are hidden.
5969
+ *
5970
+ * Use disabledActions to disable (gray out) an action.
5971
+ * Use hiddenActions to hide (fully remove) an action.
5972
+ * Use visibleActions to show only specific actions.
5967
5973
  * @example
5968
5974
  * ```js
5969
5975
  * const embed = new LiveboardEmbed('#tsEmbed', {
@@ -6814,6 +6820,26 @@ export enum Action {
6814
6820
  * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.5.1.sw
6815
6821
  */
6816
6822
  AxisMenuRemove = 'axisMenuRemove',
6823
+ /**
6824
+ * The **Compare with** action in the chart axis customization menu.
6825
+ * Allows comparing data across dimensions or measures.
6826
+ * @example
6827
+ * ```js
6828
+ * disabledActions: [Action.AxisMenuCompare]
6829
+ * ```
6830
+ * @version SDK: 1.50.0 | ThoughtSpot: 26.7.0.cl
6831
+ */
6832
+ AxisMenuCompare = 'axisMenuCompare',
6833
+ /**
6834
+ * The **Merge with** action in the chart axis customization menu.
6835
+ * Allows merging data across dimensions or measures.
6836
+ * @example
6837
+ * ```js
6838
+ * disabledActions: [Action.AxisMenuMerge]
6839
+ * ```
6840
+ * @version SDK: 1.50.0 | ThoughtSpot: 26.7.0.cl
6841
+ */
6842
+ AxisMenuMerge = 'axisMenuMerge',
6817
6843
  /**
6818
6844
  * @hidden
6819
6845
  */
@@ -8093,6 +8119,9 @@ export interface DefaultAppInitData {
8093
8119
  customActions: CustomAction[];
8094
8120
  interceptTimeout: number | undefined;
8095
8121
  interceptUrls: (string | InterceptedApiType)[];
8122
+ embedExpiryInAuthToken:boolean;
8123
+ shouldBypassPayloadValidation?:boolean
8124
+ useHostEventsV2?:boolean
8096
8125
  }
8097
8126
 
8098
8127
  /**