@thoughtspot/visual-embed-sdk 1.10.0-alpha.4 → 1.10.2

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 (91) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/README.md +1 -1
  3. package/dist/src/auth.d.ts +18 -5
  4. package/dist/src/embed/base.d.ts +21 -5
  5. package/dist/src/embed/pinboard.d.ts +6 -0
  6. package/dist/src/embed/search.d.ts +4 -0
  7. package/dist/src/embed/ts-embed.d.ts +1 -1
  8. package/dist/src/index.d.ts +3 -2
  9. package/dist/src/types.d.ts +29 -0
  10. package/dist/src/utils/authService.d.ts +1 -0
  11. package/dist/src/utils/plugin.d.ts +0 -0
  12. package/dist/src/utils/processData.d.ts +1 -1
  13. package/dist/src/utils.d.ts +1 -0
  14. package/dist/src/v1/api.d.ts +19 -0
  15. package/dist/tsembed.es.js +543 -39
  16. package/dist/tsembed.js +541 -38
  17. package/lib/package.json +2 -1
  18. package/lib/src/auth.d.ts +18 -5
  19. package/lib/src/auth.js +51 -12
  20. package/lib/src/auth.js.map +1 -1
  21. package/lib/src/auth.spec.js +69 -11
  22. package/lib/src/auth.spec.js.map +1 -1
  23. package/lib/src/embed/base.d.ts +21 -5
  24. package/lib/src/embed/base.js +64 -10
  25. package/lib/src/embed/base.js.map +1 -1
  26. package/lib/src/embed/base.spec.js +49 -3
  27. package/lib/src/embed/base.spec.js.map +1 -1
  28. package/lib/src/embed/embed.spec.js +1 -1
  29. package/lib/src/embed/embed.spec.js.map +1 -1
  30. package/lib/src/embed/events.spec.js +30 -1
  31. package/lib/src/embed/events.spec.js.map +1 -1
  32. package/lib/src/embed/pinboard.d.ts +6 -0
  33. package/lib/src/embed/pinboard.js +4 -1
  34. package/lib/src/embed/pinboard.js.map +1 -1
  35. package/lib/src/embed/search.d.ts +4 -0
  36. package/lib/src/embed/search.js +1 -1
  37. package/lib/src/embed/search.js.map +1 -1
  38. package/lib/src/embed/ts-embed.d.ts +1 -1
  39. package/lib/src/embed/ts-embed.js +18 -14
  40. package/lib/src/embed/ts-embed.js.map +1 -1
  41. package/lib/src/embed/ts-embed.spec.js +16 -6
  42. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  43. package/lib/src/index.d.ts +3 -2
  44. package/lib/src/index.js +3 -2
  45. package/lib/src/index.js.map +1 -1
  46. package/lib/src/test/test-utils.js +1 -1
  47. package/lib/src/test/test-utils.js.map +1 -1
  48. package/lib/src/types.d.ts +29 -0
  49. package/lib/src/types.js +16 -0
  50. package/lib/src/types.js.map +1 -1
  51. package/lib/src/utils/authService.d.ts +1 -0
  52. package/lib/src/utils/authService.js +21 -3
  53. package/lib/src/utils/authService.js.map +1 -1
  54. package/lib/src/utils/authService.spec.js +21 -5
  55. package/lib/src/utils/authService.spec.js.map +1 -1
  56. package/lib/src/utils/plugin.d.ts +0 -0
  57. package/lib/src/utils/plugin.js +1 -0
  58. package/lib/src/utils/plugin.js.map +1 -0
  59. package/lib/src/utils/processData.d.ts +1 -1
  60. package/lib/src/utils/processData.js +37 -3
  61. package/lib/src/utils/processData.js.map +1 -1
  62. package/lib/src/utils/processData.spec.js +106 -4
  63. package/lib/src/utils/processData.spec.js.map +1 -1
  64. package/lib/src/utils.d.ts +1 -0
  65. package/lib/src/utils.js +4 -0
  66. package/lib/src/utils.js.map +1 -1
  67. package/lib/src/utils.spec.js +14 -1
  68. package/lib/src/utils.spec.js.map +1 -1
  69. package/lib/src/visual-embed-sdk.d.ts +119 -8
  70. package/package.json +2 -1
  71. package/src/auth.spec.ts +90 -11
  72. package/src/auth.ts +74 -16
  73. package/src/embed/base.spec.ts +56 -4
  74. package/src/embed/base.ts +83 -16
  75. package/src/embed/embed.spec.ts +1 -1
  76. package/src/embed/events.spec.ts +32 -0
  77. package/src/embed/search.ts +5 -0
  78. package/src/embed/ts-embed.spec.ts +19 -9
  79. package/src/embed/ts-embed.ts +24 -15
  80. package/src/index.ts +5 -1
  81. package/src/test/test-utils.ts +1 -1
  82. package/src/types.ts +31 -0
  83. package/src/utils/authService.spec.ts +31 -5
  84. package/src/utils/authService.ts +27 -3
  85. package/src/utils/processData.spec.ts +139 -4
  86. package/src/utils/processData.ts +54 -4
  87. package/src/utils.spec.ts +26 -0
  88. package/src/utils.ts +5 -0
  89. package/lib/src/utils/fetchAnswers.d.ts +0 -3
  90. package/lib/src/utils/fetchAnswers.js +0 -49
  91. package/lib/src/utils/fetchAnswers.js.map +0 -1
