@thoughtspot/visual-embed-sdk 1.41.1 → 1.42.1-HE2

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 (186) hide show
  1. package/cjs/package.json +9 -9
  2. package/cjs/src/css-variables.d.ts +52 -14
  3. package/cjs/src/css-variables.d.ts.map +1 -1
  4. package/cjs/src/embed/app.d.ts +20 -0
  5. package/cjs/src/embed/app.d.ts.map +1 -1
  6. package/cjs/src/embed/app.js +7 -1
  7. package/cjs/src/embed/app.js.map +1 -1
  8. package/cjs/src/embed/app.spec.js +52 -0
  9. package/cjs/src/embed/app.spec.js.map +1 -1
  10. package/cjs/src/embed/bodyless-conversation.d.ts +1 -0
  11. package/cjs/src/embed/bodyless-conversation.d.ts.map +1 -1
  12. package/cjs/src/embed/bodyless-conversation.js +7 -3
  13. package/cjs/src/embed/bodyless-conversation.js.map +1 -1
  14. package/cjs/src/embed/conversation.d.ts +1 -0
  15. package/cjs/src/embed/conversation.d.ts.map +1 -1
  16. package/cjs/src/embed/conversation.js +7 -2
  17. package/cjs/src/embed/conversation.js.map +1 -1
  18. package/cjs/src/embed/hostEventClient/host-event-client.d.ts +3 -3
  19. package/cjs/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  20. package/cjs/src/embed/hostEventClient/host-event-client.js +6 -6
  21. package/cjs/src/embed/hostEventClient/host-event-client.js.map +1 -1
  22. package/cjs/src/embed/liveboard.d.ts +1 -0
  23. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  24. package/cjs/src/embed/liveboard.js +10 -2
  25. package/cjs/src/embed/liveboard.js.map +1 -1
  26. package/cjs/src/embed/liveboard.spec.js +35 -0
  27. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  28. package/cjs/src/embed/sage.d.ts +1 -0
  29. package/cjs/src/embed/sage.d.ts.map +1 -1
  30. package/cjs/src/embed/sage.js +10 -6
  31. package/cjs/src/embed/sage.js.map +1 -1
  32. package/cjs/src/embed/search-bar.d.ts +1 -0
  33. package/cjs/src/embed/search-bar.d.ts.map +1 -1
  34. package/cjs/src/embed/search-bar.js +11 -7
  35. package/cjs/src/embed/search-bar.js.map +1 -1
  36. package/cjs/src/embed/search.d.ts +1 -0
  37. package/cjs/src/embed/search.d.ts.map +1 -1
  38. package/cjs/src/embed/search.js +7 -8
  39. package/cjs/src/embed/search.js.map +1 -1
  40. package/cjs/src/embed/ts-embed.d.ts +20 -5
  41. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  42. package/cjs/src/embed/ts-embed.js +84 -31
  43. package/cjs/src/embed/ts-embed.js.map +1 -1
  44. package/cjs/src/embed/ts-embed.spec.js +83 -0
  45. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  46. package/cjs/src/errors.d.ts +1 -0
  47. package/cjs/src/errors.d.ts.map +1 -1
  48. package/cjs/src/errors.js +1 -0
  49. package/cjs/src/errors.js.map +1 -1
  50. package/cjs/src/react/all-types-export.spec.js +1 -1
  51. package/cjs/src/react/all-types-export.spec.js.map +1 -1
  52. package/cjs/src/react/util.js.map +1 -1
  53. package/cjs/src/react/util.spec.d.ts +2 -0
  54. package/cjs/src/react/util.spec.d.ts.map +1 -0
  55. package/cjs/src/react/util.spec.js +78 -0
  56. package/cjs/src/react/util.spec.js.map +1 -0
  57. package/cjs/src/types.d.ts +153 -64
  58. package/cjs/src/types.d.ts.map +1 -1
  59. package/cjs/src/types.js +136 -62
  60. package/cjs/src/types.js.map +1 -1
  61. package/cjs/src/utils/processTrigger.d.ts +2 -1
  62. package/cjs/src/utils/processTrigger.d.ts.map +1 -1
  63. package/cjs/src/utils/processTrigger.js +4 -2
  64. package/cjs/src/utils/processTrigger.js.map +1 -1
  65. package/dist/{index-DQueHwfQ.js → index-CWQnMX2L.js} +1 -1
  66. package/dist/index-Djtv-y7A.js +7371 -0
  67. package/dist/src/css-variables.d.ts +52 -14
  68. package/dist/src/css-variables.d.ts.map +1 -1
  69. package/dist/src/embed/app.d.ts +20 -0
  70. package/dist/src/embed/app.d.ts.map +1 -1
  71. package/dist/src/embed/bodyless-conversation.d.ts +1 -0
  72. package/dist/src/embed/bodyless-conversation.d.ts.map +1 -1
  73. package/dist/src/embed/conversation.d.ts +1 -0
  74. package/dist/src/embed/conversation.d.ts.map +1 -1
  75. package/dist/src/embed/hostEventClient/host-event-client.d.ts +3 -3
  76. package/dist/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  77. package/dist/src/embed/liveboard.d.ts +1 -0
  78. package/dist/src/embed/liveboard.d.ts.map +1 -1
  79. package/dist/src/embed/sage.d.ts +1 -0
  80. package/dist/src/embed/sage.d.ts.map +1 -1
  81. package/dist/src/embed/search-bar.d.ts +1 -0
  82. package/dist/src/embed/search-bar.d.ts.map +1 -1
  83. package/dist/src/embed/search.d.ts +1 -0
  84. package/dist/src/embed/search.d.ts.map +1 -1
  85. package/dist/src/embed/ts-embed.d.ts +20 -5
  86. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  87. package/dist/src/errors.d.ts +1 -0
  88. package/dist/src/errors.d.ts.map +1 -1
  89. package/dist/src/react/util.spec.d.ts +2 -0
  90. package/dist/src/react/util.spec.d.ts.map +1 -0
  91. package/dist/src/types.d.ts +153 -64
  92. package/dist/src/types.d.ts.map +1 -1
  93. package/dist/src/utils/processTrigger.d.ts +2 -1
  94. package/dist/src/utils/processTrigger.d.ts.map +1 -1
  95. package/dist/tsembed-react.es.js +294 -134
  96. package/dist/tsembed-react.js +293 -133
  97. package/dist/tsembed.es.js +294 -134
  98. package/dist/tsembed.js +293 -133
  99. package/dist/visual-embed-sdk-react-full.d.ts +9401 -9916
  100. package/dist/visual-embed-sdk-react.d.ts +9273 -9922
  101. package/dist/visual-embed-sdk.d.ts +9440 -9533
  102. package/lib/package.json +9 -9
  103. package/lib/src/css-variables.d.ts +52 -14
  104. package/lib/src/css-variables.d.ts.map +1 -1
  105. package/lib/src/embed/app.d.ts +20 -0
  106. package/lib/src/embed/app.d.ts.map +1 -1
  107. package/lib/src/embed/app.js +7 -1
  108. package/lib/src/embed/app.js.map +1 -1
  109. package/lib/src/embed/app.spec.js +52 -0
  110. package/lib/src/embed/app.spec.js.map +1 -1
  111. package/lib/src/embed/bodyless-conversation.d.ts +1 -0
  112. package/lib/src/embed/bodyless-conversation.d.ts.map +1 -1
  113. package/lib/src/embed/bodyless-conversation.js +7 -3
  114. package/lib/src/embed/bodyless-conversation.js.map +1 -1
  115. package/lib/src/embed/conversation.d.ts +1 -0
  116. package/lib/src/embed/conversation.d.ts.map +1 -1
  117. package/lib/src/embed/conversation.js +7 -2
  118. package/lib/src/embed/conversation.js.map +1 -1
  119. package/lib/src/embed/hostEventClient/host-event-client.d.ts +3 -3
  120. package/lib/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  121. package/lib/src/embed/hostEventClient/host-event-client.js +6 -6
  122. package/lib/src/embed/hostEventClient/host-event-client.js.map +1 -1
  123. package/lib/src/embed/liveboard.d.ts +1 -0
  124. package/lib/src/embed/liveboard.d.ts.map +1 -1
  125. package/lib/src/embed/liveboard.js +10 -2
  126. package/lib/src/embed/liveboard.js.map +1 -1
  127. package/lib/src/embed/liveboard.spec.js +35 -0
  128. package/lib/src/embed/liveboard.spec.js.map +1 -1
  129. package/lib/src/embed/sage.d.ts +1 -0
  130. package/lib/src/embed/sage.d.ts.map +1 -1
  131. package/lib/src/embed/sage.js +10 -6
  132. package/lib/src/embed/sage.js.map +1 -1
  133. package/lib/src/embed/search-bar.d.ts +1 -0
  134. package/lib/src/embed/search-bar.d.ts.map +1 -1
  135. package/lib/src/embed/search-bar.js +11 -7
  136. package/lib/src/embed/search-bar.js.map +1 -1
  137. package/lib/src/embed/search.d.ts +1 -0
  138. package/lib/src/embed/search.d.ts.map +1 -1
  139. package/lib/src/embed/search.js +7 -8
  140. package/lib/src/embed/search.js.map +1 -1
  141. package/lib/src/embed/ts-embed.d.ts +20 -5
  142. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  143. package/lib/src/embed/ts-embed.js +84 -31
  144. package/lib/src/embed/ts-embed.js.map +1 -1
  145. package/lib/src/embed/ts-embed.spec.js +83 -0
  146. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  147. package/lib/src/errors.d.ts +1 -0
  148. package/lib/src/errors.d.ts.map +1 -1
  149. package/lib/src/errors.js +1 -0
  150. package/lib/src/errors.js.map +1 -1
  151. package/lib/src/react/all-types-export.spec.js +1 -1
  152. package/lib/src/react/all-types-export.spec.js.map +1 -1
  153. package/lib/src/react/util.js.map +1 -1
  154. package/lib/src/react/util.spec.d.ts +2 -0
  155. package/lib/src/react/util.spec.d.ts.map +1 -0
  156. package/lib/src/react/util.spec.js +76 -0
  157. package/lib/src/react/util.spec.js.map +1 -0
  158. package/lib/src/types.d.ts +153 -64
  159. package/lib/src/types.d.ts.map +1 -1
  160. package/lib/src/types.js +136 -62
  161. package/lib/src/types.js.map +1 -1
  162. package/lib/src/utils/processTrigger.d.ts +2 -1
  163. package/lib/src/utils/processTrigger.d.ts.map +1 -1
  164. package/lib/src/utils/processTrigger.js +4 -2
  165. package/lib/src/utils/processTrigger.js.map +1 -1
  166. package/package.json +9 -9
  167. package/src/css-variables.ts +53 -16
  168. package/src/embed/app.spec.ts +73 -0
  169. package/src/embed/app.ts +30 -0
  170. package/src/embed/bodyless-conversation.ts +8 -3
  171. package/src/embed/conversation.ts +17 -2
  172. package/src/embed/hostEventClient/host-event-client.ts +7 -3
  173. package/src/embed/liveboard.spec.ts +44 -0
  174. package/src/embed/liveboard.ts +12 -1
  175. package/src/embed/sage.ts +14 -9
  176. package/src/embed/search-bar.tsx +14 -7
  177. package/src/embed/search.ts +18 -7
  178. package/src/embed/ts-embed.spec.ts +112 -1
  179. package/src/embed/ts-embed.ts +104 -37
  180. package/src/errors.ts +1 -0
  181. package/src/react/all-types-export.spec.ts +1 -1
  182. package/src/react/util.spec.tsx +88 -0
  183. package/src/react/util.ts +3 -3
  184. package/src/types.ts +211 -121
  185. package/src/utils/processTrigger.ts +5 -2
  186. package/lib/src/visual-embed-sdk.d.ts +0 -9779
