@thoughtspot/visual-embed-sdk 1.47.3 → 1.49.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 (244) 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/css-variables.d.ts +140 -0
  12. package/cjs/src/css-variables.d.ts.map +1 -1
  13. package/cjs/src/embed/app.d.ts +63 -2
  14. package/cjs/src/embed/app.d.ts.map +1 -1
  15. package/cjs/src/embed/app.js +57 -6
  16. package/cjs/src/embed/app.js.map +1 -1
  17. package/cjs/src/embed/app.spec.js +200 -1
  18. package/cjs/src/embed/app.spec.js.map +1 -1
  19. package/cjs/src/embed/auto-frame-renderer.js +7 -2
  20. package/cjs/src/embed/auto-frame-renderer.js.map +1 -1
  21. package/cjs/src/embed/auto-frame-renderer.spec.js +385 -6
  22. package/cjs/src/embed/auto-frame-renderer.spec.js.map +1 -1
  23. package/cjs/src/embed/base.d.ts +1 -0
  24. package/cjs/src/embed/base.d.ts.map +1 -1
  25. package/cjs/src/embed/base.js +13 -1
  26. package/cjs/src/embed/base.js.map +1 -1
  27. package/cjs/src/embed/base.spec.js +21 -0
  28. package/cjs/src/embed/base.spec.js.map +1 -1
  29. package/cjs/src/embed/bodyless-conversation.spec.js +86 -0
  30. package/cjs/src/embed/bodyless-conversation.spec.js.map +1 -1
  31. package/cjs/src/embed/conversation.d.ts +16 -1
  32. package/cjs/src/embed/conversation.d.ts.map +1 -1
  33. package/cjs/src/embed/conversation.js +5 -1
  34. package/cjs/src/embed/conversation.js.map +1 -1
  35. package/cjs/src/embed/conversation.spec.js +26 -0
  36. package/cjs/src/embed/conversation.spec.js.map +1 -1
  37. package/cjs/src/embed/liveboard.d.ts +48 -2
  38. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  39. package/cjs/src/embed/liveboard.js +48 -7
  40. package/cjs/src/embed/liveboard.js.map +1 -1
  41. package/cjs/src/embed/liveboard.spec.js +139 -1
  42. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  43. package/cjs/src/embed/spotter-viz-utils.d.ts +85 -0
  44. package/cjs/src/embed/spotter-viz-utils.d.ts.map +1 -0
  45. package/cjs/src/embed/spotter-viz-utils.js +17 -0
  46. package/cjs/src/embed/spotter-viz-utils.js.map +1 -0
  47. package/cjs/src/embed/spotter-viz-utils.spec.d.ts +2 -0
  48. package/cjs/src/embed/spotter-viz-utils.spec.d.ts.map +1 -0
  49. package/cjs/src/embed/spotter-viz-utils.spec.js +31 -0
  50. package/cjs/src/embed/spotter-viz-utils.spec.js.map +1 -0
  51. package/cjs/src/embed/ts-embed.d.ts +58 -38
  52. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  53. package/cjs/src/embed/ts-embed.js +247 -151
  54. package/cjs/src/embed/ts-embed.js.map +1 -1
  55. package/cjs/src/embed/ts-embed.spec.js +397 -122
  56. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  57. package/cjs/src/index.d.ts +2 -1
  58. package/cjs/src/index.d.ts.map +1 -1
  59. package/cjs/src/index.js.map +1 -1
  60. package/cjs/src/react/index.d.ts.map +1 -1
  61. package/cjs/src/react/index.js +3 -0
  62. package/cjs/src/react/index.js.map +1 -1
  63. package/cjs/src/tokenizedFetch.d.ts.map +1 -1
  64. package/cjs/src/tokenizedFetch.js +12 -9
  65. package/cjs/src/tokenizedFetch.js.map +1 -1
  66. package/cjs/src/tokenizedFetch.spec.d.ts +2 -0
  67. package/cjs/src/tokenizedFetch.spec.d.ts.map +1 -0
  68. package/cjs/src/tokenizedFetch.spec.js +68 -0
  69. package/cjs/src/tokenizedFetch.spec.js.map +1 -0
  70. package/cjs/src/types.d.ts +309 -40
  71. package/cjs/src/types.d.ts.map +1 -1
  72. package/cjs/src/types.js +251 -23
  73. package/cjs/src/types.js.map +1 -1
  74. package/cjs/src/utils/authService/tokenizedAuthService.spec.js +6 -7
  75. package/cjs/src/utils/authService/tokenizedAuthService.spec.js.map +1 -1
  76. package/cjs/src/utils/logger.js +2 -1
  77. package/cjs/src/utils/logger.js.map +1 -1
  78. package/cjs/src/utils/logger.spec.d.ts +1 -0
  79. package/cjs/src/utils/logger.spec.d.ts.map +1 -1
  80. package/cjs/src/utils/logger.spec.js +10 -9
  81. package/cjs/src/utils/logger.spec.js.map +1 -1
  82. package/cjs/src/utils.d.ts +4 -1
  83. package/cjs/src/utils.d.ts.map +1 -1
  84. package/cjs/src/utils.js +107 -10
  85. package/cjs/src/utils.js.map +1 -1
  86. package/cjs/src/utils.spec.js +163 -4
  87. package/cjs/src/utils.spec.js.map +1 -1
  88. package/dist/{index-DZq20cR6.js → index-_UGCSSDR.js} +1 -1
  89. package/dist/src/auth.d.ts.map +1 -1
  90. package/dist/src/authToken.d.ts +2 -0
  91. package/dist/src/authToken.d.ts.map +1 -1
  92. package/dist/src/css-variables.d.ts +140 -0
  93. package/dist/src/css-variables.d.ts.map +1 -1
  94. package/dist/src/embed/app.d.ts +63 -2
  95. package/dist/src/embed/app.d.ts.map +1 -1
  96. package/dist/src/embed/base.d.ts +1 -0
  97. package/dist/src/embed/base.d.ts.map +1 -1
  98. package/dist/src/embed/conversation.d.ts +16 -1
  99. package/dist/src/embed/conversation.d.ts.map +1 -1
  100. package/dist/src/embed/liveboard.d.ts +48 -2
  101. package/dist/src/embed/liveboard.d.ts.map +1 -1
  102. package/dist/src/embed/spotter-viz-utils.d.ts +85 -0
  103. package/dist/src/embed/spotter-viz-utils.d.ts.map +1 -0
  104. package/dist/src/embed/spotter-viz-utils.spec.d.ts +2 -0
  105. package/dist/src/embed/spotter-viz-utils.spec.d.ts.map +1 -0
  106. package/dist/src/embed/ts-embed.d.ts +58 -38
  107. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  108. package/dist/src/index.d.ts +2 -1
  109. package/dist/src/index.d.ts.map +1 -1
  110. package/dist/src/react/index.d.ts.map +1 -1
  111. package/dist/src/tokenizedFetch.d.ts.map +1 -1
  112. package/dist/src/tokenizedFetch.spec.d.ts +2 -0
  113. package/dist/src/tokenizedFetch.spec.d.ts.map +1 -0
  114. package/dist/src/types.d.ts +309 -40
  115. package/dist/src/types.d.ts.map +1 -1
  116. package/dist/src/utils/logger.spec.d.ts +1 -0
  117. package/dist/src/utils/logger.spec.d.ts.map +1 -1
  118. package/dist/src/utils.d.ts +4 -1
  119. package/dist/src/utils.d.ts.map +1 -1
  120. package/dist/tsembed-react.es.js +3418 -2899
  121. package/dist/tsembed-react.js +3420 -2901
  122. package/dist/tsembed.es.js +3426 -2905
  123. package/dist/tsembed.js +3419 -2898
  124. package/dist/visual-embed-sdk-react-full.d.ts +687 -78
  125. package/dist/visual-embed-sdk-react.d.ts +687 -78
  126. package/dist/visual-embed-sdk.d.ts +702 -80
  127. package/lib/package.json +1 -1
  128. package/lib/src/auth.d.ts.map +1 -1
  129. package/lib/src/auth.js +12 -2
  130. package/lib/src/auth.js.map +1 -1
  131. package/lib/src/auth.spec.js +38 -0
  132. package/lib/src/auth.spec.js.map +1 -1
  133. package/lib/src/authToken.d.ts +2 -0
  134. package/lib/src/authToken.d.ts.map +1 -1
  135. package/lib/src/authToken.js +2 -2
  136. package/lib/src/authToken.js.map +1 -1
  137. package/lib/src/css-variables.d.ts +140 -0
  138. package/lib/src/css-variables.d.ts.map +1 -1
  139. package/lib/src/embed/app.d.ts +63 -2
  140. package/lib/src/embed/app.d.ts.map +1 -1
  141. package/lib/src/embed/app.js +58 -7
  142. package/lib/src/embed/app.js.map +1 -1
  143. package/lib/src/embed/app.spec.js +201 -2
  144. package/lib/src/embed/app.spec.js.map +1 -1
  145. package/lib/src/embed/auto-frame-renderer.js +7 -2
  146. package/lib/src/embed/auto-frame-renderer.js.map +1 -1
  147. package/lib/src/embed/auto-frame-renderer.spec.js +387 -8
  148. package/lib/src/embed/auto-frame-renderer.spec.js.map +1 -1
  149. package/lib/src/embed/base.d.ts +1 -0
  150. package/lib/src/embed/base.d.ts.map +1 -1
  151. package/lib/src/embed/base.js +11 -0
  152. package/lib/src/embed/base.js.map +1 -1
  153. package/lib/src/embed/base.spec.js +22 -1
  154. package/lib/src/embed/base.spec.js.map +1 -1
  155. package/lib/src/embed/bodyless-conversation.spec.js +86 -0
  156. package/lib/src/embed/bodyless-conversation.spec.js.map +1 -1
  157. package/lib/src/embed/conversation.d.ts +16 -1
  158. package/lib/src/embed/conversation.d.ts.map +1 -1
  159. package/lib/src/embed/conversation.js +5 -1
  160. package/lib/src/embed/conversation.js.map +1 -1
  161. package/lib/src/embed/conversation.spec.js +27 -1
  162. package/lib/src/embed/conversation.spec.js.map +1 -1
  163. package/lib/src/embed/liveboard.d.ts +48 -2
  164. package/lib/src/embed/liveboard.d.ts.map +1 -1
  165. package/lib/src/embed/liveboard.js +49 -8
  166. package/lib/src/embed/liveboard.js.map +1 -1
  167. package/lib/src/embed/liveboard.spec.js +139 -1
  168. package/lib/src/embed/liveboard.spec.js.map +1 -1
  169. package/lib/src/embed/spotter-viz-utils.d.ts +85 -0
  170. package/lib/src/embed/spotter-viz-utils.d.ts.map +1 -0
  171. package/lib/src/embed/spotter-viz-utils.js +13 -0
  172. package/lib/src/embed/spotter-viz-utils.js.map +1 -0
  173. package/lib/src/embed/spotter-viz-utils.spec.d.ts +2 -0
  174. package/lib/src/embed/spotter-viz-utils.spec.d.ts.map +1 -0
  175. package/lib/src/embed/spotter-viz-utils.spec.js +29 -0
  176. package/lib/src/embed/spotter-viz-utils.spec.js.map +1 -0
  177. package/lib/src/embed/ts-embed.d.ts +58 -38
  178. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  179. package/lib/src/embed/ts-embed.js +250 -154
  180. package/lib/src/embed/ts-embed.js.map +1 -1
  181. package/lib/src/embed/ts-embed.spec.js +397 -122
  182. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  183. package/lib/src/index.d.ts +2 -1
  184. package/lib/src/index.d.ts.map +1 -1
  185. package/lib/src/index.js.map +1 -1
  186. package/lib/src/react/index.d.ts.map +1 -1
  187. package/lib/src/react/index.js +3 -0
  188. package/lib/src/react/index.js.map +1 -1
  189. package/lib/src/tokenizedFetch.d.ts.map +1 -1
  190. package/lib/src/tokenizedFetch.js +13 -10
  191. package/lib/src/tokenizedFetch.js.map +1 -1
  192. package/lib/src/tokenizedFetch.spec.d.ts +2 -0
  193. package/lib/src/tokenizedFetch.spec.d.ts.map +1 -0
  194. package/lib/src/tokenizedFetch.spec.js +65 -0
  195. package/lib/src/tokenizedFetch.spec.js.map +1 -0
  196. package/lib/src/types.d.ts +309 -40
  197. package/lib/src/types.d.ts.map +1 -1
  198. package/lib/src/types.js +251 -23
  199. package/lib/src/types.js.map +1 -1
  200. package/lib/src/utils/authService/tokenizedAuthService.spec.js +6 -7
  201. package/lib/src/utils/authService/tokenizedAuthService.spec.js.map +1 -1
  202. package/lib/src/utils/logger.js +2 -1
  203. package/lib/src/utils/logger.js.map +1 -1
  204. package/lib/src/utils/logger.spec.d.ts +1 -0
  205. package/lib/src/utils/logger.spec.d.ts.map +1 -1
  206. package/lib/src/utils/logger.spec.js +10 -9
  207. package/lib/src/utils/logger.spec.js.map +1 -1
  208. package/lib/src/utils.d.ts +4 -1
  209. package/lib/src/utils.d.ts.map +1 -1
  210. package/lib/src/utils.js +103 -9
  211. package/lib/src/utils.js.map +1 -1
  212. package/lib/src/utils.spec.js +164 -5
  213. package/lib/src/utils.spec.js.map +1 -1
  214. package/lib/src/visual-embed-sdk.d.ts +702 -80
  215. package/package.json +1 -1
  216. package/src/auth.spec.ts +55 -1
  217. package/src/auth.ts +11 -2
  218. package/src/authToken.ts +2 -2
  219. package/src/css-variables.ts +175 -1
  220. package/src/embed/app.spec.ts +260 -3
  221. package/src/embed/app.ts +127 -7
  222. package/src/embed/auto-frame-renderer.spec.ts +457 -58
  223. package/src/embed/auto-frame-renderer.ts +7 -2
  224. package/src/embed/base.spec.ts +25 -1
  225. package/src/embed/base.ts +19 -5
  226. package/src/embed/bodyless-conversation.spec.ts +93 -0
  227. package/src/embed/conversation.spec.ts +34 -0
  228. package/src/embed/conversation.ts +22 -1
  229. package/src/embed/liveboard.spec.ts +163 -1
  230. package/src/embed/liveboard.ts +106 -10
  231. package/src/embed/spotter-viz-utils.spec.ts +30 -0
  232. package/src/embed/spotter-viz-utils.ts +94 -0
  233. package/src/embed/ts-embed.spec.ts +564 -231
  234. package/src/embed/ts-embed.ts +384 -258
  235. package/src/index.ts +3 -0
  236. package/src/react/index.tsx +3 -0
  237. package/src/tokenizedFetch.spec.ts +81 -0
  238. package/src/tokenizedFetch.ts +14 -11
  239. package/src/types.ts +326 -36
  240. package/src/utils/authService/tokenizedAuthService.spec.ts +6 -6
  241. package/src/utils/logger.spec.ts +11 -9
  242. package/src/utils/logger.ts +2 -2
  243. package/src/utils.spec.ts +200 -4
  244. package/src/utils.ts +128 -9
