@thoughtspot/visual-embed-sdk 1.42.0 → 1.42.1-alpha.1

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 (139) hide show
  1. package/cjs/package.json +2 -2
  2. package/cjs/src/api-intercept.d.ts +25 -0
  3. package/cjs/src/api-intercept.d.ts.map +1 -0
  4. package/cjs/src/api-intercept.js +115 -0
  5. package/cjs/src/api-intercept.js.map +1 -0
  6. package/cjs/src/embed/app.d.ts.map +1 -1
  7. package/cjs/src/embed/app.js +7 -2
  8. package/cjs/src/embed/app.js.map +1 -1
  9. package/cjs/src/embed/app.spec.js +20 -0
  10. package/cjs/src/embed/app.spec.js.map +1 -1
  11. package/cjs/src/embed/hostEventClient/contracts.d.ts +11 -1
  12. package/cjs/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  13. package/cjs/src/embed/hostEventClient/contracts.js +1 -0
  14. package/cjs/src/embed/hostEventClient/contracts.js.map +1 -1
  15. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  16. package/cjs/src/embed/liveboard.js +4 -1
  17. package/cjs/src/embed/liveboard.js.map +1 -1
  18. package/cjs/src/embed/liveboard.spec.js +22 -0
  19. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  20. package/cjs/src/embed/search.d.ts.map +1 -1
  21. package/cjs/src/embed/search.js +3 -1
  22. package/cjs/src/embed/search.js.map +1 -1
  23. package/cjs/src/embed/ts-embed.d.ts +15 -0
  24. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  25. package/cjs/src/embed/ts-embed.js +94 -26
  26. package/cjs/src/embed/ts-embed.js.map +1 -1
  27. package/cjs/src/embed/ts-embed.spec.js +83 -0
  28. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  29. package/cjs/src/errors.d.ts +1 -0
  30. package/cjs/src/errors.d.ts.map +1 -1
  31. package/cjs/src/errors.js +1 -0
  32. package/cjs/src/errors.js.map +1 -1
  33. package/cjs/src/index.d.ts +2 -2
  34. package/cjs/src/index.d.ts.map +1 -1
  35. package/cjs/src/index.js +2 -1
  36. package/cjs/src/index.js.map +1 -1
  37. package/cjs/src/react/all-types-export.d.ts +1 -1
  38. package/cjs/src/react/all-types-export.d.ts.map +1 -1
  39. package/cjs/src/react/all-types-export.js +2 -1
  40. package/cjs/src/react/all-types-export.js.map +1 -1
  41. package/cjs/src/types.d.ts +100 -4
  42. package/cjs/src/types.d.ts.map +1 -1
  43. package/cjs/src/types.js +39 -1
  44. package/cjs/src/types.js.map +1 -1
  45. package/cjs/src/utils/processData.d.ts +1 -1
  46. package/cjs/src/utils/processData.d.ts.map +1 -1
  47. package/cjs/src/utils/processData.js +8 -8
  48. package/cjs/src/utils/processData.js.map +1 -1
  49. package/dist/index-BEzW4MDA.js +7371 -0
  50. package/dist/{index-BpSohedu.js → index-DvNA626T.js} +1 -1
  51. package/dist/src/api-intercept.d.ts +25 -0
  52. package/dist/src/api-intercept.d.ts.map +1 -0
  53. package/dist/src/embed/app.d.ts.map +1 -1
  54. package/dist/src/embed/hostEventClient/contracts.d.ts +11 -1
  55. package/dist/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  56. package/dist/src/embed/liveboard.d.ts.map +1 -1
  57. package/dist/src/embed/search.d.ts.map +1 -1
  58. package/dist/src/embed/ts-embed.d.ts +15 -0
  59. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  60. package/dist/src/errors.d.ts +1 -0
  61. package/dist/src/errors.d.ts.map +1 -1
  62. package/dist/src/index.d.ts +2 -2
  63. package/dist/src/index.d.ts.map +1 -1
  64. package/dist/src/react/all-types-export.d.ts +1 -1
  65. package/dist/src/react/all-types-export.d.ts.map +1 -1
  66. package/dist/src/types.d.ts +100 -4
  67. package/dist/src/types.d.ts.map +1 -1
  68. package/dist/src/utils/processData.d.ts +1 -1
  69. package/dist/src/utils/processData.d.ts.map +1 -1
  70. package/dist/tsembed-react.es.js +266 -51
  71. package/dist/tsembed-react.js +265 -50
  72. package/dist/tsembed.es.js +267 -52
  73. package/dist/tsembed.js +265 -50
  74. package/dist/visual-embed-sdk-react-full.d.ts +124 -4
  75. package/dist/visual-embed-sdk-react.d.ts +121 -4
  76. package/dist/visual-embed-sdk.d.ts +124 -4
  77. package/lib/package.json +2 -2
  78. package/lib/src/api-intercept.d.ts +25 -0
  79. package/lib/src/api-intercept.d.ts.map +1 -0
  80. package/lib/src/api-intercept.js +108 -0
  81. package/lib/src/api-intercept.js.map +1 -0
  82. package/lib/src/embed/app.d.ts.map +1 -1
  83. package/lib/src/embed/app.js +7 -2
  84. package/lib/src/embed/app.js.map +1 -1
  85. package/lib/src/embed/app.spec.js +20 -0
  86. package/lib/src/embed/app.spec.js.map +1 -1
  87. package/lib/src/embed/hostEventClient/contracts.d.ts +11 -1
  88. package/lib/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  89. package/lib/src/embed/hostEventClient/contracts.js +1 -0
  90. package/lib/src/embed/hostEventClient/contracts.js.map +1 -1
  91. package/lib/src/embed/liveboard.d.ts.map +1 -1
  92. package/lib/src/embed/liveboard.js +4 -1
  93. package/lib/src/embed/liveboard.js.map +1 -1
  94. package/lib/src/embed/liveboard.spec.js +22 -0
  95. package/lib/src/embed/liveboard.spec.js.map +1 -1
  96. package/lib/src/embed/search.d.ts.map +1 -1
  97. package/lib/src/embed/search.js +3 -1
  98. package/lib/src/embed/search.js.map +1 -1
  99. package/lib/src/embed/ts-embed.d.ts +15 -0
  100. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  101. package/lib/src/embed/ts-embed.js +94 -26
  102. package/lib/src/embed/ts-embed.js.map +1 -1
  103. package/lib/src/embed/ts-embed.spec.js +83 -0
  104. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  105. package/lib/src/errors.d.ts +1 -0
  106. package/lib/src/errors.d.ts.map +1 -1
  107. package/lib/src/errors.js +1 -0
  108. package/lib/src/errors.js.map +1 -1
  109. package/lib/src/index.d.ts +2 -2
  110. package/lib/src/index.d.ts.map +1 -1
  111. package/lib/src/index.js +2 -2
  112. package/lib/src/index.js.map +1 -1
  113. package/lib/src/react/all-types-export.d.ts +1 -1
  114. package/lib/src/react/all-types-export.d.ts.map +1 -1
  115. package/lib/src/react/all-types-export.js +1 -1
  116. package/lib/src/react/all-types-export.js.map +1 -1
  117. package/lib/src/types.d.ts +100 -4
  118. package/lib/src/types.d.ts.map +1 -1
  119. package/lib/src/types.js +38 -0
  120. package/lib/src/types.js.map +1 -1
  121. package/lib/src/utils/processData.d.ts +1 -1
  122. package/lib/src/utils/processData.d.ts.map +1 -1
  123. package/lib/src/utils/processData.js +8 -8
  124. package/lib/src/utils/processData.js.map +1 -1
  125. package/package.json +2 -2
  126. package/src/api-intercept.ts +134 -0
  127. package/src/embed/app.spec.ts +28 -0
  128. package/src/embed/app.ts +9 -1
  129. package/src/embed/hostEventClient/contracts.ts +10 -0
  130. package/src/embed/liveboard.spec.ts +30 -0
  131. package/src/embed/liveboard.ts +5 -0
  132. package/src/embed/search.ts +3 -1
  133. package/src/embed/ts-embed.spec.ts +116 -5
  134. package/src/embed/ts-embed.ts +129 -43
  135. package/src/errors.ts +1 -0
  136. package/src/index.ts +2 -0
  137. package/src/react/all-types-export.ts +1 -0
  138. package/src/types.ts +102 -3
  139. package/src/utils/processData.ts +11 -11