@@ -331,7 +331,7 @@ export const HiddenActionItemByDefaultForSearchEmbed = [
331
331
  ];
332
332
 
333
333
  export interface SearchAppInitData extends DefaultAppInitData {
334
- searchOptions?: SearchOptions;
334
+ searchOptions?: SearchOptions;
335
335
  }
336
336
 
337
337
  /**
@@ -381,7 +381,7 @@ export class SearchEmbed extends TsEmbed {
381
381
  return { ...defaultAppInitData, ...this.getSearchInitData() };
382
382
  }
383
383
 
384
- protected getEmbedParams(): string {
384
+ protected getEmbedParamsObject() {
385
385
  const {
386
386
  hideResults,
387
387
  enableSearchAssist,
@@ -398,7 +398,7 @@ export class SearchEmbed extends TsEmbed {
398
398
  collapseSearchBarInitially = false,
399
399
  enableCustomColumnGroups = false,
400
400
  isOnBeforeGetVizDataInterceptEnabled = false,
401
- /* eslint-disable-next-line max-len */
401
+
402
402
  dataPanelCustomGroupsAccordionInitialState = DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL,
403
403
  focusSearchBarOnRender = true,
404
404
  excludeRuntimeParametersfromURL,
@@ -443,7 +443,7 @@ export class SearchEmbed extends TsEmbed {
443
443
  }