@@ -1,4 +1,7 @@
1
+ import EventEmitter from 'eventemitter3';
2
+ import * as auth from '../auth';
1
3
  import * as index from '../index';
4
+ import * as base from './base';
2
5
  import {
3
6
  executeAfterWait,
4
7
  getAllIframeEl,
@@ -9,10 +12,11 @@ import {
9
12
  } from '../test/test-utils';
10
13
 
11
14
  const thoughtSpotHost = 'tshost';
15
+ let authEE: EventEmitter;
12
16
 
13
17
  describe('Base TS Embed', () => {
14
18
  beforeAll(() => {
15
- index.init({
19
+ authEE = index.init({
16
20
  thoughtSpotHost,
17
21
  authType: index.AuthType.None,
18
22
  });
@@ -38,10 +42,12 @@ describe('Base TS Embed', () => {
38
42
  },
39
43
  '*',
40
44
  );
41
-
42
- jest.spyOn(window, 'alert').mockImplementation(() => {
45
+ jest.spyOn(window, 'alert').mockReset();
46
+ jest.spyOn(window, 'alert').mockImplementation(() => undefined);
47
+ authEE.on(auth.AuthStatus.FAILURE, (reason) => {
48
+ expect(reason).toEqual(auth.AuthFailureType.NO_COOKIE_ACCESS);
43
49
  expect(window.alert).toBeCalledWith(
44
- 'Third party cookie access is blocked on this browser, please allow third party cookies for ThoughtSpot to work properly',
50
+ 'Third party cookie access is blocked on this browser, please allow third party cookies for this to work properly. \nYou can use `suppressNoCookieAccessAlert` to suppress this message.',
45
51
  );
46
52
  done();
47
53
  });
@@ -92,4 +98,50 @@ describe('Base TS Embed', () => {
92
98
  expect(getIFrameSrc()).toContain('disableLoginRedirect=true');
93
99
  });
94
100
  });
101
+
102
+ test('handleAuth notifies for SDK auth failure', (done) => {
103
+ jest.spyOn(auth, 'authenticate').mockResolvedValue(false);
104
+ const authEmitter = index.init({
105
+ thoughtSpotHost,
106
+ authType: index.AuthType.Basic,
107
+ username: 'test',
108
+ password: 'test',
109
+ });
110
+ authEmitter.on(auth.AuthStatus.FAILURE, (reason) => {
111
+ expect(reason).toBe(auth.AuthFailureType.SDK);
112
+ done();
113
+ });
114
+ });
115
+
116
+ test('Logout method should disable autoLogin', () => {
117
+ jest.spyOn(window, 'fetch').mockResolvedValue({
118
+ type: 'opaque',
119
+ });
120
+ index.init({
121
+ thoughtSpotHost,
122
+ authType: index.AuthType.None,
123
+ autoLogin: true,
124
+ });
125
+ index.logout();
126
+ expect(window.fetch).toHaveBeenCalledWith(
127
+ `http://${thoughtSpotHost}${auth.EndPoints.LOGOUT}`,
128
+ {
129
+ credentials: 'include',
130
+ mode: 'no-cors',
131
+ method: 'POST',
132
+ },
133
+ );
134
+ expect(base.getEmbedConfig().autoLogin).toBe(false);
135
+ });
136
+ });
137
+
138
+ describe('Base without init', () => {
139
+ test('notify should error when called without init', () => {
140
+ base.reset();
141
+ jest.spyOn(global.console, 'error').mockImplementation(() => undefined);
142
+ base.notifyAuthSuccess();
143
+ base.notifyAuthFailure(auth.AuthFailureType.SDK);
144
+ base.notifyLogout();
145
+ expect(global.console.error).toHaveBeenCalledTimes(3);
146
+ });
95
147
  });
package/src/embed/base.ts CHANGED
@@ -7,31 +7,72 @@
7
7
  * @summary Base classes
8
8
  * @author Ayon Ghosh <ayon.ghosh@thoughtspot.com>
9
9
  */
10
+ import EventEmitter from 'eventemitter3';
10
11
  import { getThoughtSpotHost } from '../config';
11
- import { EmbedConfig } from '../types';
12
- import { authenticate } from '../auth';
12
+ import { AuthType, EmbedConfig } from '../types';
13
+ import {
14
+ authenticate,
15
+ logout as _logout,
16
+ AuthFailureType,
17
+ AuthStatus,
18
+ } from '../auth';
13
19
  import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service';
14
20
 
15
21
  let config = {} as EmbedConfig;
22
+ const CONFIG_DEFAULTS: Partial<EmbedConfig> = {
23
+ loginFailedMessage: 'Not logged in',
24
+ authType: AuthType.None,
25
+ };
26
+
27
+ export let authPromise: Promise<boolean>;
28
+
29
+ export const getEmbedConfig = (): EmbedConfig => config;
30
+
31
+ export const getAuthPromise = (): Promise<boolean> => authPromise;
16
32
 
17
- export let authPromise: Promise<void>;
33
+ let authEE: EventEmitter;
18
34
 
35
+ export function notifyAuthSuccess(): void {
36
+ if (!authEE) {
37
+ console.error('SDK not initialized');
38
+ return;
39
+ }
40
+ authEE.emit(AuthStatus.SUCCESS);
41
+ }
42
+
43
+ export function notifyAuthFailure(failureType: AuthFailureType): void {
44
+ if (!authEE) {
45
+ console.error('SDK not initialized');
46
+ return;
47
+ }
48
+ authEE.emit(AuthStatus.FAILURE, failureType);
49
+ }
50
+
51
+ export function notifyLogout(): void {
52
+ if (!authEE) {
53
+ console.error('SDK not initialized');
54
+ return;
55
+ }
56
+ authEE.emit(AuthStatus.LOGOUT);
57
+ }
19
58
  /**
20
59
  * Perform authentication on the ThoughtSpot app as applicable.
21
60
  */
22
- export const handleAuth = (): Promise<void> => {
23
- const authConfig = {
24
- ...config,
25
- thoughtSpotHost: getThoughtSpotHost(config),
26
- };
27
- authPromise = authenticate(authConfig);
61
+ export const handleAuth = (): Promise<boolean> => {
62
+ authPromise = authenticate(config);
63
+ authPromise.then(
64
+ (isLoggedIn) => {
65
+ if (!isLoggedIn) {
66
+ notifyAuthFailure(AuthFailureType.SDK);
67
+ }
68
+ },
69
+ () => {
70
+ notifyAuthFailure(AuthFailureType.SDK);
71
+ },
72
+ );
28
73
  return authPromise;
29
74
  };
30
75
 
31
- export const getEmbedConfig = (): EmbedConfig => config;
32
-
33
- export const getAuthPromise = (): Promise<void> => authPromise;
34
-
35
76
  /**
36
77
  * Prefetches static resources from the specified URL. Web browsers can then cache the prefetched resources and serve them from the user's local disk to provide faster access to your app.
37
78
  * @param url The URL provided for prefetch
@@ -59,8 +100,13 @@ export const prefetch = (url?: string): void => {
59
100
  *
60
101
  * @returns authPromise Promise which resolves when authentication is complete.
61
102
  */
62
- export const init = (embedConfig: EmbedConfig): Promise<void> => {
63
- config = embedConfig;
103
+ export const init = (embedConfig: EmbedConfig): EventEmitter => {
104
+ config = {
105
+ ...CONFIG_DEFAULTS,
106
+ ...embedConfig,
107
+ thoughtSpotHost: getThoughtSpotHost(embedConfig),
108
+ };
109
+ authEE = new EventEmitter();
64
110
  handleAuth();
65
111
 
66
112
  uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_CALLED_INIT, {
@@ -71,7 +117,21 @@ export const init = (embedConfig: EmbedConfig): Promise<void> => {
71
117
  if (config.callPrefetch) {
72
118
  prefetch(config.thoughtSpotHost);
73
119
  }
74
- return authPromise;
120
+ return authEE;
121
+ };
122
+
123
+ export function disableAutoLogin(): void {
124
+ config.autoLogin = false;
125
+ }
126
+
127
+ export const logout = (doNotDisableAutoLogin = false): Promise<boolean> => {
128
+ if (!doNotDisableAutoLogin) {
129
+ disableAutoLogin();
130
+ }
131
+ return _logout(config).then((isLoggedIn) => {
132
+ notifyLogout();
133
+ return isLoggedIn;
134
+ });
75
135
  };
76
136
 
77
137
  let renderQueue: Promise<any> = Promise.resolve();
@@ -89,3 +149,10 @@ export const renderInQueue = (fn: (next?: (val?: any) => void) => void) => {
89
149
  fn(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function
90
150
  }
91
151
  };
152
+
153
+ // For testing purposes only
154
+ export function reset(): void {
155
+ config = {} as any;
156
+ authEE = null;
157
+ authPromise = null;
158
+ }
@@ -71,7 +71,7 @@ describe('Custom CSS Url', () => {
71
71
  document.body.innerHTML = getDocumentBody();
72
72
  });
73
73
 
74
- test.only('passing customCssUrl should set the correct query params on the iframe', async (done) => {
74
+ test('passing customCssUrl should set the correct query params on the iframe', async (done) => {
75
75
  init({
76
76
  thoughtSpotHost,
77
77
  authType: AuthType.None,
@@ -5,6 +5,7 @@ import {
5
5
  SearchEmbed,
6
6
  PinboardEmbed,
7
7
  LiveboardEmbed,
8
+ AppEmbed,
8
9
  HostEvent,
9
10
  } from '../index';
10
11
  import {
@@ -247,4 +248,35 @@ describe('test communication between host app and ThoughtSpot', () => {
247
248
  };
248
249
  expect(mockPort.postMessage).toHaveBeenCalledWith(heightObj);
249
250
  });
251
+ test('ALL event listener should fire for all events with the event type set correctly', async () => {
252
+ const embed = new AppEmbed(getRootEl(), defaultViewConfig);
253
+ const spy = jest.fn();
254
+ embed.on(EmbedEvent.ALL, spy);
255
+ embed.render();
256
+
257
+ await executeAfterWait(() => {
258
+ const iframe = getIFrameEl();
259
+ postMessageToParent(iframe.contentWindow, {
260
+ type: EmbedEvent.CustomAction,
261
+ data: PAYLOAD,
262
+ });
263
+ postMessageToParent(iframe.contentWindow, {
264
+ type: EmbedEvent.DialogOpen,
265
+ });
266
+ });
267
+
268
+ await executeAfterWait(() => {
269
+ expect(spy).toHaveBeenCalledTimes(3);
270
+ expect(spy.mock.calls[0][0]).toMatchObject({
271
+ type: EmbedEvent.Init,
272
+ });
273
+ expect(spy.mock.calls[1][0]).toMatchObject({
274
+ type: EmbedEvent.CustomAction,
275
+ data: PAYLOAD,
276
+ });
277
+ expect(spy.mock.calls[2][0]).toMatchObject({
278
+ type: EmbedEvent.DialogOpen,
279
+ });
280
+ }, EVENT_WAIT_TIME);
281
+ });
250
282
  });
@@ -49,6 +49,10 @@ export interface SearchViewConfig extends ViewConfig {
49
49
  * using raw answer data.
50
50
  */
51
51
  hideResults?: boolean;
52
+ /**
53
+ * If set to true, expands all the data sources panel.
54
+ */
55
+ expandAllDataSource?: boolean;
52
56
  /**
53
57
  * If set to true, the Search Assist feature is enabled.
54
58
  */
@@ -126,6 +130,7 @@ export class SearchEmbed extends TsEmbed {
126
130
  private getIFrameSrc(answerId: string, dataSources?: string[]) {
127
131
  const {
128
132
  hideResults,
133
+ expandAllDataSource,
129
134
  enableSearchAssist,
130
135
  forceTable,
131
136
  searchOptions,
@@ -165,15 +165,16 @@ describe('Unit test case for ts embed', () => {
165
165
  });
166
166
  });
167
167
 
168
- describe('when thoughtSpotHost have value and authPromise return success response', () => {
168
+ describe('when thoughtSpotHost have value and authPromise return response true/false', () => {
169
169
  beforeAll(() => {
170
170
  init({
171
171
  thoughtSpotHost,
172
172
  authType: AuthType.None,
173
+ loginFailedMessage: 'Failed to Login',
173
174
  });
174
175
  });
175
176
 
176
- beforeEach(() => {
177
+ const setup = async (isLoggedIn = false) => {
177
178
  jest.spyOn(window, 'addEventListener').mockImplementationOnce(
178
179
  (event, handler, options) => {
179
180
  handler({
@@ -186,10 +187,9 @@ describe('Unit test case for ts embed', () => {
186
187
  },
187
188
  );
188
189
  const iFrame: any = document.createElement('div');
189
- jest.spyOn(
190
- baseInstance,
191
- 'getAuthPromise',
192
- ).mockResolvedValueOnce(() => Promise.resolve());
190
+ jest.spyOn(baseInstance, 'getAuthPromise').mockResolvedValueOnce(
191
+ isLoggedIn,
192
+ );
193
193
  const tsEmbed = new SearchEmbed(getRootEl(), {});
194
194
  iFrame.contentWindow = null;
195
195
  tsEmbed.on(EmbedEvent.CustomAction, jest.fn());
@@ -199,10 +199,11 @@ describe('Unit test case for ts embed', () => {
199
199
  },
200
200
  );
201
201
  jest.spyOn(document, 'createElement').mockReturnValueOnce(iFrame);
202
- tsEmbed.render();
203
- });
202
+ await tsEmbed.render();
203
+ };
204
204
 
205
- test('mixpanel should call with VISUAL_SDK_RENDER_COMPLETE', () => {
205
+ test('mixpanel should call with VISUAL_SDK_RENDER_COMPLETE', async () => {
206
+ await setup(true);
206
207
  expect(mockMixPanelEvent).toBeCalledWith(
207
208
  MIXPANEL_EVENT.VISUAL_SDK_RENDER_START,
208
209
  );
@@ -212,11 +213,20 @@ describe('Unit test case for ts embed', () => {
212
213
  });
213
214
 
214
215
  test('Should remove prefetch iframe', async () => {
216
+ await setup(true);
215
217
  const prefetchIframe = document.querySelectorAll<HTMLIFrameElement>(
216
218
  '.prefetchIframe',
217
219
  );
218
220
  expect(prefetchIframe.length).toBe(0);
219
221
  });
222
+
223
+ test('Should render failure when login fails', async (done) => {
224
+ setup(false);
225
+ executeAfterWait(() => {
226
+ expect(getRootEl().innerHTML).toContain('Failed to Login');
227
+ done();
228
+ });
229
+ });
220
230
  });
221
231
 
222
232
  describe('when thoughtSpotHost have value and authPromise return error', () => {
@@ -34,7 +34,7 @@ import {
34
34
  MessageCallbackObj,
35
35
  } from '../types';
36
36
  import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service';
37
- import { getProcessData } from '../utils/processData';
37
+ import { processEventData } from '../utils/processData';
38
38
  import { processTrigger } from '../utils/processTrigger';
39
39
  import pkgInfo from '../../package.json';
40
40
  import { getAuthPromise, getEmbedConfig, renderInQueue } from './base';
@@ -212,14 +212,6 @@ export class TsEmbed {
212
212
  this.isError = false;
213
213
  this.viewConfig = viewConfig;
214
214
  this.shouldEncodeUrlQueryParams = this.embedConfig.shouldEncodeUrlQueryParams;
215
- if (!this.embedConfig.suppressNoCookieAccessAlert) {
216
- this.on(EmbedEvent.NoCookieAccess, () => {
217
- // eslint-disable-next-line no-alert
218
- alert(
219
- 'Third party cookie access is blocked on this browser, please allow third party cookies for ThoughtSpot to work properly',
220
- );
221
- });
222
- }
223
215
  }
224
216
 
225
217
  /**
@@ -279,9 +271,10 @@ export class TsEmbed {
279
271
  * will be removed for ts7.oct.cl
280
272
  * @hidden
281
273
  */
282
- private formatEventData(event: MessageEvent) {
274
+ private formatEventData(event: MessageEvent, eventType: string) {
283
275
  const eventData = {
284
276
  ...event.data,
277
+ type: eventType,
285
278
  };
286
279
  if (!eventData.data) {
287
280
  eventData.data = event.data.payload;
@@ -299,11 +292,16 @@ export class TsEmbed {
299
292
  window.addEventListener('message', (event) => {
300
293
  const eventType = this.getEventType(event);
301
294
  const eventPort = this.getEventPort(event);
302
- const eventData = this.formatEventData(event);
295
+ const eventData = this.formatEventData(event, eventType);
303
296
  if (event.source === this.iFrame.contentWindow) {
304
297
  this.executeCallbacks(
305
298
  eventType,
306
- getProcessData(eventType, eventData, this.thoughtSpotHost),
299
+ processEventData(
300
+ eventType,
301
+ eventData,
302
+ this.thoughtSpotHost,
303
+ this.el,
304
+ ),
307
305
  eventPort,
308
306
  );
309
307
  }
@@ -457,12 +455,18 @@ export class TsEmbed {
457
455
  data: {
458
456
  timestamp: initTimestamp,
459
457
  },
458
+ type: EmbedEvent.Init,
460
459
  });
461
460
 
462
461
  uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_START);
463
462
 
464
463
  getAuthPromise()
465
- ?.then(() => {
464
+ ?.then((isLoggedIn: boolean) => {
465
+ if (!isLoggedIn) {
466
+ this.el.innerHTML = this.embedConfig.loginFailedMessage;
467
+ return;
468
+ }
469
+
466
470
  uploadMixpanelEvent(
467
471
  MIXPANEL_EVENT.VISUAL_SDK_RENDER_COMPLETE,
468
472
  );
@@ -490,7 +494,7 @@ export class TsEmbed {
490
494
  frameWidth || DEFAULT_EMBED_WIDTH,
491
495
  );
492
496
  const height = getCssDimension(
493
- frameWidth || DEFAULT_EMBED_HEIGHT,
497
+ frameHeight || DEFAULT_EMBED_HEIGHT,
494
498
  );
495
499
  setAttributes(this.iFrame, restParams);
496
500
 
@@ -505,6 +509,7 @@ export class TsEmbed {
505
509
  data: {
506
510
  timestamp: loadTimestamp,
507
511
  },
512
+ type: EmbedEvent.Load,
508
513
  });
509
514
  uploadMixpanelEvent(
510
515
  MIXPANEL_EVENT.VISUAL_SDK_IFRAME_LOAD_PERFORMANCE,
@@ -533,6 +538,7 @@ export class TsEmbed {
533
538
  uploadMixpanelEvent(
534
539
  MIXPANEL_EVENT.VISUAL_SDK_RENDER_FAILED,
535
540
  );
541
+ this.el.innerHTML = this.embedConfig.loginFailedMessage;
536
542
  this.handleError(error);
537
543
  });
538
544
  });
@@ -558,6 +564,8 @@ export class TsEmbed {
558
564
  eventPort?: MessagePort | void,
559
565
  ): void {
560
566
  const callbacks = this.eventHandlerMap.get(eventType) || [];
567
+ const allHandlers = this.eventHandlerMap.get(EmbedEvent.ALL) || [];
568
+ callbacks.push(...allHandlers);
561
569
  const dataStatus = data?.status || embedEventStatus.END;
562
570
  callbacks.forEach((callbackObj) => {
563
571
  if (
@@ -760,9 +768,10 @@ export class V1Embed extends TsEmbed {
760
768
  public on(
761
769
  messageType: EmbedEvent,
762
770
  callback: MessageCallback,
771
+ options: MessageOptions = { start: false },
763
772
  ): typeof TsEmbed.prototype {
764
773
  const eventType = this.getCompatibleEventType(messageType);
765
774
 
766
- return super.on(eventType, callback);
775
+ return super.on(eventType, callback, options);
767
776
  }
768
777
  }
package/src/index.ts CHANGED
@@ -9,13 +9,14 @@
9
9
  */
10
10
 
11
11
  import { AppEmbed, Page, AppViewConfig } from './embed/app';
12
- import { init, prefetch } from './embed/base';
12
+ import { init, prefetch, logout } from './embed/base';
13
13
  import {
14
14
  PinboardEmbed,
15
15
  LiveboardViewConfig,
16
16
  LiveboardEmbed,
17
17
  } from './embed/liveboard';
18
18
  import { SearchEmbed, SearchViewConfig } from './embed/search';
19
+ import { AuthFailureType, AuthStatus } from './auth';
19
20
  import {
20
21
  AuthType,
21
22
  RuntimeFilter,
@@ -29,11 +30,14 @@ import {
29
30
 
30
31
  export {
31
32
  init,
33
+ logout,
32
34
  prefetch,
33
35
  SearchEmbed,
34
36
  PinboardEmbed,
35
37
  LiveboardEmbed,
36
38
  AppEmbed,
39
+ AuthFailureType,
40
+ AuthStatus,
37
41
  // types
38
42
  Page,
39
43
  AuthType,
@@ -14,7 +14,7 @@ type DOMElement = HTMLElement | Document;
14
14
 
15
15
  export const getRootEl = () => document.getElementById('embed');
16
16
 
17
- export const getRootEl2 = () => document.getElementById('emebed-2');
17
+ export const getRootEl2 = () => document.getElementById('embed-2');
18
18
 
19
19
  export const getIFrameEl = (container: DOMElement = document) =>
20
20
  container.querySelector('iframe');
package/src/types.ts CHANGED
@@ -97,6 +97,15 @@ export interface EmbedConfig {
97
97
  */
98
98
  noRedirect?: boolean;
99
99
 
100
+ /**
101
+ * [SSO] For SSO Authentication, one can supply an optional path param,
102
+ * this will be the path on the host origin where the SAML flow will be
103
+ * terminated.
104
+ *
105
+ * @version SDK: 1.10.2 | ThoughtSpot: *
106
+ */
107
+ redirectPath?: string;
108
+
100
109
  /** @internal */
101
110
  basepath?: string;
102
111
 
@@ -131,6 +140,12 @@ export interface EmbedConfig {
131
140
  */
132
141
  disableLoginRedirect?: boolean;
133
142
 
143
+ /**
144
+ * This message is displayed on the embed view when the login fails.
145
+ * @version 1.10.1 | ThoughtSpot: *
146
+ */
147
+ loginFailedMessage?: string;
148
+
134
149
  /**
135
150
  * Calls the prefetch method internally when set to true
136
151
  * @default false
@@ -365,6 +380,16 @@ export enum EmbedEvent {
365
380
  * The ThoughtSpot auth session has expired.
366
381
  */
367
382
  AuthExpire = 'ThoughtspotAuthExpired',
383
+ /**
384
+ * ThoughtSpot failed to validate the auth session.
385
+ * @hidden
386
+ */
387
+ AuthFailure = 'ThoughtspotAuthFailure',
388
+ /**
389
+ * ThoughtSpot failed to validate the auth session.
390
+ * @hidden
391
+ */
392
+ AuthLogout = 'ThoughtspotAuthLogout',
368
393
  /**
369
394
  * The height of the embedded Liveboard or visualization has been computed.
370
395
  * @return data - The height of the embedded Liveboard or visualization
@@ -416,6 +441,12 @@ export enum EmbedEvent {
416
441
  * @version 1.9.1 or later
417
442
  */
418
443
  LiveboardRendered = 'PinboardRendered',
444
+ /**
445
+ * This can be used to register an event listener which
446
+ * is triggered on all events.
447
+ * @version SDK: 1.10.0 | ThoughtSpot: any
448
+ */
449
+ ALL = '*',
419
450
  /**
420
451
  * Emitted when answer is saved in the app
421
452
  * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl
@@ -4,6 +4,7 @@ import {
4
4
  fetchAuthService,
5
5
  fetchBasicAuthService,
6
6
  } from './authService';
7
+ import { EndPoints } from '../auth';
7
8
 
8
9
  const thoughtSpotHost = 'http://10.79.135.124:3000';
9
10
 
@@ -21,6 +22,7 @@ describe('Unit test for authService', () => {
21
22
  Promise.resolve({
22
23
  json: () => ({ success: true }),
23
24
  status: 200,
25
+ ok: true,
24
26
  }),
25
27
  );
26
28
  const response = await fetchSessionInfoService(authVerificationUrl);
@@ -32,6 +34,7 @@ describe('Unit test for authService', () => {
32
34
  global.fetch = jest.fn(() =>
33
35
  Promise.resolve({
34
36
  text: () => ({ success: true }),
37
+ ok: true,
35
38
  }),
36
39
  );
37
40
  const response = await fetchAuthTokenService(authEndpoint);
@@ -40,14 +43,37 @@ describe('Unit test for authService', () => {
40
43
  });
41
44
 
42
45
  test('fetchAuthService', async () => {
43
- global.fetch = jest.fn(() => Promise.resolve({ success: true }));
44
- await fetchAuthService(authVerificationUrl, username, authToken);
45
- expect(fetch).toBeCalled();
46
+ global.fetch = jest.fn(() =>
47
+ Promise.resolve({ success: true, ok: true }),
48
+ );
49
+ await fetchAuthService(thoughtSpotHost, username, authToken);
50
+ expect(fetch).toBeCalledWith(
51
+ `${thoughtSpotHost}${EndPoints.TOKEN_LOGIN}?username=${username}&auth_token=${authToken}`,
52
+ {
53
+ credentials: 'include',
54
+ redirect: 'manual',
55
+ },
56
+ );
46
57
  });
47
58
 
48
- test('fetchBasicAuthService', async () => {
49
- global.fetch = jest.fn(() => Promise.resolve({ success: true }));
59
+ test('fetchBasicAuthService called with manual redirect', async () => {
60
+ global.fetch = jest.fn(() =>
61
+ Promise.resolve({ success: true, ok: true }),
62
+ );
50
63
  await fetchBasicAuthService(thoughtSpotHost, username, password);
51
64
  expect(fetch).toBeCalled();
52
65
  });
66
+
67
+ test('log error on API failures', async () => {
68
+ jest.spyOn(global.console, 'error').mockImplementation(() => undefined);
69
+ global.fetch = jest.fn(() =>
70
+ Promise.resolve({
71
+ text: () => Promise.resolve('error'),
72
+ status: 500,
73
+ ok: false,
74
+ }),
75
+ );
76
+ await fetchSessionInfoService(authVerificationUrl);
77
+ expect(global.console.error).toHaveBeenCalledWith('Failure', 'error');
78
+ });
53
79
  });