@@ -35,6 +35,7 @@ import {
35
35
  removeStyleProperties,
36
36
  isUndefined,
37
37
  getHostEventsConfig,
38
+ getValueFromWindow,
38
39
  } from '../utils';
39
40
  import { getCustomActions } from '../utils/custom-actions';
40
41
  import {
@@ -69,16 +70,25 @@ import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service';
69
70
  import { processEventData, processAuthFailure } from '../utils/processData';
70
71
  import { version } from '../../package.json';
71
72
  import {
72
- getAuthPromise, renderInQueue, handleAuth, notifyAuthFailure,
73
+ getAuthPromise,
74
+ renderInQueue,
75
+ handleAuth,
76
+ notifyAuthFailure,
73
77
  getInitPromise,
74
78
  getIsInitCalled,
79
+ getIsInitCompleted,
75
80
  } from './base';
76
81
  import { AuthFailureType } from '../auth';
77
82
  import { getEmbedConfig } from './embedConfig';
78
83
  import { ERROR_MESSAGE } from '../errors';
79
84
  import { getPreauthInfo } from '../utils/sessionInfoService';
80
85
  import { HostEventClient } from './hostEventClient/host-event-client';
81
- import { getInterceptInitData, handleInterceptEvent, processApiInterceptResponse, processLegacyInterceptResponse } from '../api-intercept';
86
+ import {
87
+ getInterceptInitData,
88
+ handleInterceptEvent,
89
+ processApiInterceptResponse,
90
+ processLegacyInterceptResponse,
91
+ } from '../api-intercept';
82
92
 
83
93
  /**
84
94
  * Global prefix for all ThoughtSpot postHash Params.
@@ -111,7 +121,7 @@ export class TsEmbed {
111
121
  /**
112
122
  * The DOM node where the ThoughtSpot app is to be embedded.
113
123
  */
114
- protected el: HTMLElement;
124
+ protected hostElement: HTMLElement;
115
125
 
116
126
  /**
117
127
  * The key to store the embed instance in the DOM node
@@ -135,7 +145,11 @@ export class TsEmbed {
135
145
  this.hostEventClient.setIframeElement(iFrame);
136
146
  }
137
147
 
138
- protected viewConfig: ViewConfig & { visibleTabs?: string[], hiddenTabs?: string[], showAlerts?: boolean };
148
+ protected viewConfig: ViewConfig & {
149
+ visibleTabs?: string[];
150
+ hiddenTabs?: string[];
151
+ showAlerts?: boolean;
152
+ };
139
153
 
140
154
  protected embedConfig: EmbedConfig;
141
155
 
@@ -186,6 +200,7 @@ export class TsEmbed {
186
200
  protected hostEventClient: HostEventClient;
187
201
 
188
202
  protected isReadyForRenderPromise;
203
+ protected shouldWaitForRenderPromise: boolean;
189
204
 
190
205
  /**
191
206
  * Handler for fullscreen change events
@@ -193,7 +208,7 @@ export class TsEmbed {
193
208
  private fullscreenChangeHandler: (() => void) | null = null;
194
209
 
195
210
  constructor(domSelector: DOMSelector, viewConfig?: ViewConfig) {
196
- this.el = getDOMNode(domSelector);
211
+ this.hostElement = getDOMNode(domSelector);
197
212
  this.eventHandlerMap = new Map();
198
213
  this.isError = false;
199
214
  this.viewConfig = {
@@ -206,17 +221,42 @@ export class TsEmbed {
206
221
  ...viewConfig,
207
222
  });
208
223
  const embedConfig = getEmbedConfig();
209
- this.embedConfig = embedConfig;
210
-
224
+ if (embedConfig) {
225
+ this.embedConfig = embedConfig;
226
+ this.thoughtSpotHost = getThoughtSpotHost(embedConfig);
227
+ this.thoughtSpotV2Base = getV2BasePath(embedConfig);
228
+ }
211
229
  this.hostEventClient = new HostEventClient(this.iFrame);
212
- this.isReadyForRenderPromise = getInitPromise().then(async () => {
213
- if (!embedConfig.authTriggerContainer && !embedConfig.useEventForSAMLPopup) {
230
+ this.shouldWaitForRenderPromise = !getIsInitCompleted();
231
+ const afterInit = () => {
232
+ // Prefer the config captured at construction time; fall back to
233
+ // getEmbedConfig() for the case where init()
234
+ // hadn't been called yet.
235
+ this.embedConfig = embedConfig ?? getEmbedConfig();
236
+ if (!this.embedConfig) {
237
+ logger.error('embedConfig unavailable in afterInit; init() may not have completed');
238
+ return;
239
+ }
240
+ if (!this.embedConfig.authTriggerContainer && !this.embedConfig.useEventForSAMLPopup) {
214
241
  this.embedConfig.authTriggerContainer = domSelector;
215
242
  }
216
- this.thoughtSpotHost = getThoughtSpotHost(embedConfig);
217
- this.thoughtSpotV2Base = getV2BasePath(embedConfig);
218
- this.shouldEncodeUrlQueryParams = embedConfig.shouldEncodeUrlQueryParams;
219
- });
243
+ this.thoughtSpotHost = getThoughtSpotHost(this.embedConfig);
244
+ this.thoughtSpotV2Base = getV2BasePath(this.embedConfig);
245
+ this.shouldEncodeUrlQueryParams = this.embedConfig.shouldEncodeUrlQueryParams;
246
+ };
247
+ if (!this.shouldWaitForRenderPromise) {
248
+ afterInit();
249
+ } else {
250
+ this.isReadyForRenderPromise = getInitPromise()
251
+ .then(afterInit)
252
+ .catch((err) => {
253
+ logger.error('SDK init failed before embed could be configured', err);
254
+ this.throwInitError();
255
+ })
256
+ .finally(() => {
257
+ this.shouldWaitForRenderPromise = false;
258
+ });
259
+ }
220
260
  }
221
261
 
222
262
  /**
@@ -227,7 +267,7 @@ export class TsEmbed {
227
267
  errorType: ErrorDetailsTypes.VALIDATION_ERROR,
228
268
  message: ERROR_MESSAGE.INIT_SDK_REQUIRED,
229
269
  code: EmbedErrorCodes.INIT_ERROR,
230
- error : ERROR_MESSAGE.INIT_SDK_REQUIRED,
270
+ error: ERROR_MESSAGE.INIT_SDK_REQUIRED,
231
271
  });
232
272
  }
233
273
 
@@ -248,7 +288,6 @@ export class TsEmbed {
248
288
  * @param event The window message event
249
289
  */
250
290
  private getEventType(event: MessageEvent) {
251
-
252
291
  return event.data?.type || event.data?.__type;
253
292
  }
254
293
 
@@ -278,11 +317,10 @@ export class TsEmbed {
278
317
  // 3. FullAppEmbed has primary navbar visible since:
279
318
  // - primary navbar requires fresh auth state for navigation
280
319
  // - cached auth may not reflect current user permissions
281
- const isDisabled = (
282
- this.viewConfig.overrideOrgId !== undefined
283
- || this.embedConfig.disablePreauthCache === true
284
- || this.isFullAppEmbedWithVisiblePrimaryNavbar()
285
- );
320
+ const isDisabled =
321
+ this.viewConfig.overrideOrgId !== undefined ||
322
+ this.embedConfig.disablePreauthCache === true ||
323
+ this.isFullAppEmbedWithVisiblePrimaryNavbar();
286
324
  return !isDisabled;
287
325
  }
288
326
 
@@ -296,8 +334,8 @@ export class TsEmbed {
296
334
  // Check if this is a FullAppEmbed (AppEmbed)
297
335
  // showPrimaryNavbar defaults to true if not explicitly set to false
298
336
  return (
299
- appViewConfig.embedComponentType === 'AppEmbed'
300
- && appViewConfig.showPrimaryNavbar === true
337
+ appViewConfig.embedComponentType === 'AppEmbed' &&
338
+ appViewConfig.showPrimaryNavbar === true
301
339
  );
302
340
  }
303
341
 
@@ -338,7 +376,7 @@ export class TsEmbed {
338
376
  errorType: ErrorDetailsTypes.NETWORK,
339
377
  message: ERROR_MESSAGE.OFFLINE_WARNING,
340
378
  code: EmbedErrorCodes.NETWORK_ERROR,
341
- offlineWarning : ERROR_MESSAGE.OFFLINE_WARNING,
379
+ offlineWarning: ERROR_MESSAGE.OFFLINE_WARNING,
342
380
  };
343
381
  this.executeCallbacks(EmbedEvent.Error, errorDetails);
344
382
  logger.warn(errorDetails);
@@ -349,15 +387,29 @@ export class TsEmbed {
349
387
  this.subscribedListeners.offline = offlineEventListener;
350
388
  }
351
389
 
352
- private handleApiInterceptEvent({ eventData, eventPort }: { eventData: any, eventPort: MessagePort | void }) {
390
+ private handleApiInterceptEvent({
391
+ eventData,
392
+ eventPort,
393
+ }: {
394
+ eventData: any;
395
+ eventPort: MessagePort | void;
396
+ }) {
353
397
  const executeEvent = (_eventType: EmbedEvent, data: any) => {
354
398
  this.executeCallbacks(_eventType, data, eventPort);
355
- }
356
- const getUnsavedAnswerTml = async (props: { sessionId?: string, vizId?: string }) => {
357
- const response = await this.triggerUIPassThrough(UIPassthroughEvent.GetUnsavedAnswerTML, props);
399
+ };
400
+ const getUnsavedAnswerTml = async (props: { sessionId?: string; vizId?: string }) => {
401
+ const response = await this.triggerUIPassThrough(
402
+ UIPassthroughEvent.GetUnsavedAnswerTML,
403
+ props,
404
+ );
358
405
  return response.filter((item) => item.value)?.[0]?.value;
359
- }
360
- handleInterceptEvent({ eventData, executeEvent, viewConfig: this.viewConfig, getUnsavedAnswerTml });
406
+ };
407
+ handleInterceptEvent({
408
+ eventData,
409
+ executeEvent,
410
+ viewConfig: this.viewConfig,
411
+ getUnsavedAnswerTml,
412
+ });
361
413
  }
362
414
 
363
415
  private messageEventListener = (event: MessageEvent<any>) => {
@@ -369,7 +421,7 @@ export class TsEmbed {
369
421
  eventType,
370
422
  eventData,
371
423
  this.thoughtSpotHost,
372
- this.isPreRendered ? this.preRenderWrapper : this.el,
424
+ this.isPreRendered ? this.preRenderWrapper : this.hostElement,
373
425
  );
374
426
 
375
427
  if (eventType === EmbedEvent.ApiIntercept) {
@@ -377,11 +429,7 @@ export class TsEmbed {
377
429
  return;
378
430
  }
379
431
 
380
- this.executeCallbacks(
381
- eventType,
382
- processedEventData,
383
- eventPort,
384
- );
432
+ this.executeCallbacks(eventType, processedEventData, eventPort);
385
433
  }
386
434
  };
387
435
  /**
@@ -394,7 +442,6 @@ export class TsEmbed {
394
442
 
395
443
  this.subscribedListeners.message = this.messageEventListener;
396
444
  }
397
-
398
445
 
399
446
  /**
400
447
  * Adds event listeners for both network and message events.
@@ -409,7 +456,6 @@ export class TsEmbed {
409
456
  this.subscribeToMessageEvents();
410
457
  }
411
458
 
412
-
413
459
  private unsubscribeToNetworkEvents() {
414
460
  if (this.subscribedListeners.online) {
415
461
  window.removeEventListener('online', this.subscribedListeners.online);
@@ -441,7 +487,7 @@ export class TsEmbed {
441
487
  try {
442
488
  authToken = await getAuthenticationToken(this.embedConfig);
443
489
  } catch (e) {
444
- processAuthFailure(e, this.isPreRendered ? this.preRenderWrapper : this.el);
490
+ processAuthFailure(e, this.isPreRendered ? this.preRenderWrapper : this.hostElement);
445
491
  throw e;
446
492
  }
447
493
 
@@ -452,15 +498,18 @@ export class TsEmbed {
452
498
  const authToken = await this.getAuthTokenForCookielessInit();
453
499
  const customActionsResult = getCustomActions([
454
500
  ...(this.viewConfig.customActions || []),
455
- ...(this.embedConfig.customActions || [])
501
+ ...(this.embedConfig.customActions || []),
456
502
  ]);
457
503
  if (customActionsResult.errors.length > 0) {
458
504
  this.handleError({
459
- errorType: ErrorDetailsTypes.VALIDATION_ERROR,
505
+ errorType: ErrorDetailsTypes.VALIDATION_ERROR,
506
+ message: customActionsResult.errors,
507
+ code: EmbedErrorCodes.CUSTOM_ACTION_VALIDATION,
508
+ error: {
509
+ type: EmbedErrorCodes.CUSTOM_ACTION_VALIDATION,
460
510
  message: customActionsResult.errors,
461
- code: EmbedErrorCodes.CUSTOM_ACTION_VALIDATION,
462
- error : { type: EmbedErrorCodes.CUSTOM_ACTION_VALIDATION, message: customActionsResult.errors }
463
- });
511
+ },
512
+ });
464
513
  }
465
514
  const baseInitData = {
466
515
  customisations: getCustomisations(this.embedConfig, this.viewConfig),
@@ -481,7 +530,7 @@ export class TsEmbed {
481
530
  this.embedConfig.customVariablesForThirdPartyTools || {},
482
531
  hiddenListColumns: this.viewConfig.hiddenListColumns || [],
483
532
  customActions: customActionsResult.actions,
484
- embedExpiryInAuthToken: this.viewConfig.refreshAuthTokenOnNearExpiry,
533
+ embedExpiryInAuthToken: this.viewConfig.refreshAuthTokenOnNearExpiry ?? true,
485
534
  ...getInterceptInitData(this.viewConfig),
486
535
  ...getHostEventsConfig(this.viewConfig),
487
536
  };
@@ -521,11 +570,11 @@ export class TsEmbed {
521
570
  private async refreshAuthTokenForCookieless(
522
571
  responder: (data: any) => void,
523
572
  eventType: EmbedEvent,
524
- forceRefresh: boolean = false
573
+ forceRefresh: boolean = false,
525
574
  ): Promise<void> {
526
575
  const { authType, autoLogin } = this.embedConfig;
527
- const isAutoLoginTrue = autoLogin ?? (authType === AuthType.TrustedAuthTokenCookieless);
528
-
576
+ const isAutoLoginTrue = autoLogin ?? authType === AuthType.TrustedAuthTokenCookieless;
577
+
529
578
  if (isAutoLoginTrue && authType === AuthType.TrustedAuthTokenCookieless) {
530
579
  const authToken = await getAuthenticationToken(this.embedConfig, forceRefresh);
531
580
  responder({
@@ -537,21 +586,24 @@ export class TsEmbed {
537
586
 
538
587
  private handleAuthFailure = (error: Error) => {
539
588
  logger.error(`${ERROR_MESSAGE.INVALID_TOKEN_ERROR} Error : ${error?.message}`);
540
- processAuthFailure(error, this.isPreRendered ? this.preRenderWrapper : this.el);
541
- }
589
+ processAuthFailure(error, this.isPreRendered ? this.preRenderWrapper : this.hostElement);
590
+ };
542
591
 
543
592
  /**
544
593
  * Refresh the auth token if the autoLogin is true and the authType is TrustedAuthTokenCookieless
545
594
  * @param _
546
595
  * @param responder
547
596
  */
548
- private tokenRefresh = async (_: MessagePayload, responder: (data: {type: EmbedEvent, data: {authToken: string}}) => void) => {
597
+ private tokenRefresh = async (
598
+ _: MessagePayload,
599
+ responder: (data: { type: EmbedEvent; data: { authToken: string } }) => void,
600
+ ) => {
549
601
  try {
550
602
  await this.refreshAuthTokenForCookieless(responder, EmbedEvent.RefreshAuthToken, true);
551
603
  } catch (e) {
552
604
  this.handleAuthFailure(e);
553
605
  }
554
- }
606
+ };
555
607
 
556
608
  /**
557
609
  * Sends updated auth token to the iFrame to avoid user logout
@@ -562,14 +614,14 @@ export class TsEmbed {
562
614
  const { authType, autoLogin: autoLoginConfig } = this.embedConfig;
563
615
  // Default autoLogin: true for cookieless if undefined/null, otherwise
564
616
  // false
565
- const autoLogin = autoLoginConfig ?? (authType === AuthType.TrustedAuthTokenCookieless);
566
-
617
+ const autoLogin = autoLoginConfig ?? authType === AuthType.TrustedAuthTokenCookieless;
618
+
567
619
  try {
568
620
  await this.refreshAuthTokenForCookieless(responder, EmbedEvent.AuthExpire, false);
569
621
  } catch (e) {
570
622
  this.handleAuthFailure(e);
571
623
  }
572
-
624
+
573
625
  if (autoLogin && authType !== AuthType.TrustedAuthTokenCookieless) {
574
626
  handleAuth();
575
627
  }
@@ -582,20 +634,22 @@ export class TsEmbed {
582
634
  * @param responder
583
635
  */
584
636
  private idleSessionTimeout = (_: any, responder: any) => {
585
- handleAuth().then(async () => {
586
- let authToken = '';
587
- try {
588
- authToken = await getAuthenticationToken(this.embedConfig);
589
- responder({
590
- type: EmbedEvent.IdleSessionTimeout,
591
- data: { authToken },
592
- });
593
- } catch (e) {
594
- this.handleAuthFailure(e);
595
- }
596
- }).catch((e) => {
597
- logger.error(`Auto Login failed, Error : ${e?.message}`);
598
- });
637
+ handleAuth()
638
+ .then(async () => {
639
+ let authToken = '';
640
+ try {
641
+ authToken = await getAuthenticationToken(this.embedConfig);
642
+ responder({
643
+ type: EmbedEvent.IdleSessionTimeout,
644
+ data: { authToken },
645
+ });
646
+ } catch (e) {
647
+ this.handleAuthFailure(e);
648
+ }
649
+ })
650
+ .catch((e) => {
651
+ logger.error(`Auto Login failed, Error : ${e?.message}`);
652
+ });
599
653
  notifyAuthFailure(AuthFailureType.IDLE_SESSION_TIMEOUT);
600
654
  };
601
655
 
@@ -607,9 +661,11 @@ export class TsEmbed {
607
661
  this.on(EmbedEvent.AuthExpire, this.updateAuthToken, { start: false }, true);
608
662
  this.on(EmbedEvent.IdleSessionTimeout, this.idleSessionTimeout, { start: false }, true);
609
663
 
610
- const embedListenerReadyHandler = this.createEmbedContainerHandler(EmbedEvent.EmbedListenerReady);
664
+ const embedListenerReadyHandler = this.createEmbedContainerHandler(
665
+ EmbedEvent.EmbedListenerReady,
666
+ );
611
667
  this.on(EmbedEvent.EmbedListenerReady, embedListenerReadyHandler, { start: false }, true);
612
-
668
+
613
669
  const authInitHandler = this.createEmbedContainerHandler(EmbedEvent.AuthInit);
614
670
  this.on(EmbedEvent.AuthInit, authInitHandler, { start: false }, true);
615
671
  this.on(EmbedEvent.RefreshAuthToken, this.tokenRefresh, { start: false }, true);
@@ -637,7 +693,7 @@ export class TsEmbed {
637
693
  let queryParams = this.getEmbedParamsObject();
638
694
  const appInitData = await this.getAppInitData();
639
695
  queryParams = { ...this.viewConfig, ...queryParams, ...appInitData };
640
-
696
+
641
697
  return queryParams;
642
698
  }
643
699
 
@@ -718,7 +774,7 @@ export class TsEmbed {
718
774
  errorType: ErrorDetailsTypes.VALIDATION_ERROR,
719
775
  message: ERROR_MESSAGE.CONFLICTING_ACTIONS_CONFIG,
720
776
  code: EmbedErrorCodes.CONFLICTING_ACTIONS_CONFIG,
721
- error : ERROR_MESSAGE.CONFLICTING_ACTIONS_CONFIG,
777
+ error: ERROR_MESSAGE.CONFLICTING_ACTIONS_CONFIG,
722
778
  });
723
779
  return queryParams;
724
780
  }