444
444
 
445
445
  if (isOnBeforeGetVizDataInterceptEnabled) {
446
- /* eslint-disable-next-line max-len */
446
+
447
447
  queryParams[Param.IsOnBeforeGetVizDataInterceptEnabled] = isOnBeforeGetVizDataInterceptEnabled;
448
448
  }
449
449
 
@@ -460,7 +460,7 @@ export class SearchEmbed extends TsEmbed {
460
460
  }
461
461
 
462
462
  queryParams[Param.searchEmbed] = true;
463
- /* eslint-disable-next-line max-len */
463
+
464
464
  queryParams[Param.CollapseSearchBarInitially] = collapseSearchBarInitially || collapseSearchBar;
465
465
  queryParams[Param.EnableCustomColumnGroups] = enableCustomColumnGroups;
466
466
  if (dataPanelCustomGroupsAccordionInitialState
@@ -468,12 +468,23 @@ export class SearchEmbed extends TsEmbed {
468
468
  || dataPanelCustomGroupsAccordionInitialState
469
469
  === DataPanelCustomColumnGroupsAccordionState.EXPAND_FIRST
470
470
  ) {
471
- /* eslint-disable-next-line max-len */
471
+
472
472
  queryParams[Param.DataPanelCustomGroupsAccordionInitialState] = dataPanelCustomGroupsAccordionInitialState;
473
473
  } else {
474
- /* eslint-disable-next-line max-len */
474
+
475
475
  queryParams[Param.DataPanelCustomGroupsAccordionInitialState] = DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL;
476
476
  }
477
+ return queryParams;
478
+ }
479
+
480
+ protected getEmbedParams() {
481
+ const {
482
+ runtimeParameters,
483
+ runtimeFilters,
484
+ excludeRuntimeParametersfromURL,
485
+ excludeRuntimeFiltersfromURL,
486
+ } = this.viewConfig;
487
+ const queryParams = this.getEmbedParamsObject();
477
488
  let query = '';
478
489
  const queryParamsString = getQueryParamString(queryParams, true);
479
490
  if (queryParamsString) {
@@ -2488,7 +2488,7 @@ describe('Unit test case for ts embed', () => {
2488
2488
  });
2489
2489
 
2490
2490
  afterAll((): void => {
2491
- window.location = location;
2491
+ window.location = location as any;
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
  });
@@ -199,11 +199,11 @@ export class TsEmbed {
199
199
  uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_EMBED_CREATE, {
200
200
  ...viewConfig,
201
201
  });
202
+ const embedConfig = getEmbedConfig();
203
+ this.embedConfig = embedConfig;
204
+
202
205
  this.hostEventClient = new HostEventClient(this.iFrame);
203
-
204
206
  this.isReadyForRenderPromise = getInitPromise().then(async () => {
205
- const embedConfig = getEmbedConfig();
206
- this.embedConfig = embedConfig;
207
207
  if (!embedConfig.authTriggerContainer && !embedConfig.useEventForSAMLPopup) {
208
208
  this.embedConfig.authTriggerContainer = domSelector;
209
209
  }
@@ -312,13 +312,36 @@ export class TsEmbed {
312
312
  private subscribedListeners: Record<string, any> = {};
313
313
 
314
314
  /**
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.
315
+ * Subscribe to network events (online/offline) that should
316
+ * work regardless of auth status
319
317
  */
320
- private subscribeToEvents() {
321
- this.unsubscribeToEvents();
318
+ private subscribeToNetworkEvents() {
319
+ this.unsubscribeToNetworkEvents();
320
+
321
+ const onlineEventListener = (e: Event) => {
322
+ this.trigger(HostEvent.Reload);
323
+ };
324
+ window.addEventListener('online', onlineEventListener);
325
+
326
+ const offlineEventListener = (e: Event) => {
327
+ const offlineWarning = ERROR_MESSAGE.OFFLINE_WARNING;
328
+ this.executeCallbacks(EmbedEvent.Error, {
329
+ offlineWarning,
330
+ });
331
+ logger.warn(offlineWarning);
332
+ };
333
+ window.addEventListener('offline', offlineEventListener);
334
+
335
+ this.subscribedListeners.online = onlineEventListener;
336
+ this.subscribedListeners.offline = offlineEventListener;
337
+ }
338
+
339
+ /**
340
+ * Subscribe to message events that depend on successful iframe setup
341
+ */
342
+ private subscribeToMessageEvents() {
343
+ this.unsubscribeToMessageEvents();
344
+
322
345
  const messageEventListener = (event: MessageEvent<any>) => {
323
346
  const eventType = this.getEventType(event);
324
347
  const eventPort = this.getEventPort(event);
@@ -338,25 +361,37 @@ export class TsEmbed {
338
361
  };
339
362
  window.addEventListener('message', messageEventListener);
340
363
 
341
- const onlineEventListener = (e: Event) => {
342
- this.trigger(HostEvent.Reload);
343
- };
344
- window.addEventListener('online', onlineEventListener);
364
+ this.subscribedListeners.message = messageEventListener;
365
+ }
366
+ /**
367
+ * Adds event listeners for both network and message events.
368
+ * This maintains backward compatibility with the existing method.
369
+ * Adds a global event listener to window for "message" events.
370
+ * ThoughtSpot detects if a particular event is targeted to this
371
+ * embed instance through an identifier contained in the payload,
372
+ * and executes the registered callbacks accordingly.
373
+ */
374
+ private subscribeToEvents() {
375
+ this.subscribeToNetworkEvents();
376
+ this.subscribeToMessageEvents();
377
+ }
345
378
 
346
- const offlineEventListener = (e: Event) => {
347
- const offlineWarning = 'Network not Detected. Embed is offline. Please reconnect and refresh';
348
- this.executeCallbacks(EmbedEvent.Error, {
349
- offlineWarning,
350
- });
351
- logger.warn(offlineWarning);
352
- };
353
- window.addEventListener('offline', offlineEventListener);
379
+ private unsubscribeToNetworkEvents() {
380
+ if (this.subscribedListeners.online) {
381
+ window.removeEventListener('online', this.subscribedListeners.online);
382
+ delete this.subscribedListeners.online;
383
+ }
384
+ if (this.subscribedListeners.offline) {
385
+ window.removeEventListener('offline', this.subscribedListeners.offline);
386
+ delete this.subscribedListeners.offline;
387
+ }
388
+ }
354
389
 
355
- this.subscribedListeners = {
356
- message: messageEventListener,
357
- online: onlineEventListener,
358
- offline: offlineEventListener,
359
- };
390
+ private unsubscribeToMessageEvents() {
391
+ if (this.subscribedListeners.message) {
392
+ window.removeEventListener('message', this.subscribedListeners.message);
393
+ delete this.subscribedListeners.message;
394
+ }
360
395
  }
361
396
 
362
397
  private unsubscribeToEvents() {
@@ -494,10 +529,10 @@ export class TsEmbed {
494
529
  this.on(EmbedEvent.APP_INIT, this.appInitCb, { start: false }, true);
495
530
  this.on(EmbedEvent.AuthExpire, this.updateAuthToken, { start: false }, true);
496
531
  this.on(EmbedEvent.IdleSessionTimeout, this.idleSessionTimeout, { start: false }, true);
497
-
498
- const embedListenerReadyHandler = this.createEmbedContainerHandler(EmbedEvent.EmbedListenerReady);
532
+
533
+ const embedListenerReadyHandler = this.createEmbedContainerHandler(EmbedEvent.EmbedListenerReady);
499
534
  this.on(EmbedEvent.EmbedListenerReady, embedListenerReadyHandler, { start: false }, true);
500
-
535
+
501
536
  const authInitHandler = this.createEmbedContainerHandler(EmbedEvent.AuthInit);
502
537
  this.on(EmbedEvent.AuthInit, authInitHandler, { start: false }, true);
503
538
  };
@@ -520,6 +555,12 @@ export class TsEmbed {
520
555
  return `${basePath}#`;
521
556
  }
522
557
 
558
+ protected getUpdateEmbedParamsObject() {
559
+ let queryParams = this.getEmbedParamsObject();
560
+ queryParams = { ...this.viewConfig, ...queryParams };
561
+ return queryParams;
562
+ }
563
+
523
564
  /**
524
565
  * Common query params set for all the embed modes.
525
566
  * @param queryParams
@@ -702,10 +743,15 @@ export class TsEmbed {
702
743
  }
703
744
 
704
745
  protected getEmbedParams() {
705
- const queryParams = this.getBaseQueryParams();
746
+ const queryParams = this.getEmbedParamsObject();
706
747
  return getQueryParamString(queryParams);
707
748
  }
708
749
 
750
+ protected getEmbedParamsObject() {
751
+ const params = this.getBaseQueryParams();
752
+ return params;
753
+ }
754
+
709
755
  protected getRootIframeSrc() {
710
756
  const query = this.getEmbedParams();
711
757
  return this.getEmbedBasePath(query);
@@ -787,6 +833,9 @@ export class TsEmbed {
787
833
 
788
834
  uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_START);
789
835
 
836
+ // Always subscribe to network events, regardless of auth status
837
+ this.subscribeToNetworkEvents();
838
+
790
839
  return getAuthPromise()
791
840
  ?.then((isLoggedIn: boolean) => {
792
841
  if (!isLoggedIn) {
@@ -832,7 +881,9 @@ export class TsEmbed {
832
881
  el.remove();
833
882
  });
834
883
  }
835
- this.subscribeToEvents();
884
+ // Subscribe to message events only after successful
885
+ // auth and iframe setup
886
+ this.subscribeToMessageEvents();
836
887
  })
837
888
  .catch((error) => {
838
889
  nextInQueue();
@@ -1140,12 +1191,12 @@ export class TsEmbed {
1140
1191
  }
1141
1192
  }
1142
1193
 
1143
- /**
1194
+ /**
1144
1195
  * @hidden
1145
1196
  * Internal state to track if the embed container is loaded.
1146
1197
  * This is used to trigger events after the embed container is loaded.
1147
1198
  */
1148
- public isEmbedContainerLoaded = false;
1199
+ public isEmbedContainerLoaded = false;
1149
1200
 
1150
1201
  /**
1151
1202
  * @hidden
@@ -1191,7 +1242,7 @@ export class TsEmbed {
1191
1242
  } else {
1192
1243
  logger.debug('pushing callback to embedContainerReadyCallbacks', callback);
1193
1244
  this.embedContainerReadyCallbacks.push(callback);
1194
- }
1245
+ }
1195
1246
  }
1196
1247
 
1197
1248
  protected createEmbedContainerHandler = (source: EmbedEvent.AuthInit | EmbedEvent.EmbedListenerReady) => () => {
@@ -1220,6 +1271,7 @@ export class TsEmbed {
1220
1271
  public async trigger<HostEventT extends HostEvent, PayloadT>(
1221
1272
  messageType: HostEventT,
1222
1273
  data: TriggerPayload<PayloadT, HostEventT> = {} as any,
1274
+ context: any = {},
1223
1275
  ): Promise<TriggerResponse<PayloadT, HostEventT>> {
1224
1276
  uploadMixpanelEvent(`${MIXPANEL_EVENT.VISUAL_SDK_TRIGGER}-${messageType}`);
1225
1277
 
@@ -1232,8 +1284,18 @@ export class TsEmbed {
1232
1284
  this.handleError('Host event type is undefined');
1233
1285
  return null;
1234
1286
  }
1287
+
1288
+ // Check if iframe exists before triggering -
1289
+ // this prevents the error when auth fails
1290
+ if (!this.iFrame) {
1291
+ logger.debug(
1292
+ `Cannot trigger ${messageType} - iframe not available (likely due to auth failure)`,
1293
+ );
1294
+ return null;
1295
+ }
1296
+
1235
1297
  // send an empty object, this is needed for liveboard default handlers
1236
- return this.hostEventClient.triggerHostEvent(messageType, data);
1298
+ return this.hostEventClient.triggerHostEvent(messageType, data, context);
1237
1299
  }
1238
1300
 
1239
1301
  /**
@@ -1279,6 +1341,7 @@ export class TsEmbed {
1279
1341
  * Creates the preRender shell
1280
1342
  * @param showPreRenderByDefault - Show the preRender after render, hidden by default
1281
1343
  */
1344
+
1282
1345
  public async preRender(showPreRenderByDefault = false, replaceExistingPreRender = false): Promise<TsEmbed> {
1283
1346
  if (!this.viewConfig.preRenderId) {
1284
1347
  logger.error(ERROR_MESSAGE.PRERENDER_ID_MISSING);
@@ -1413,8 +1476,14 @@ export class TsEmbed {
1413
1476
  return this.preRender(true);
1414
1477
  }
1415
1478
  this.validatePreRenderViewConfig(this.viewConfig);
1479
+ logger.debug('triggering UpdateEmbedParams', this.viewConfig);
1480
+ this.executeAfterEmbedContainerLoaded(() => {
1481
+ this.trigger(HostEvent.UpdateEmbedParams, this.getUpdateEmbedParamsObject());
1482
+ });
1416
1483
  }
1417
1484
 
1485
+ this.beforePrerenderVisible();
1486
+
1418
1487
  if (this.el) {
1419
1488
  this.syncPreRenderStyle();
1420
1489
  if (!this.viewConfig.doNotTrackPreRenderSize) {
@@ -1432,8 +1501,6 @@ export class TsEmbed {
1432
1501
  }
1433
1502
  }
1434
1503
 
1435
- this.beforePrerenderVisible();
1436
-
1437
1504
  removeStyleProperties(this.preRenderWrapper, ['z-index', 'opacity', 'pointer-events']);
1438
1505
 
1439
1506
  this.subscribeToEvents();
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 = {
@@ -6,6 +6,6 @@ describe('Exports', () => {
6
6
  });
7
7
 
8
8
  it('should not have undefined exports', () => {
9
- Object.keys(Exports).forEach((exportKey) => expect(Boolean(Exports[exportKey])).toBe(true));
9
+ Object.entries(Exports).forEach(([, exportValue]) => {expect(Boolean(exportValue)).toBe(true);});
10
10
  });
11
11
  });
@@ -0,0 +1,88 @@
1
+ import { getViewPropsAndListeners } from './util';
2
+ import { EmbedEvent, MessageCallback } from '../types';
3
+
4
+ describe('React util functions', () => {
5
+ describe('getViewPropsAndListeners', () => {
6
+ test('should return empty viewConfig and listeners for empty props', () => {
7
+ const props = {};
8
+ const result = getViewPropsAndListeners(props);
9
+
10
+ expect(result.viewConfig).toEqual({});
11
+ expect(result.listeners).toEqual({});
12
+ });
13
+
14
+ test('should separate view config properties from props', () => {
15
+ const props = {
16
+ frameParams: { width: 100, height: 200 },
17
+ showLiveboardTitle: true,
18
+ liveboardId: 'test-liveboard-id',
19
+ vizId: 'test-viz-id',
20
+ className: 'test-class',
21
+ style: { color: 'red' },
22
+ };
23
+
24
+ const result = getViewPropsAndListeners(props);
25
+
26
+ expect(result.viewConfig).toEqual({
27
+ frameParams: { width: 100, height: 200 },
28
+ showLiveboardTitle: true,
29
+ liveboardId: 'test-liveboard-id',
30
+ vizId: 'test-viz-id',
31
+ className: 'test-class',
32
+ style: { color: 'red' },
33
+ });
34
+ expect(result.listeners).toEqual({});
35
+ });
36
+
37
+ test('should separate event handlers from props', () => {
38
+ const onInit: MessageCallback = jest.fn();
39
+ const onLoad: MessageCallback = jest.fn();
40
+ const onData: MessageCallback = jest.fn();
41
+
42
+ const props = {
43
+ onInit,
44
+ onLoad,
45
+ onData,
46
+ };
47
+
48
+ const result = getViewPropsAndListeners(props);
49
+
50
+ expect(result.viewConfig).toEqual({});
51
+ expect(result.listeners).toEqual({
52
+ [EmbedEvent.Init]: onInit,
53
+ [EmbedEvent.Load]: onLoad,
54
+ [EmbedEvent.Data]: onData,
55
+ });
56
+ });
57
+
58
+ test('should handle both view config and event handlers', () => {
59
+ const onInit: MessageCallback = jest.fn();
60
+ const onAuthInit: MessageCallback = jest.fn();
61
+ const onQueryChanged: MessageCallback = jest.fn();
62
+
63
+ const props = {
64
+ liveboardId: 'test-liveboard-id',
65
+ showLiveboardTitle: false,
66
+ frameParams: { height: 500 },
67
+ onInit,
68
+ onAuthInit,
69
+ onQueryChanged,
70
+ className: 'embed-container',
71
+ };
72
+
73
+ const result = getViewPropsAndListeners(props);
74
+
75
+ expect(result.viewConfig).toEqual({
76
+ liveboardId: 'test-liveboard-id',
77
+ showLiveboardTitle: false,
78
+ frameParams: { height: 500 },
79
+ className: 'embed-container',
80
+ });
81
+ expect(result.listeners).toEqual({
82
+ [EmbedEvent.Init]: onInit,
83
+ [EmbedEvent.AuthInit]: onAuthInit,
84
+ [EmbedEvent.QueryChanged]: onQueryChanged,
85
+ });
86
+ });
87
+ });
88
+ });
package/src/react/util.ts CHANGED
@@ -24,10 +24,10 @@ export function getViewPropsAndListeners<
24
24
  return Object.keys(props).reduce(
25
25
  (accu, key) => {
26
26
  if (key.startsWith('on')) {
27
- const eventName = key.substr(2);
28
- accu.listeners[EmbedEvent[eventName]] = props[key];
27
+ const eventName = key.substr(2) as any;
28
+ (accu.listeners as any)[EmbedEvent[eventName as keyof typeof EmbedEvent] as any] = props[key as keyof T];
29
29
  } else {
30
- accu.viewConfig[key] = props[key];
30
+ (accu.viewConfig as any)[key] = props[key as keyof T];
31
31
  }
32
32
  return accu;
33
33
  },