@@ -358,6 +358,34 @@ describe('App embed tests', () => {
358
358
  });
359
359
  });
360
360
 
361
+ test('should set isLinkParametersEnabled to true in url', async () => {
362
+ const appEmbed = new AppEmbed(getRootEl(), {
363
+ ...defaultViewConfig,
364
+ isLinkParametersEnabled: true,
365
+ } as AppViewConfig);
366
+ appEmbed.render();
367
+ await executeAfterWait(() => {
368
+ expectUrlMatchesWithParams(
369
+ getIFrameSrc(),
370
+ `http://${thoughtSpotHost}/?embedApp=true&profileAndHelpInNavBarHidden=false&isLinkParametersEnabled=true${defaultParamsPost}#/home`,
371
+ );
372
+ });
373
+ });
374
+
375
+ test('should set isLinkParametersEnabled to false in url', async () => {
376
+ const appEmbed = new AppEmbed(getRootEl(), {
377
+ ...defaultViewConfig,
378
+ isLinkParametersEnabled: false,
379
+ } as AppViewConfig);
380
+ appEmbed.render();
381
+ await executeAfterWait(() => {
382
+ expectUrlMatchesWithParams(
383
+ getIFrameSrc(),
384
+ `http://${thoughtSpotHost}/?embedApp=true&profileAndHelpInNavBarHidden=false&isLinkParametersEnabled=false${defaultParamsPost}#/home`,
385
+ );
386
+ });
387
+ });
388
+
361
389
  test('should set liveboardXLSXCSVDownload to true in url', async () => {
362
390
  const appEmbed = new AppEmbed(getRootEl(), {
363
391
  ...defaultViewConfig,
package/src/embed/app.ts CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  AllEmbedViewConfig,
20
20
  } from '../types';
21
21
  import { V1Embed } from './ts-embed';
22
+ import { getInterceptInitData } from '../api-intercept';
22
23
 
23
24
  /**
24
25
  * Pages within the ThoughtSpot app that can be embedded.
@@ -663,6 +664,7 @@ export class AppEmbed extends V1Embed {
663
664
  liveboardXLSXCSVDownload = false,
664
665
  isLiveboardStylingAndGroupingEnabled,
665
666
  isPNGInScheduledEmailsEnabled = false,
667
+ isLinkParametersEnabled,
666
668
  } = this.viewConfig;
667
669
 
668
670
  let params: any = {};
@@ -727,7 +729,9 @@ export class AppEmbed extends V1Embed {
727
729
  params[Param.enableAskSage] = enableAskSage;
728
730
  }
729
731
 
730
- if (isOnBeforeGetVizDataInterceptEnabled) {
732
+ const { enableApiIntercept } = getInterceptInitData(this.embedConfig, this.viewConfig);
733
+
734
+ if (isOnBeforeGetVizDataInterceptEnabled && !enableApiIntercept) {
731
735
 
732
736
  params[
733
737
  Param.IsOnBeforeGetVizDataInterceptEnabled
@@ -750,6 +754,10 @@ export class AppEmbed extends V1Embed {
750
754
  params[Param.isPNGInScheduledEmailsEnabled] = isPNGInScheduledEmailsEnabled;
751
755
  }
752
756
 
757
+ if (isLinkParametersEnabled !== undefined) {
758
+ params[Param.isLinkParametersEnabled] = isLinkParametersEnabled;
759
+ }
760
+
753
761
  params[Param.DataPanelV2Enabled] = dataPanelV2;
754
762
  params[Param.HideHomepageLeftNav] = hideHomepageLeftNav;
755
763
  params[Param.ModularHomeExperienceEnabled] = modularHomeExperience;
@@ -7,6 +7,7 @@ export enum UIPassthroughEvent {
7
7
  GetAvailableUIPassthroughs = 'getAvailableUiPassthroughs',
8
8
  GetAnswerConfig = 'getAnswerPageConfig',
9
9
  GetLiveboardConfig = 'getPinboardPageConfig',
10
+ GetUnsavedAnswerTML = 'getUnsavedAnswerTML',
10
11
  }
11
12
 
12
13
  // UI Passthrough Contract
@@ -63,6 +64,15 @@ export type UIPassthroughContractBase = {
63
64
  request: any;
64
65
  response: any;
65
66
  };
67
+ [UIPassthroughEvent.GetUnsavedAnswerTML]: {
68
+ request: {
69
+ sessionId?: string;
70
+ vizId?: string;
71
+ };
72
+ response: {
73
+ tml: string;
74
+ };
75
+ };
66
76
  };
67
77
 
68
78
  // UI Passthrough Request and Response
@@ -183,6 +183,36 @@ describe('Liveboard/viz embed tests', () => {
183
183
  });
184
184
  });
185
185
 
186
+ test('should set isLinkParametersEnabled to true in url', async () => {
187
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
188
+ isLinkParametersEnabled: true,
189
+ ...defaultViewConfig,
190
+ liveboardId,
191
+ } as LiveboardViewConfig);
192
+ liveboardEmbed.render();
193
+ await executeAfterWait(() => {
194
+ expectUrlMatchesWithParams(
195
+ getIFrameSrc(),
196
+ `http://${thoughtSpotHost}/?embedApp=true${defaultParams}&isLinkParametersEnabled=true${prefixParams}#/embed/viz/${liveboardId}`,
197
+ );
198
+ });
199
+ });
200
+
201
+ test('should set isLinkParametersEnabled to false in url', async () => {
202
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
203
+ isLinkParametersEnabled: false,
204
+ ...defaultViewConfig,
205
+ liveboardId,
206
+ } as LiveboardViewConfig);
207
+ liveboardEmbed.render();
208
+ await executeAfterWait(() => {
209
+ expectUrlMatchesWithParams(
210
+ getIFrameSrc(),
211
+ `http://${thoughtSpotHost}/?embedApp=true${defaultParams}&isLinkParametersEnabled=false${prefixParams}#/embed/viz/${liveboardId}`,
212
+ );
213
+ });
214
+ });
215
+
186
216
  test('should set visible actions as empty array', async () => {
187
217
  const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
188
218
  visibleActions: [],
@@ -477,6 +477,7 @@ export class LiveboardEmbed extends V1Embed {
477
477
  isLiveboardStylingAndGroupingEnabled,
478
478
  isPNGInScheduledEmailsEnabled = false,
479
479
  showSpotterLimitations,
480
+ isLinkParametersEnabled,
480
481
  } = this.viewConfig;
481
482
 
482
483
  const preventLiveboardFilterRemoval = this.viewConfig.preventLiveboardFilterRemoval
@@ -552,6 +553,10 @@ export class LiveboardEmbed extends V1Embed {
552
553
  params[Param.ShowSpotterLimitations] = showSpotterLimitations;
553
554
  }
554
555
 
556
+ if (isLinkParametersEnabled !== undefined) {
557
+ params[Param.isLinkParametersEnabled] = isLinkParametersEnabled;
558
+ }
559
+
555
560
  params[Param.LiveboardHeaderSticky] = isLiveboardHeaderSticky;
556
561
  params[Param.LiveboardHeaderV2] = isLiveboardCompactHeaderEnabled;
557
562
  params[Param.ShowLiveboardVerifiedBadge] = showLiveboardVerifiedBadge;
@@ -26,6 +26,7 @@ import { ERROR_MESSAGE } from '../errors';
26
26
  import { getAuthPromise } from './base';
27
27
  import { getReleaseVersion } from '../auth';
28
28
  import { getEmbedConfig } from './embedConfig';
29
+ import { getInterceptInitData } from '../api-intercept';
29
30
 
30
31
  /**
31
32
  * Configuration for search options.
@@ -442,7 +443,8 @@ export class SearchEmbed extends TsEmbed {
442
443
  queryParams[Param.HideSearchBar] = true;
443
444
  }
444
445
 
445
- if (isOnBeforeGetVizDataInterceptEnabled) {
446
+ const { enableApiIntercept } = getInterceptInitData(this.embedConfig, this.viewConfig);
447
+ if (isOnBeforeGetVizDataInterceptEnabled && !enableApiIntercept) {
446
448
 
447
449
  queryParams[Param.IsOnBeforeGetVizDataInterceptEnabled] = isOnBeforeGetVizDataInterceptEnabled;
448
450
  }
@@ -1045,7 +1045,7 @@ describe('Unit test case for ts embed', () => {
1045
1045
  type: EmbedEvent.APP_INIT,
1046
1046
  data: {},
1047
1047
  };
1048
-
1048
+
1049
1049
  // Create a SearchEmbed with valid custom actions to test
1050
1050
  // CustomActionsValidationResult
1051
1051
  const searchEmbed = new SearchEmbed(getRootEl(), {
@@ -1067,7 +1067,7 @@ describe('Unit test case for ts embed', () => {
1067
1067
  }
1068
1068
  ]
1069
1069
  });
1070
-
1070
+
1071
1071
  searchEmbed.render();
1072
1072
  const mockPort: any = {
1073
1073
  postMessage: jest.fn(),
@@ -1116,7 +1116,7 @@ describe('Unit test case for ts embed', () => {
1116
1116
  customVariablesForThirdPartyTools: {},
1117
1117
  },
1118
1118
  });
1119
-
1119
+
1120
1120
  // Verify that CustomActionsValidationResult structure is
1121
1121
  // correct
1122
1122
  const appInitData = mockPort.postMessage.mock.calls[0][0].data;
@@ -1137,7 +1137,7 @@ describe('Unit test case for ts embed', () => {
1137
1137
  })
1138
1138
  ])
1139
1139
  );
1140
-
1140
+
1141
1141
  // Verify actions are sorted by name (alphabetically)
1142
1142
  expect(appInitData.customActions[0].name).toBe('Another Valid Action');
1143
1143
  expect(appInitData.customActions[1].name).toBe('Valid Action');
@@ -2488,7 +2488,7 @@ describe('Unit test case for ts embed', () => {
2488
2488
  });
2489
2489
 
2490
2490
  afterAll((): void => {
2491
- window.location = location as any;
2491
+ (window.location as any) = location;
2492
2492
  });
2493
2493
 
2494
2494
  it('get url params for TS', () => {
@@ -3345,4 +3345,115 @@ describe('Unit test case for ts embed', () => {
3345
3345
  expect(searchEmbed.isEmbedContainerLoaded).toBe(true);
3346
3346
  });
3347
3347
  });
3348
+
3349
+ describe('Online event listener registration after auth failure', () => {
3350
+ beforeAll(() => {
3351
+ init({
3352
+ thoughtSpotHost: 'tshost',
3353
+ authType: AuthType.None,
3354
+ loginFailedMessage: 'Not logged in',
3355
+ });
3356
+ });
3357
+
3358
+ test('should register online event listener when authentication fails', async () => {
3359
+ const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
3360
+ jest.spyOn(baseInstance, 'getAuthPromise').mockRejectedValueOnce(
3361
+ new Error('Auth failed'),
3362
+ );
3363
+ const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
3364
+ addEventListenerSpy.mockClear();
3365
+ await searchEmbed.render();
3366
+ await executeAfterWait(() => {
3367
+ expect(getRootEl().innerHTML).toContain('Not logged in');
3368
+ const onlineListenerCalls = addEventListenerSpy.mock.calls.filter(
3369
+ (call) => call[0] === 'online',
3370
+ );
3371
+ expect(onlineListenerCalls).toHaveLength(1);
3372
+ const offlineListenerCalls = addEventListenerSpy.mock.calls.filter(
3373
+ (call) => call[0] === 'offline',
3374
+ );
3375
+ expect(offlineListenerCalls).toHaveLength(1);
3376
+ const messageListenerCalls = addEventListenerSpy.mock.calls.filter(
3377
+ (call) => call[0] === 'message',
3378
+ );
3379
+ expect(messageListenerCalls).toHaveLength(0);
3380
+ });
3381
+
3382
+ addEventListenerSpy.mockRestore();
3383
+ });
3384
+
3385
+ test('should attempt to trigger reload when online event occurs after auth failure', async () => {
3386
+ jest.spyOn(baseInstance, 'getAuthPromise').mockRejectedValueOnce(
3387
+ new Error('Auth failed'),
3388
+ );
3389
+ const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
3390
+ const triggerSpy = jest.spyOn(searchEmbed, 'trigger').mockResolvedValue(null);
3391
+ await searchEmbed.render();
3392
+
3393
+ await executeAfterWait(() => {
3394
+ expect(getRootEl().innerHTML).toContain('Not logged in');
3395
+ triggerSpy.mockClear();
3396
+ const onlineEvent = new Event('online');
3397
+ window.dispatchEvent(onlineEvent);
3398
+ expect(triggerSpy).toHaveBeenCalledWith(HostEvent.Reload);
3399
+ });
3400
+
3401
+ triggerSpy.mockReset();
3402
+ });
3403
+
3404
+ test('should handle online event gracefully when no iframe exists', async () => {
3405
+ jest.spyOn(baseInstance, 'getAuthPromise').mockRejectedValueOnce(
3406
+ new Error('Auth failed'),
3407
+ );
3408
+ const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
3409
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
3410
+ await searchEmbed.render();
3411
+ await executeAfterWait(() => {
3412
+ expect(getRootEl().innerHTML).toContain('Not logged in');
3413
+ const onlineEvent = new Event('online');
3414
+ expect(() => {
3415
+ window.dispatchEvent(onlineEvent);
3416
+ }).not.toThrow();
3417
+ });
3418
+
3419
+ errorSpy.mockReset();
3420
+ });
3421
+
3422
+ test('should register all event listeners when authentication succeeds', async () => {
3423
+ const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
3424
+ jest.spyOn(baseInstance, 'getAuthPromise').mockResolvedValueOnce(true);
3425
+ const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
3426
+ addEventListenerSpy.mockClear();
3427
+ await searchEmbed.render();
3428
+ await executeAfterWait(() => {
3429
+ const onlineListenerCalls = addEventListenerSpy.mock.calls.filter(
3430
+ (call) => call[0] === 'online',
3431
+ );
3432
+ expect(onlineListenerCalls).toHaveLength(1);
3433
+ const offlineListenerCalls = addEventListenerSpy.mock.calls.filter(
3434
+ (call) => call[0] === 'offline',
3435
+ );
3436
+ expect(offlineListenerCalls).toHaveLength(1);
3437
+ const messageListenerCalls = addEventListenerSpy.mock.calls.filter(
3438
+ (call) => call[0] === 'message',
3439
+ );
3440
+ expect(messageListenerCalls).toHaveLength(1);
3441
+ });
3442
+
3443
+ addEventListenerSpy.mockRestore();
3444
+ });
3445
+ test('should successfully trigger reload when online event occurs after auth success', async () => {
3446
+ jest.spyOn(baseInstance, 'getAuthPromise').mockResolvedValueOnce(true);
3447
+ const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
3448
+ const triggerSpy = jest.spyOn(searchEmbed, 'trigger').mockResolvedValue({} as any);
3449
+ await searchEmbed.render();
3450
+ await executeAfterWait(() => {
3451
+ triggerSpy.mockClear();
3452
+ const onlineEvent = new Event('online');
3453
+ window.dispatchEvent(onlineEvent);
3454
+ expect(triggerSpy).toHaveBeenCalledWith(HostEvent.Reload);
3455
+ });
3456
+ triggerSpy.mockReset();
3457
+ });
3458
+ });
3348
3459
  });
@@ -71,6 +71,7 @@ import { getEmbedConfig } from './embedConfig';
71
71
  import { ERROR_MESSAGE } from '../errors';
72
72
  import { getPreauthInfo } from '../utils/sessionInfoService';
73
73
  import { HostEventClient } from './hostEventClient/host-event-client';
74
+ import { getInterceptInitData, handleInterceptEvent, processLegacyInterceptResponse } from '../api-intercept';
74
75
 
75
76
  const { version } = pkgInfo;
76
77
 
@@ -201,7 +202,7 @@ export class TsEmbed {
201
202
  });
202
203
  const embedConfig = getEmbedConfig();
203
204
  this.embedConfig = embedConfig;
204
-
205
+
205
206
  this.hostEventClient = new HostEventClient(this.iFrame);
206
207
  this.isReadyForRenderPromise = getInitPromise().then(async () => {
207
208
  if (!embedConfig.authTriggerContainer && !embedConfig.useEventForSAMLPopup) {
@@ -312,31 +313,11 @@ export class TsEmbed {
312
313
  private subscribedListeners: Record<string, any> = {};
313
314
 
314
315
  /**
315
- * Adds a global event listener to window for "message" events.
316
- * ThoughtSpot detects if a particular event is targeted to this
317
- * embed instance through an identifier contained in the payload,
318
- * and executes the registered callbacks accordingly.
316
+ * Subscribe to network events (online/offline) that should
317
+ * work regardless of auth status
319
318
  */
320
- private subscribeToEvents() {
321
- this.unsubscribeToEvents();
322
- const messageEventListener = (event: MessageEvent<any>) => {
323
- const eventType = this.getEventType(event);
324
- const eventPort = this.getEventPort(event);
325
- const eventData = this.formatEventData(event, eventType);
326
- if (event.source === this.iFrame.contentWindow) {
327
- this.executeCallbacks(
328
- eventType,
329
- processEventData(
330
- eventType,
331
- eventData,
332
- this.thoughtSpotHost,
333
- this.isPreRendered ? this.preRenderWrapper : this.el,
334
- ),
335
- eventPort,
336
- );
337
- }
338
- };
339
- window.addEventListener('message', messageEventListener);
319
+ private subscribeToNetworkEvents() {
320
+ this.unsubscribeToNetworkEvents();
340
321
 
341
322
  const onlineEventListener = (e: Event) => {
342
323
  this.trigger(HostEvent.Reload);
@@ -344,7 +325,7 @@ export class TsEmbed {
344
325
  window.addEventListener('online', onlineEventListener);
345
326
 
346
327
  const offlineEventListener = (e: Event) => {
347
- const offlineWarning = 'Network not Detected. Embed is offline. Please reconnect and refresh';
328
+ const offlineWarning = ERROR_MESSAGE.OFFLINE_WARNING;
348
329
  this.executeCallbacks(EmbedEvent.Error, {
349
330
  offlineWarning,
350
331
  });
@@ -352,11 +333,84 @@ export class TsEmbed {
352
333
  };
353
334
  window.addEventListener('offline', offlineEventListener);
354
335
 
355
- this.subscribedListeners = {
356
- message: messageEventListener,
357
- online: onlineEventListener,
358
- offline: offlineEventListener,
359
- };
336
+ this.subscribedListeners.online = onlineEventListener;
337
+ this.subscribedListeners.offline = offlineEventListener;
338
+ }
339
+
340
+ private messageEventListener = async (event: MessageEvent<any>) => {
341
+ const eventType = this.getEventType(event);
342
+ const eventPort = this.getEventPort(event);
343
+ const eventData = this.formatEventData(event, eventType);
344
+ if (event.source === this.iFrame.contentWindow) {
345
+ const processedEventData = await processEventData(
346
+ eventType,
347
+ eventData,
348
+ this.thoughtSpotHost,
349
+ this.isPreRendered ? this.preRenderWrapper : this.el,
350
+ );
351
+
352
+ const executeEvent = (_eventType: EmbedEvent, data: any) => {
353
+ this.executeCallbacks(_eventType, data, eventPort);
354
+ }
355
+
356
+ if (eventType === EmbedEvent.ApiIntercept && this.viewConfig.enableApiIntercept) {
357
+ const getUnsavedAnswerTml = async (props: { sessionId?: string, vizId?: string }) => {
358
+ const response = await this.triggerUIPassThrough(UIPassthroughEvent.GetUnsavedAnswerTML, props);
359
+ return response[0]?.value;
360
+ }
361
+ handleInterceptEvent({ eventData: processedEventData, executeEvent, embedConfig: this.embedConfig, viewConfig: this.viewConfig, getUnsavedAnswerTml });
362
+ return;
363
+ }
364
+
365
+ this.executeCallbacks(
366
+ eventType,
367
+ processedEventData,
368
+ eventPort,
369
+ );
370
+ }
371
+ };
372
+ /**
373
+ * Subscribe to message events that depend on successful iframe setup
374
+ */
375
+ private subscribeToMessageEvents() {
376
+ this.unsubscribeToMessageEvents();
377
+
378
+ window.addEventListener('message', this.messageEventListener);
379
+
380
+ this.subscribedListeners.message = this.messageEventListener;
381
+ }
382
+
383
+
384
+ /**
385
+ * Adds event listeners for both network and message events.
386
+ * This maintains backward compatibility with the existing method.
387
+ * Adds a global event listener to window for "message" events.
388
+ * ThoughtSpot detects if a particular event is targeted to this
389
+ * embed instance through an identifier contained in the payload,
390
+ * and executes the registered callbacks accordingly.
391
+ */
392
+ private subscribeToEvents() {
393
+ this.subscribeToNetworkEvents();
394
+ this.subscribeToMessageEvents();
395
+ }
396
+
397
+
398
+ private unsubscribeToNetworkEvents() {
399
+ if (this.subscribedListeners.online) {
400
+ window.removeEventListener('online', this.subscribedListeners.online);
401
+ delete this.subscribedListeners.online;
402
+ }
403
+ if (this.subscribedListeners.offline) {
404
+ window.removeEventListener('offline', this.subscribedListeners.offline);
405
+ delete this.subscribedListeners.offline;
406
+ }
407
+ }
408
+
409
+ private unsubscribeToMessageEvents() {
410
+ if (this.subscribedListeners.message) {
411
+ window.removeEventListener('message', this.subscribedListeners.message);
412
+ delete this.subscribedListeners.message;
413
+ }
360
414
  }
361
415
 
362
416
  private unsubscribeToEvents() {
@@ -391,7 +445,7 @@ export class TsEmbed {
391
445
  message: customActionsResult.errors,
392
446
  });
393
447
  }
394
- return {
448
+ const baseInitData = {
395
449
  customisations: getCustomisations(this.embedConfig, this.viewConfig),
396
450
  authToken,
397
451
  runtimeFilterParams: this.viewConfig.excludeRuntimeFiltersfromURL
@@ -410,7 +464,10 @@ export class TsEmbed {
410
464
  this.embedConfig.customVariablesForThirdPartyTools || {},
411
465
  hiddenListColumns: this.viewConfig.hiddenListColumns || [],
412
466
  customActions: customActionsResult.actions,
467
+ ...getInterceptInitData(this.embedConfig, this.viewConfig),
413
468
  };
469
+
470
+ return baseInitData;
414
471
  }
415
472
 
416
473
  protected async getAppInitData() {
@@ -798,6 +855,9 @@ export class TsEmbed {
798
855
 
799
856
  uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_START);
800
857
 
858
+ // Always subscribe to network events, regardless of auth status
859
+ this.subscribeToNetworkEvents();
860
+
801
861
  return getAuthPromise()
802
862
  ?.then((isLoggedIn: boolean) => {
803
863
  if (!isLoggedIn) {
@@ -843,7 +903,9 @@ export class TsEmbed {
843
903
  el.remove();
844
904
  });
845
905
  }
846
- this.subscribeToEvents();
906
+ // Subscribe to message events only after successful
907
+ // auth and iframe setup
908
+ this.subscribeToMessageEvents();
847
909
  })
848
910
  .catch((error) => {
849
911
  nextInQueue();
@@ -983,6 +1045,21 @@ export class TsEmbed {
983
1045
  this.iFrame.style.height = getCssDimension(height);
984
1046
  }
985
1047
 
1048
+ protected createEmbedEventResponder = (eventPort: MessagePort | void, eventType: EmbedEvent) => {
1049
+
1050
+ const { enableApiIntercept } = getInterceptInitData(this.embedConfig, this.viewConfig);
1051
+ if (eventType === EmbedEvent.OnBeforeGetVizDataIntercept && enableApiIntercept) {
1052
+ return (payload: any) => {
1053
+ const payloadToSend = processLegacyInterceptResponse(payload);
1054
+ this.triggerEventOnPort(eventPort, payloadToSend);
1055
+ }
1056
+ }
1057
+
1058
+ return (payload: any) => {
1059
+ this.triggerEventOnPort(eventPort, payload);
1060
+ }
1061
+ }
1062
+
986
1063
  /**
987
1064
  * Executes all registered event handlers for a particular event type
988
1065
  * @param eventType The event type
@@ -1007,9 +1084,8 @@ export class TsEmbed {
1007
1084
  // payload
1008
1085
  || (!callbackObj.options.start && dataStatus === embedEventStatus.END)
1009
1086
  ) {
1010
- callbackObj.callback(data, (payload) => {
1011
- this.triggerEventOnPort(eventPort, payload);
1012
- });
1087
+ const responder = this.createEmbedEventResponder(eventPort, eventType);
1088
+ callbackObj.callback(data, responder);
1013
1089
  }
1014
1090
  });
1015
1091
  }
@@ -1151,12 +1227,12 @@ export class TsEmbed {
1151
1227
  }
1152
1228
  }
1153
1229
 
1154
- /**
1155
- * @hidden
1156
- * Internal state to track if the embed container is loaded.
1157
- * This is used to trigger events after the embed container is loaded.
1158
- */
1159
- public isEmbedContainerLoaded = false;
1230
+ /**
1231
+ * @hidden
1232
+ * Internal state to track if the embed container is loaded.
1233
+ * This is used to trigger events after the embed container is loaded.
1234
+ */
1235
+ public isEmbedContainerLoaded = false;
1160
1236
 
1161
1237
  /**
1162
1238
  * @hidden
@@ -1243,6 +1319,16 @@ export class TsEmbed {
1243
1319
  this.handleError('Host event type is undefined');
1244
1320
  return null;
1245
1321
  }
1322
+
1323
+ // Check if iframe exists before triggering -
1324
+ // this prevents the error when auth fails
1325
+ if (!this.iFrame) {
1326
+ logger.debug(
1327
+ `Cannot trigger ${messageType} - iframe not available (likely due to auth failure)`,
1328
+ );
1329
+ return null;
1330
+ }
1331
+
1246
1332
  // send an empty object, this is needed for liveboard default handlers
1247
1333
  return this.hostEventClient.triggerHostEvent(messageType, data);
1248
1334
  }
@@ -1298,7 +1384,7 @@ export class TsEmbed {
1298
1384
  }
1299
1385
  this.isPreRendered = true;
1300
1386
  this.showPreRenderByDefault = showPreRenderByDefault;
1301
-
1387
+
1302
1388
  const isAlreadyRendered = this.connectPreRendered();
1303
1389
  if (isAlreadyRendered && !replaceExistingPreRender) {
1304
1390
  return this;
package/src/errors.ts CHANGED
@@ -19,6 +19,7 @@ export const ERROR_MESSAGE = {
19
19
  MISSING_REPORTING_OBSERVER: 'ReportingObserver not supported',
20
20
  RENDER_CALLED_BEFORE_INIT: 'Looks like render was called before calling init, the render won\'t start until init is called.\nFor more info check\n1. https://developers.thoughtspot.com/docs/Function_init#_init\n2.https://developers.thoughtspot.com/docs/getting-started#initSdk',
21
21
  SPOTTER_AGENT_NOT_INITIALIZED: 'SpotterAgent not initialized',
22
+ OFFLINE_WARNING : 'Network not Detected. Embed is offline. Please reconnect and refresh',
22
23
  };
23
24
 
24
25
  export const CUSTOM_ACTIONS_ERROR_MESSAGE = {
package/src/index.ts CHANGED
@@ -64,6 +64,7 @@ import {
64
64
  ListPageColumns,
65
65
  CustomActionsPosition,
66
66
  CustomActionTarget,
67
+ InterceptedApiType,
67
68
  } from './types';
68
69
  import { CustomCssVariables } from './css-variables';
69
70
  import { SageEmbed, SageViewConfig } from './embed/sage';
@@ -152,6 +153,7 @@ export {
152
153
  DataPanelCustomColumnGroupsAccordionState,
153
154
  CustomActionsPosition,
154
155
  CustomActionTarget,
156
+ InterceptedApiType,
155
157
  };
156
158
 
157
159
  export { resetCachedAuthToken } from './authToken';
@@ -59,4 +59,5 @@ export {
59
59
  resetCachedAuthToken,
60
60
  UIPassthroughEvent,
61
61
  DataPanelCustomColumnGroupsAccordionState,
62
+ InterceptedApiType,
62
63
  } from '../index';