@@ -728,7 +784,7 @@ export class TsEmbed {
728
784
  errorType: ErrorDetailsTypes.VALIDATION_ERROR,
729
785
  message: ERROR_MESSAGE.CONFLICTING_TABS_CONFIG,
730
786
  code: EmbedErrorCodes.CONFLICTING_TABS_CONFIG,
731
- error : ERROR_MESSAGE.CONFLICTING_TABS_CONFIG,
787
+ error: ERROR_MESSAGE.CONFLICTING_TABS_CONFIG,
732
788
  });
733
789
  return queryParams;
734
790
  }
@@ -774,8 +830,8 @@ export class TsEmbed {
774
830
  queryParams[Param.IconSpriteUrl] = spriteUrl.replace('https://', '');
775
831
  }
776
832
 
777
- const stringIDsUrl = customizations?.content?.stringIDsUrl
778
- || embedCustomizations?.content?.stringIDsUrl;
833
+ const stringIDsUrl =
834
+ customizations?.content?.stringIDsUrl || embedCustomizations?.content?.stringIDsUrl;
779
835
  if (stringIDsUrl) {
780
836
  queryParams[Param.StringIDsUrl] = stringIDsUrl;
781
837
  }
@@ -786,13 +842,10 @@ export class TsEmbed {
786
842
  if (locale !== undefined) {
787
843
  queryParams[Param.Locale] = locale;
788
844
  }
789
- // TODO: Once V2 is stable, send both flags when
790
- // linkOverride is true (remove the else-if).
791
- if (enableLinkOverridesV2) {
845
+
846
+ if (!disableRedirectionLinksInNewTab && (enableLinkOverridesV2 || linkOverride)) {
792
847
  queryParams[Param.EnableLinkOverridesV2] = true;
793
848
  queryParams[Param.LinkOverride] = true;
794
- } else if (linkOverride) {
795
- queryParams[Param.LinkOverride] = linkOverride;
796
849
  }
797
850
  if (insertInToSlide) {
798
851
  queryParams[Param.ShowInsertToSlide] = insertInToSlide;
@@ -889,8 +942,14 @@ export class TsEmbed {
889
942
  return iFrame;
890
943
  }
891
944
 
945
+ /**
946
+ * Returns true if this embed instance is configured for pre-rendering.
947
+ */
948
+ protected isPreRenderEmbed() {
949
+ return !!this.viewConfig.preRenderId;
950
+ }
892
951
  protected handleInsertionIntoDOM(child: string | Node): void {
893
- if (this.isPreRendered) {
952
+ if (this.isPreRenderEmbed()) {
894
953
  this.insertIntoDOMForPreRender(child);
895
954
  } else {
896
955
  this.insertIntoDOM(child);
@@ -898,6 +957,9 @@ export class TsEmbed {
898
957
  if (this.insertedDomEl instanceof Node) {
899
958
  (this.insertedDomEl as any)[this.embedNodeKey] = this;
900
959
  }
960
+ if (this.preRenderWrapper) {
961
+ (this.preRenderWrapper as any)[this.embedNodeKey] = this;
962
+ }
901
963
  }
902
964
 
903
965
  /**
@@ -990,7 +1052,7 @@ export class TsEmbed {
990
1052
  errorType: ErrorDetailsTypes.API,
991
1053
  message: error.message || ERROR_MESSAGE.LOGIN_FAILED,
992
1054
  code: EmbedErrorCodes.LOGIN_FAILED,
993
- error : error,
1055
+ error: error,
994
1056
  });
995
1057
  });
996
1058
  });
@@ -1019,6 +1081,14 @@ export class TsEmbed {
1019
1081
 
1020
1082
  protected preRenderChild: HTMLElement;
1021
1083
 
1084
+ /**
1085
+ * Checks for an existing pre-rendered component and connects to it.
1086
+ *
1087
+ * If a matching pre-rendered component is found in the DOM, this method
1088
+ * sets the internal properties of the embed object to reference it.
1089
+ *
1090
+ * @returns True if a connection was successfully established, false otherwise.
1091
+ */
1022
1092
  protected connectPreRendered(): boolean {
1023
1093
  const preRenderIds = this.getPreRenderIds();
1024
1094
  const preRenderWrapperElement = document.getElementById(preRenderIds.wrapper);
@@ -1031,19 +1101,14 @@ export class TsEmbed {
1031
1101
  if (this.preRenderChild instanceof HTMLIFrameElement) {
1032
1102
  this.setIframeElement(this.preRenderChild);
1033
1103
  }
1034
- this.insertedDomEl = this.preRenderWrapper;
1035
1104
  this.isRendered = true;
1036
1105
  }
1037
1106
 
1038
- return this.isPreRenderAvailable();
1107
+ return this.isPreRenderConnected();
1039
1108
  }
1040
1109
 
1041
- protected isPreRenderAvailable(): boolean {
1042
- return (
1043
- this.isRendered
1044
- && this.isPreRendered
1045
- && Boolean(this.preRenderWrapper && this.preRenderChild)
1046
- );
1110
+ protected isPreRenderConnected(): boolean {
1111
+ return Boolean(this.preRenderWrapper && this.preRenderChild);
1047
1112
  }
1048
1113
 
1049
1114
  protected createPreRenderChild(child: string | Node): HTMLElement {
@@ -1069,6 +1134,24 @@ export class TsEmbed {
1069
1134
  return divChildNode;
1070
1135
  }
1071
1136
 
1137
+ /**
1138
+ * Creates the in-flow placeholder div inserted into the host element when
1139
+ * showPreRender() is called. The wrapper observes this element to stay
1140
+ * aligned with the host layout.
1141
+ */
1142
+ private createPreRenderPlaceholder(): HTMLDivElement {
1143
+ const placeholder = document.createElement('div');
1144
+ const id = this.getPreRenderIds();
1145
+ const { width: frameWidth, height: frameHeight } = this.viewConfig.frameParams || {};
1146
+ const width = getCssDimension(frameWidth || DEFAULT_EMBED_WIDTH);
1147
+ const height = getCssDimension(frameHeight || DEFAULT_EMBED_HEIGHT);
1148
+ placeholder.style.width = width;
1149
+ placeholder.style.height = height;
1150
+ // we can improve this , lol
1151
+ placeholder.id = id.placeHolder;
1152
+ return placeholder;
1153
+ }
1154
+
1072
1155
  protected insertIntoDOMForPreRender(child: string | Node): void {
1073
1156
  const preRenderChild = this.createPreRenderChild(child);
1074
1157
  const preRenderWrapper = this.createPreRenderWrapper();
@@ -1080,7 +1163,11 @@ export class TsEmbed {
1080
1163
  if (preRenderChild instanceof HTMLIFrameElement) {
1081
1164
  this.setIframeElement(preRenderChild);
1082
1165
  }
1083
- this.insertedDomEl = preRenderWrapper;
1166
+
1167
+ if (this.iFrame) {
1168
+ this.iFrame.style.height = '100%';
1169
+ this.iFrame.style.width = '100%';
1170
+ }
1084
1171
 
1085
1172
  if (this.showPreRenderByDefault) {
1086
1173
  this.showPreRender();
@@ -1102,17 +1189,17 @@ export class TsEmbed {
1102
1189
 
1103
1190
  child = div;
1104
1191
  }
1105
- if (this.el.nextElementSibling?.id === TS_EMBED_ID) {
1106
- this.el.nextElementSibling.remove();
1192
+ if (this.hostElement.nextElementSibling?.id === TS_EMBED_ID) {
1193
+ this.hostElement.nextElementSibling.remove();
1107
1194
  }
1108
- this.el.parentElement.insertBefore(child, this.el.nextSibling);
1195
+ this.hostElement.parentElement.insertBefore(child, this.hostElement.nextSibling);
1109
1196
  this.insertedDomEl = child;
1110
1197
  } else if (typeof child === 'string') {
1111
- this.el.innerHTML = child;
1112
- this.insertedDomEl = this.el.children[0];
1198
+ this.hostElement.innerHTML = child;
1199
+ this.insertedDomEl = this.hostElement.children[0];
1113
1200
  } else {
1114
- this.el.innerHTML = '';
1115
- this.el.appendChild(child);
1201
+ this.hostElement.innerHTML = '';
1202
+ this.hostElement.appendChild(child);
1116
1203
  this.insertedDomEl = child;
1117
1204
  }
1118
1205
  }
@@ -1122,7 +1209,16 @@ export class TsEmbed {
1122
1209
  * @param height The height in pixels
1123
1210
  */
1124
1211
  protected setIFrameHeight(height: number | string): void {
1125
- this.iFrame.style.height = getCssDimension(height);
1212
+ if (this.isPreRendered) {
1213
+ if (this.insertedDomEl) {
1214
+ (this.insertedDomEl as HTMLElement).style.height = getCssDimension(height);
1215
+ } else if (this.preRenderWrapper) {
1216
+ this.preRenderWrapper.style.height = getCssDimension(height);
1217
+ }
1218
+ } else {
1219
+ // normal (non-preRender) mode: size the iframe directly
1220
+ this.iFrame.style.height = getCssDimension(height);
1221
+ }
1126
1222
  }
1127
1223
 
1128
1224
  /**
@@ -1130,10 +1226,12 @@ export class TsEmbed {
1130
1226
  * Embed event handler -> responder -> createEmbedEventResponder -> send response
1131
1227
  * @param eventPort The event port for a specific MessageChannel
1132
1228
  * @param eventType The event type
1133
- * @returns
1229
+ * @returns
1134
1230
  */
1135
- protected createEmbedEventResponder = (eventPort: MessagePort | void, eventType: EmbedEvent) => {
1136
-
1231
+ protected createEmbedEventResponder = (
1232
+ eventPort: MessagePort | void,
1233
+ eventType: EmbedEvent,
1234
+ ) => {
1137
1235
  const getPayloadToSend = (payload: any) => {
1138
1236
  if (eventType === EmbedEvent.OnBeforeGetVizDataIntercept) {
1139
1237
  return processLegacyInterceptResponse(payload);
@@ -1142,25 +1240,26 @@ export class TsEmbed {
1142
1240
  return processApiInterceptResponse(payload);
1143
1241
  }
1144
1242
  return payload;
1145
- }
1243
+ };
1146
1244
  return (payload: any) => {
1147
1245
  const payloadToSend = getPayloadToSend(payload);
1148
1246
  this.triggerEventOnPort(eventPort, payloadToSend);
1149
- }
1150
- }
1247
+ };
1248
+ };
1151
1249
 
1152
- private shouldSkipEvent(eventType: EmbedEvent, data: any): boolean {
1153
- const errorType = data?.errorType ?? data?.data?.code;
1154
- if (
1155
- eventType === EmbedEvent.Error
1156
- && errorType === EmbedErrorCodes.HOST_EVENT_VALIDATION
1157
- && (!getHostEventsConfig(this.viewConfig).useHostEventsV2 || getHostEventsConfig(this.viewConfig).shouldBypassPayloadValidation)
1250
+ private shouldSkipEvent(eventType: EmbedEvent, data: any): boolean {
1251
+ const errorType = data?.errorType ?? data?.data?.code;
1252
+ if (
1253
+ eventType === EmbedEvent.Error &&
1254
+ errorType === EmbedErrorCodes.HOST_EVENT_VALIDATION &&
1255
+ (!getHostEventsConfig(this.viewConfig).useHostEventsV2 ||
1256
+ getHostEventsConfig(this.viewConfig).shouldBypassPayloadValidation)
1158
1257
  ) {
1159
1258
  logger.warn(`Host Event Validation failed: ${data?.data?.message}`);
1160
- return true;
1161
- }
1259
+ return true;
1260
+ }
1162
1261
  return false;
1163
- }
1262
+ }
1164
1263
  /**
1165
1264
  * Executes all registered event handlers for a particular event type
1166
1265
  * @param eventType The event type
@@ -1181,10 +1280,10 @@ export class TsEmbed {
1181
1280
  if (
1182
1281
  // When start status is true it trigger only start releated
1183
1282
  // payload
1184
- (callbackObj.options.start && dataStatus === embedEventStatus.START)
1283
+ (callbackObj.options.start && dataStatus === embedEventStatus.START) ||
1185
1284
  // When start status is false it trigger only end releated
1186
1285
  // payload
1187
- || (!callbackObj.options.start && dataStatus === embedEventStatus.END)
1286
+ (!callbackObj.options.start && dataStatus === embedEventStatus.END)
1188
1287
  ) {
1189
1288
  const responder = this.createEmbedEventResponder(eventPort, eventType);
1190
1289
  callbackObj.callback(data, responder);
@@ -1330,22 +1429,22 @@ export class TsEmbed {
1330
1429
  }
1331
1430
 
1332
1431
  /**
1333
- * @hidden
1334
- * Internal state to track if the embed container is loaded.
1335
- * This is used to trigger events after the embed container is loaded.
1336
- */
1432
+ * @hidden
1433
+ * Internal state to track if the embed container is loaded.
1434
+ * This is used to trigger events after the embed container is loaded.
1435
+ */
1337
1436
  public isEmbedContainerLoaded = false;
1338
1437
 
1339
1438
  /**
1340
1439
  * @hidden
1341
- * Internal state to track the callbacks to be executed after the embed container
1440
+ * Internal state to track the callbacks to be executed after the embed container
1342
1441
  * is loaded.
1343
1442
  * This is used to trigger events after the embed container is loaded.
1344
1443
  */
1345
1444
  private embedContainerReadyCallbacks: Array<() => void> = [];
1346
1445
 
1347
1446
  protected getPreRenderObj<T extends TsEmbed>(): T {
1348
- const embedObj = (this.insertedDomEl as any)?.[this.embedNodeKey] as T;
1447
+ const embedObj = (this.preRenderWrapper as any)?.[this.embedNodeKey] as T;
1349
1448
  if (embedObj === (this as any)) {
1350
1449
  logger.info('embedObj is same as this');
1351
1450
  }
@@ -1383,22 +1482,23 @@ export class TsEmbed {
1383
1482
  }
1384
1483
  }
1385
1484
 
1386
- protected createEmbedContainerHandler = (source: EmbedEvent.AuthInit | EmbedEvent.EmbedListenerReady) => () => {
1387
- const processEmbedContainerReady = () => {
1388
- logger.debug('processEmbedContainerReady');
1389
- this.isEmbedContainerLoaded = true;
1390
- this.executeEmbedContainerReadyCallbacks();
1391
- }
1392
- if (source === EmbedEvent.AuthInit) {
1393
- const AUTH_INIT_FALLBACK_DELAY = 1000;
1394
- // Wait for 1 second to ensure the embed container is loaded
1395
- // This is a workaround to ensure the embed container is loaded
1396
- // this is needed until all clusters have EmbedListenerReady event
1397
- setTimeout(processEmbedContainerReady, AUTH_INIT_FALLBACK_DELAY);
1398
- } else if (source === EmbedEvent.EmbedListenerReady) {
1399
- processEmbedContainerReady();
1400
- }
1401
- }
1485
+ protected createEmbedContainerHandler =
1486
+ (source: EmbedEvent.AuthInit | EmbedEvent.EmbedListenerReady) => () => {
1487
+ const processEmbedContainerReady = () => {
1488
+ logger.debug('processEmbedContainerReady');
1489
+ this.isEmbedContainerLoaded = true;
1490
+ this.executeEmbedContainerReadyCallbacks();
1491
+ };
1492
+ if (source === EmbedEvent.AuthInit) {
1493
+ const AUTH_INIT_FALLBACK_DELAY = 1000;
1494
+ // Wait for 1 second to ensure the embed container is loaded
1495
+ // This is a workaround to ensure the embed container is loaded
1496
+ // this is needed until all clusters have EmbedListenerReady event
1497
+ setTimeout(processEmbedContainerReady, AUTH_INIT_FALLBACK_DELAY);
1498
+ } else if (source === EmbedEvent.EmbedListenerReady) {
1499
+ processEmbedContainerReady();
1500
+ }
1501
+ };
1402
1502
 
1403
1503
  /**
1404
1504
  * Triggers an event to the embedded app
@@ -1447,7 +1547,7 @@ export class TsEmbed {
1447
1547
  return null;
1448
1548
  }
1449
1549
 
1450
- // Check if iframe exists before triggering -
1550
+ // Check if iframe exists before triggering -
1451
1551
  // this prevents the error when auth fails
1452
1552
  if (!this.iFrame) {
1453
1553
  logger.debug(
@@ -1457,21 +1557,30 @@ export class TsEmbed {
1457
1557
  }
1458
1558
 
1459
1559
  // send an empty object, this is needed for liveboard default handlers
1460
- return this.hostEventClient.triggerHostEvent(messageType, data, context).catch((err: Error & {
1461
- isValidationError?: boolean;
1462
- embedErrorDetails?: { errorType: ErrorDetailsTypes; message: string; code: EmbedErrorCodes; error: string };
1463
- }): Promise<null> => {
1464
- if (err?.isValidationError) {
1465
- const errorDetails = err.embedErrorDetails ?? {
1466
- errorType: ErrorDetailsTypes.VALIDATION_ERROR,
1467
- message: err.message || ERROR_MESSAGE.UPDATEFILTERS_INVALID_PAYLOAD,
1468
- code: EmbedErrorCodes.UPDATEFILTERS_INVALID_PAYLOAD,
1469
- error: err.message,
1470
- };
1471
- this.handleError(errorDetails);
1472
- }
1473
- throw err;
1474
- });
1560
+ return this.hostEventClient.triggerHostEvent(messageType, data, context).catch(
1561
+ (
1562
+ err: Error & {
1563
+ isValidationError?: boolean;
1564
+ embedErrorDetails?: {
1565
+ errorType: ErrorDetailsTypes;
1566
+ message: string;
1567
+ code: EmbedErrorCodes;
1568
+ error: string;
1569
+ };
1570
+ },
1571
+ ): Promise<null> => {
1572
+ if (err?.isValidationError) {
1573
+ const errorDetails = err.embedErrorDetails ?? {
1574
+ errorType: ErrorDetailsTypes.VALIDATION_ERROR,
1575
+ message: err.message || ERROR_MESSAGE.UPDATEFILTERS_INVALID_PAYLOAD,
1576
+ code: EmbedErrorCodes.UPDATEFILTERS_INVALID_PAYLOAD,
1577
+ error: err.message,
1578
+ };
1579
+ this.handleError(errorDetails);
1580
+ }
1581
+ throw err;
1582
+ },
1583
+ );
1475
1584
  }
1476
1585
 
1477
1586
  /**
@@ -1499,7 +1608,7 @@ export class TsEmbed {
1499
1608
  if (!getIsInitCalled()) {
1500
1609
  logger.error(ERROR_MESSAGE.RENDER_CALLED_BEFORE_INIT);
1501
1610
  }
1502
- await this.isReadyForRenderPromise;
1611
+ if (this.shouldWaitForRenderPromise) await this.isReadyForRenderPromise;
1503
1612
  this.isRendered = true;
1504
1613
 
1505
1614
  return this;
@@ -1514,33 +1623,33 @@ export class TsEmbed {
1514
1623
  }
1515
1624
 
1516
1625
  /**
1517
- * Context object for the embedded component.
1518
- * @returns {ContextObject} The current context object containing the page type and object ids.
1519
- * @example
1520
- * ```js
1521
- * const context = await embed.getCurrentContext();
1522
- * console.log(context);
1523
- *
1524
- * // Example output
1525
- * {
1526
- * stack: [
1527
- * {
1528
- * name: 'Liveboard',
1529
- * type: ContextType.Liveboard,
1530
- * objectIds: {
1531
- * liveboardId: '123',
1532
- * },
1533
- * },
1534
- * ],
1535
- * currentContext: {
1536
- * name: 'Liveboard',
1537
- * type: ContextType.Liveboard,
1538
- * objectIds: {
1539
- * liveboardId: '123',
1540
- * },
1541
- * },
1542
- * }
1543
- * ```
1626
+ * Context object for the embedded component.
1627
+ * @returns {ContextObject} The current context object containing the page type and object ids.
1628
+ * @example
1629
+ * ```js
1630
+ * const context = await embed.getCurrentContext();
1631
+ * console.log(context);
1632
+ *
1633
+ * // Example output
1634
+ * {
1635
+ * stack: [
1636
+ * {
1637
+ * name: 'Liveboard',
1638
+ * type: ContextType.Liveboard,
1639
+ * objectIds: {
1640
+ * liveboardId: '123',
1641
+ * },
1642
+ * },
1643
+ * ],
1644
+ * currentContext: {
1645
+ * name: 'Liveboard',
1646
+ * type: ContextType.Liveboard,
1647
+ * objectIds: {
1648
+ * liveboardId: '123',
1649
+ * },
1650
+ * },
1651
+ * }
1652
+ * ```
1544
1653
  * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl
1545
1654
  */
1546
1655
  public async getCurrentContext(): Promise<ContextObject> {
@@ -1560,7 +1669,7 @@ export class TsEmbed {
1560
1669
  *
1561
1670
  * @param eventName - The host or action event to generate the subscribed event name for.
1562
1671
  * @returns The formatted event name (e.g., "Save Subscribed").
1563
- *
1672
+ *
1564
1673
  * @version SDK: 1.47.2 | ThoughtSpot: 26.3.0.cl
1565
1674
  */
1566
1675
  public subscribedEvent(eventName: HostEvent | Action): string {
@@ -1572,7 +1681,10 @@ export class TsEmbed {
1572
1681
  * @param showPreRenderByDefault - Show the preRender after render, hidden by default
1573
1682
  */
1574
1683
 
1575
- public async preRender(showPreRenderByDefault = false, replaceExistingPreRender = false): Promise<TsEmbed> {
1684
+ public async preRender(
1685
+ showPreRenderByDefault = false,
1686
+ replaceExistingPreRender = false,
1687
+ ): Promise<TsEmbed> {
1576
1688
  if (!this.viewConfig.preRenderId) {
1577
1689
  logger.error(ERROR_MESSAGE.PRERENDER_ID_MISSING);
1578
1690
  return this;
@@ -1582,6 +1694,9 @@ export class TsEmbed {
1582
1694
 
1583
1695
  const isAlreadyRendered = this.connectPreRendered();
1584
1696
  if (isAlreadyRendered && !replaceExistingPreRender) {
1697
+ if (this.showPreRenderByDefault) {
1698
+ this.showPreRender();
1699
+ }
1585
1700
  return this;
1586
1701
  }
1587
1702
 
@@ -1628,26 +1743,29 @@ export class TsEmbed {
1628
1743
  try {
1629
1744
  this.removeFullscreenChangeHandler();
1630
1745
  this.unsubscribeToEvents();
1746
+ this.preRenderWrapper?.remove();
1631
1747
  if (!this.isRendered) {
1632
1748
  return;
1633
1749
  }
1634
1750
  if (!getEmbedConfig().waitForCleanupOnDestroy) {
1635
- this.trigger(HostEvent.DestroyEmbed)
1751
+ this.trigger(HostEvent.DestroyEmbed);
1636
1752
  this.insertedDomEl?.parentNode?.removeChild(this.insertedDomEl);
1637
1753
  } else {
1638
1754
  const cleanupTimeout = getEmbedConfig().cleanupTimeout;
1639
1755
  Promise.race([
1640
1756
  this.trigger(HostEvent.DestroyEmbed),
1641
1757
  new Promise((resolve) => setTimeout(resolve, cleanupTimeout)),
1642
- ]).catch((e) => {
1643
- logger.log('Error destroying TS Embed', e);
1644
- }).finally(() => {
1645
- try {
1646
- this.insertedDomEl?.parentNode?.removeChild(this.insertedDomEl);
1647
- } catch (e) {
1648
- logger.log('Error removing DOM element on destroy', e);
1649
- }
1650
- });
1758
+ ])
1759
+ .catch((e) => {
1760
+ logger.log('Error destroying TS Embed', e);
1761
+ })
1762
+ .finally(() => {
1763
+ try {
1764
+ this.insertedDomEl?.parentNode?.removeChild(this.insertedDomEl);
1765
+ } catch (e) {
1766
+ logger.log('Error removing DOM element on destroy', e);
1767
+ }
1768
+ });
1651
1769
  }
1652
1770
  } catch (e) {
1653
1771
  logger.log('Error destroying TS Embed', e);
@@ -1669,7 +1787,7 @@ export class TsEmbed {
1669
1787
  if (!getIsInitCalled()) {
1670
1788
  logger.error(ERROR_MESSAGE.RENDER_CALLED_BEFORE_INIT);
1671
1789
  }
1672
- await this.isReadyForRenderPromise;
1790
+ if (this.shouldWaitForRenderPromise) await this.isReadyForRenderPromise;
1673
1791
 
1674
1792
  const prerenderFrameSrc = this.getRootIframeSrc();
1675
1793
  this.isRendered = true;
@@ -1677,80 +1795,72 @@ export class TsEmbed {
1677
1795
  }
1678
1796
 
1679
1797
  protected beforePrerenderVisible(): void {
1680
- // Override in subclass
1798
+ // We can ignore this as its a bit expensive and the newer customers
1799
+ // have moved on to UpdateEmbedParams supported clusters
1800
+ // this.validatePreRenderViewConfig(this.viewConfig); removed in #517
1801
+ logger.debug('triggering UpdateEmbedParams', this.viewConfig);
1802
+ this.executeAfterEmbedContainerLoaded(async () => {
1803
+ try {
1804
+ const params = await this.getUpdateEmbedParamsObject();
1805
+ this.trigger(HostEvent.UpdateEmbedParams, params);
1806
+ } catch (error) {
1807
+ logger.error(ERROR_MESSAGE.UPDATE_PARAMS_FAILED, error);
1808
+ this.handleError({
1809
+ errorType: ErrorDetailsTypes.API,
1810
+ message: error?.message || ERROR_MESSAGE.UPDATE_PARAMS_FAILED,
1811
+ code: EmbedErrorCodes.UPDATE_PARAMS_FAILED,
1812
+ error: error?.message || error,
1813
+ });
1814
+ }
1815
+ });
1681
1816
  }
1682
1817
 
1683
- private validatePreRenderViewConfig = (viewConfig: ViewConfig) => {
1684
- const preRenderAllowedKeys = ['preRenderId', 'vizId', 'liveboardId'];
1685
- const preRenderedObject = (this.insertedDomEl as any)?.[this.embedNodeKey] as TsEmbed;
1686
- if (!preRenderedObject) return;
1687
- if (viewConfig.preRenderId) {
1688
- const allOtherKeys = Object.keys(viewConfig).filter(
1689
- (key) => !preRenderAllowedKeys.includes(key) && !key.startsWith('on'),
1690
- );
1691
-
1692
- allOtherKeys.forEach((key: keyof ViewConfig) => {
1693
- if (
1694
- !isUndefined(viewConfig[key])
1695
- && !isEqual(viewConfig[key], preRenderedObject.viewConfig[key])
1696
- ) {
1697
- logger.warn(
1698
- `${viewConfig.embedComponentType || 'Component'} was pre-rendered with `
1699
- + `"${key}" as "${JSON.stringify(preRenderedObject.viewConfig[key])}" `
1700
- + `but a different value "${JSON.stringify(viewConfig[key])}" `
1701
- + 'was passed to the Embed component. '
1702
- + 'The new value provided is ignored, the value provided during '
1703
- + 'preRender is used.',
1704
- );
1705
- }
1706
- });
1707
- }
1708
- };
1709
-
1710
1818
  /**
1711
- * Displays the PreRender component.
1712
- * If the component is not preRendered, it attempts to create and render it.
1713
- * Also, synchronizes the style of the PreRender component with the embedding
1714
- * element.
1819
+ * Displays the pre-rendered component inside the host element.
1820
+ * If the component has not been pre-rendered yet, it initiates rendering first.
1821
+ * Inserts a placeholder element into the host and positions the pre-render
1822
+ * wrapper to overlay it.
1715
1823
  */
1716
1824
  public async showPreRender(): Promise<TsEmbed> {
1825
+ if (this.shouldWaitForRenderPromise) await this.isReadyForRenderPromise;
1826
+
1717
1827
  if (!this.viewConfig.preRenderId) {
1718
1828
  logger.error(ERROR_MESSAGE.PRERENDER_ID_MISSING);
1719
1829
  return this;
1720
1830
  }
1721
- if (!this.isPreRenderAvailable()) {
1722
- const isAvailable = this.connectPreRendered();
1831
+ if (!this.isPreRenderConnected()) {
1832
+ // this will call showPreRender down the line
1833
+ return this.preRender(true);
1834
+ }
1835
+ this.isRendered = true;
1836
+ this.beforePrerenderVisible();
1723
1837
 
1724
- if (!isAvailable) {
1725
- // if the Embed component is not preRendered , Render it now and
1726
- return this.preRender(true);
1727
- }
1728
- this.validatePreRenderViewConfig(this.viewConfig);
1729
- logger.debug('triggering UpdateEmbedParams', this.viewConfig);
1730
- this.executeAfterEmbedContainerLoaded(async () => {
1731
- try {
1732
- const params = await this.getUpdateEmbedParamsObject();
1733
- this.trigger(HostEvent.UpdateEmbedParams, params);
1734
- } catch (error) {
1735
- logger.error(ERROR_MESSAGE.UPDATE_PARAMS_FAILED, error);
1736
- this.handleError({
1737
- errorType: ErrorDetailsTypes.API,
1738
- message: error?.message || ERROR_MESSAGE.UPDATE_PARAMS_FAILED,
1739
- code: EmbedErrorCodes.UPDATE_PARAMS_FAILED,
1740
- error: error?.message || error,
1741
- });
1838
+ if (this.hostElement) {
1839
+ this.insertedDomEl = this.createPreRenderPlaceholder();
1840
+ if ((this.viewConfig as { fullHeight: boolean }).fullHeight) {
1841
+ // If fullHeight has already sized the wrapper, seed the placeholder
1842
+ // with the same height so syncPreRenderStyle gets an accurate rect.
1843
+ const existingHeight = this.preRenderWrapper.style.height;
1844
+ if (existingHeight) {
1845
+ (this.insertedDomEl as HTMLDivElement).style.height = existingHeight;
1742
1846
  }
1743
- });
1744
- }
1847
+ }
1745
1848
 
1746
- this.beforePrerenderVisible();
1849
+ const placeHolderId = this.getPreRenderIds().placeHolder;
1850
+ const oldEle = this.hostElement.querySelector(`#${placeHolderId}`);
1851
+ if (oldEle) {
1852
+ this.hostElement.removeChild(oldEle);
1853
+ }
1854
+
1855
+ this.hostElement.appendChild(this.insertedDomEl);
1747
1856
 
1748
- if (this.el) {
1749
1857
  this.syncPreRenderStyle();
1858
+
1750
1859
  if (!this.viewConfig.doNotTrackPreRenderSize) {
1860
+ const observeTarget = (this.insertedDomEl as HTMLElement) ?? this.hostElement;
1751
1861
  this.resizeObserver = new ResizeObserver((entries) => {
1752
1862
  entries.forEach((entry) => {
1753
- if (entry.contentRect && entry.target === this.el) {
1863
+ if (entry.contentRect && entry.target === observeTarget) {
1754
1864
  setStyleProperties(this.preRenderWrapper, {
1755
1865
  width: `${entry.contentRect.width}px`,
1756
1866
  height: `${entry.contentRect.height}px`,
@@ -1758,12 +1868,16 @@ export class TsEmbed {
1758
1868
  }
1759
1869
  });
1760
1870
  });
1761
- this.resizeObserver.observe(this.el);
1871
+ this.resizeObserver.observe(observeTarget);
1762
1872
  }
1763
1873
  }
1764
1874
 
1765
- removeStyleProperties(this.preRenderWrapper, ['z-index', 'opacity', 'pointer-events', 'overflow']);
1766
-
1875
+ removeStyleProperties(this.preRenderWrapper, [
1876
+ 'z-index',
1877
+ 'opacity',
1878
+ 'pointer-events',
1879
+ 'overflow',
1880
+ ]);
1767
1881
  this.subscribeToEvents();
1768
1882
 
1769
1883
  // Setup fullscreen change handler for prerendered components
@@ -1774,6 +1888,10 @@ export class TsEmbed {
1774
1888
  return this;
1775
1889
  }
1776
1890
 
1891
+ protected getPreRenderPlaceHolderElement() {
1892
+ return this.insertedDomEl as HTMLDivElement;
1893
+ }
1894
+
1777
1895
  /**
1778
1896
  * Synchronizes the style properties of the PreRender component with the embedding
1779
1897
  * element. This function adjusts the position, width, and height of the PreRender
@@ -1783,17 +1901,18 @@ export class TsEmbed {
1783
1901
  * is not defined or not found.
1784
1902
  */
1785
1903
  public syncPreRenderStyle(): void {
1786
- if (!this.isPreRenderAvailable() || !this.el) {
1904
+ if (!this.isPreRenderConnected() || !this.getPreRenderPlaceHolderElement()) {
1787
1905
  logger.error(ERROR_MESSAGE.SYNC_STYLE_CALLED_BEFORE_RENDER);
1788
1906
  return;
1789
1907
  }
1790
- const elBoundingClient = this.el.getBoundingClientRect();
1908
+ const elBoundingClient = this.getPreRenderPlaceHolderElement().getBoundingClientRect();
1791
1909
 
1792
1910
  setStyleProperties(this.preRenderWrapper, {
1793
1911
  top: `${elBoundingClient.y + window.scrollY}px`,
1794
1912
  left: `${elBoundingClient.x + window.scrollX}px`,
1795
1913
  width: `${elBoundingClient.width}px`,
1796
1914
  height: `${elBoundingClient.height}px`,
1915
+ position: 'absolute',
1797
1916
  });
1798
1917
  }
1799
1918
 
@@ -1802,7 +1921,8 @@ export class TsEmbed {
1802
1921
  * If the component is not preRendered, it issues a warning.
1803
1922
  */
1804
1923
  public hidePreRender(): void {
1805
- if (!this.isPreRenderAvailable()) {
1924
+ logger.debug('HidePreRender Called');
1925
+ if (!this.isPreRenderConnected()) {
1806
1926
  // if the embed component is not preRendered , nothing to hide
1807
1927
  logger.warn('PreRender should be called before hiding it using hidePreRender.');
1808
1928
  return;
@@ -1822,6 +1942,11 @@ export class TsEmbed {
1822
1942
  this.resizeObserver.disconnect();
1823
1943
  }
1824
1944
 
1945
+ const placeHolderEle = this.getPreRenderPlaceHolderElement();
1946
+ if (placeHolderEle) {
1947
+ placeHolderEle.parentElement.removeChild(placeHolderEle);
1948
+ }
1949
+
1825
1950
  this.unsubscribeToEvents();
1826
1951
  }
1827
1952
 
@@ -1836,6 +1961,7 @@ export class TsEmbed {
1836
1961
  return {
1837
1962
  wrapper: `tsEmbed-pre-render-wrapper-${this.viewConfig.preRenderId}`,
1838
1963
  child: `tsEmbed-pre-render-child-${this.viewConfig.preRenderId}`,
1964
+ placeHolder: `tsEmbed-pre-render-placeholder-${this.viewConfig.preRenderId}`,
1839
1965
  };
1840
1966
  }
1841
1967