@thoughtspot/visual-embed-sdk 1.10.0-alpha.4 → 1.11.0-auth.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 (50) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -1
  3. package/dist/src/auth.d.ts +5 -5
  4. package/dist/src/embed/base.d.ts +4 -4
  5. package/dist/src/embed/pinboard.d.ts +6 -0
  6. package/dist/src/embed/ts-embed.d.ts +1 -1
  7. package/dist/src/types.d.ts +6 -0
  8. package/dist/src/utils/plugin.d.ts +0 -0
  9. package/dist/src/v1/api.d.ts +19 -0
  10. package/dist/tsembed.es.js +45 -14
  11. package/dist/tsembed.js +45 -14
  12. package/lib/package.json +1 -1
  13. package/lib/src/auth.d.ts +5 -5
  14. package/lib/src/auth.js +13 -5
  15. package/lib/src/auth.js.map +1 -1
  16. package/lib/src/auth.spec.js +7 -3
  17. package/lib/src/auth.spec.js.map +1 -1
  18. package/lib/src/embed/base.d.ts +4 -4
  19. package/lib/src/embed/base.js.map +1 -1
  20. package/lib/src/embed/events.spec.js +30 -1
  21. package/lib/src/embed/events.spec.js.map +1 -1
  22. package/lib/src/embed/pinboard.d.ts +6 -0
  23. package/lib/src/embed/pinboard.js +4 -1
  24. package/lib/src/embed/pinboard.js.map +1 -1
  25. package/lib/src/embed/ts-embed.d.ts +1 -1
  26. package/lib/src/embed/ts-embed.js +14 -5
  27. package/lib/src/embed/ts-embed.js.map +1 -1
  28. package/lib/src/types.d.ts +6 -0
  29. package/lib/src/types.js +6 -0
  30. package/lib/src/types.js.map +1 -1
  31. package/lib/src/utils/authService.js +11 -3
  32. package/lib/src/utils/authService.js.map +1 -1
  33. package/lib/src/utils/authService.spec.js +4 -2
  34. package/lib/src/utils/authService.spec.js.map +1 -1
  35. package/lib/src/utils/plugin.d.ts +0 -0
  36. package/lib/src/utils/plugin.js +1 -0
  37. package/lib/src/utils/plugin.js.map +1 -0
  38. package/lib/src/visual-embed-sdk.d.ts +11 -5
  39. package/package.json +1 -1
  40. package/src/auth.spec.ts +11 -3
  41. package/src/auth.ts +24 -10
  42. package/src/embed/base.ts +4 -4
  43. package/src/embed/events.spec.ts +32 -0
  44. package/src/embed/ts-embed.ts +15 -4
  45. package/src/types.ts +6 -0
  46. package/src/utils/authService.spec.ts +8 -2
  47. package/src/utils/authService.ts +15 -3
  48. package/lib/src/utils/fetchAnswers.d.ts +0 -3
  49. package/lib/src/utils/fetchAnswers.js +0 -49
  50. package/lib/src/utils/fetchAnswers.js.map +0 -1
package/src/auth.spec.ts CHANGED
@@ -120,7 +120,11 @@ describe('Unit test for auth', () => {
120
120
  authService,
121
121
  'fetchAuthTokenService',
122
122
  ).mockImplementation(() => ({ text: () => Promise.resolve('abc') }));
123
- jest.spyOn(authService, 'fetchAuthService');
123
+ jest.spyOn(authService, 'fetchAuthService').mockImplementation(() =>
124
+ Promise.resolve({
125
+ status: 200,
126
+ }),
127
+ );
124
128
  await authInstance.doTokenAuth(embedConfig.doTokenAuthSuccess);
125
129
  expect(authService.fetchSessionInfoService).toBeCalled();
126
130
  expect(authService.fetchAuthService).toBeCalledWith(
@@ -140,7 +144,11 @@ describe('Unit test for auth', () => {
140
144
  ).mockImplementation(() =>
141
145
  Promise.resolve({ text: () => Promise.resolve('abc') }),
142
146
  );
143
- jest.spyOn(authService, 'fetchAuthService');
147
+ jest.spyOn(authService, 'fetchAuthService').mockImplementation(() =>
148
+ Promise.resolve({
149
+ status: 200,
150
+ }),
151
+ );
144
152
  await authInstance.doTokenAuth(
145
153
  embedConfig.doTokenAuthFailureWithoutGetAuthToken,
146
154
  );
@@ -263,7 +271,7 @@ describe('Unit test for auth', () => {
263
271
  ...embedConfig.doSamlAuth,
264
272
  noRedirect: true,
265
273
  }),
266
- ).toBe(undefined);
274
+ ).toBe(false);
267
275
  expect(authService.fetchSessionInfoService).toBeCalled();
268
276
  });
269
277
  });
package/src/auth.ts CHANGED
@@ -83,7 +83,9 @@ function removeSSORedirectUrlMarker(): void {
83
83
  * Perform token based authentication
84
84
  * @param embedConfig The embed configuration
85
85
  */
86
- export const doTokenAuth = async (embedConfig: EmbedConfig): Promise<void> => {
86
+ export const doTokenAuth = async (
87
+ embedConfig: EmbedConfig,
88
+ ): Promise<boolean> => {
87
89
  const {
88
90
  thoughtSpotHost,
89
91
  username,
@@ -104,11 +106,16 @@ export const doTokenAuth = async (embedConfig: EmbedConfig): Promise<void> => {
104
106
  const response = await fetchAuthTokenService(authEndpoint);
105
107
  authToken = await response.text();
106
108
  }
107
- await fetchAuthService(thoughtSpotHost, username, authToken);
108
- loggedInStatus = false;
109
+ const resp = await fetchAuthService(
110
+ thoughtSpotHost,
111
+ username,
112
+ authToken,
113
+ );
114
+ loggedInStatus = resp.status === 200;
115
+ } else {
116
+ loggedInStatus = true;
109
117
  }
110
-
111
- loggedInStatus = true;
118
+ return loggedInStatus;
112
119
  };
113
120
 
114
121
  /**
@@ -119,7 +126,9 @@ export const doTokenAuth = async (embedConfig: EmbedConfig): Promise<void> => {
119
126
  * strongly advised not to use this authentication method in production.
120
127
  * @param embedConfig The embed configuration
121
128
  */
122
- export const doBasicAuth = async (embedConfig: EmbedConfig): Promise<void> => {
129
+ export const doBasicAuth = async (
130
+ embedConfig: EmbedConfig,
131
+ ): Promise<boolean> => {
123
132
  const { thoughtSpotHost, username, password } = embedConfig;
124
133
  const loggedIn = await isLoggedIn(thoughtSpotHost);
125
134
  if (!loggedIn) {
@@ -129,9 +138,10 @@ export const doBasicAuth = async (embedConfig: EmbedConfig): Promise<void> => {
129
138
  password,
130
139
  );
131
140
  loggedInStatus = response.status === 200;
141
+ } else {
142
+ loggedInStatus = true;
132
143
  }
133
-
134
- loggedInStatus = true;
144
+ return loggedInStatus;
135
145
  };
136
146
 
137
147
  async function samlPopupFlow(ssoURL: string) {
@@ -218,6 +228,7 @@ export const doSamlAuth = async (embedConfig: EmbedConfig) => {
218
228
  )}`;
219
229
 
220
230
  await doSSOAuth(embedConfig, ssoEndPoint);
231
+ return loggedInStatus;
221
232
  };
222
233
 
223
234
  export const doOIDCAuth = async (embedConfig: EmbedConfig) => {
@@ -234,13 +245,16 @@ export const doOIDCAuth = async (embedConfig: EmbedConfig) => {
234
245
  )}`;
235
246
 
236
247
  await doSSOAuth(embedConfig, ssoEndPoint);
248
+ return loggedInStatus;
237
249
  };
238
250
 
239
251
  /**
240
252
  * Perform authentication on the ThoughtSpot cluster
241
253
  * @param embedConfig The embed configuration
242
254
  */
243
- export const authenticate = async (embedConfig: EmbedConfig): Promise<void> => {
255
+ export const authenticate = async (
256
+ embedConfig: EmbedConfig,
257
+ ): Promise<boolean> => {
244
258
  const { authType } = embedConfig;
245
259
  switch (authType) {
246
260
  case AuthType.SSO:
@@ -252,7 +266,7 @@ export const authenticate = async (embedConfig: EmbedConfig): Promise<void> => {
252
266
  case AuthType.Basic:
253
267
  return doBasicAuth(embedConfig);
254
268
  default:
255
- return Promise.resolve();
269
+ return Promise.resolve(true);
256
270
  }
257
271
  };
258
272
 
package/src/embed/base.ts CHANGED
@@ -14,12 +14,12 @@ import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service';
14
14
 
15
15
  let config = {} as EmbedConfig;
16
16
 
17
- export let authPromise: Promise<void>;
17
+ export let authPromise: Promise<boolean>;
18
18
 
19
19
  /**
20
20
  * Perform authentication on the ThoughtSpot app as applicable.
21
21
  */
22
- export const handleAuth = (): Promise<void> => {
22
+ export const handleAuth = (): Promise<boolean> => {
23
23
  const authConfig = {
24
24
  ...config,
25
25
  thoughtSpotHost: getThoughtSpotHost(config),
@@ -30,7 +30,7 @@ export const handleAuth = (): Promise<void> => {
30
30
 
31
31
  export const getEmbedConfig = (): EmbedConfig => config;
32
32
 
33
- export const getAuthPromise = (): Promise<void> => authPromise;
33
+ export const getAuthPromise = (): Promise<boolean> => authPromise;
34
34
 
35
35
  /**
36
36
  * 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.
@@ -59,7 +59,7 @@ export const prefetch = (url?: string): void => {
59
59
  *
60
60
  * @returns authPromise Promise which resolves when authentication is complete.
61
61
  */
62
- export const init = (embedConfig: EmbedConfig): Promise<void> => {
62
+ export const init = (embedConfig: EmbedConfig): Promise<boolean> => {
63
63
  config = embedConfig;
64
64
  handleAuth();
65
65
 
@@ -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
  });
@@ -279,9 +279,10 @@ export class TsEmbed {
279
279
  * will be removed for ts7.oct.cl
280
280
  * @hidden
281
281
  */
282
- private formatEventData(event: MessageEvent) {
282
+ private formatEventData(event: MessageEvent, eventType: string) {
283
283
  const eventData = {
284
284
  ...event.data,
285
+ type: eventType,
285
286
  };
286
287
  if (!eventData.data) {
287
288
  eventData.data = event.data.payload;
@@ -299,7 +300,7 @@ export class TsEmbed {
299
300
  window.addEventListener('message', (event) => {
300
301
  const eventType = this.getEventType(event);
301
302
  const eventPort = this.getEventPort(event);
302
- const eventData = this.formatEventData(event);
303
+ const eventData = this.formatEventData(event, eventType);
303
304
  if (event.source === this.iFrame.contentWindow) {
304
305
  this.executeCallbacks(
305
306
  eventType,
@@ -457,12 +458,18 @@ export class TsEmbed {
457
458
  data: {
458
459
  timestamp: initTimestamp,
459
460
  },
461
+ type: EmbedEvent.Init,
460
462
  });
461
463
 
462
464
  uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_START);
463
465
 
464
466
  getAuthPromise()
465
- ?.then(() => {
467
+ ?.then((isLoggedIn: boolean) => {
468
+ if (!isLoggedIn) {
469
+ this.el.innerHTML = 'Login failed';
470
+ return;
471
+ }
472
+
466
473
  uploadMixpanelEvent(
467
474
  MIXPANEL_EVENT.VISUAL_SDK_RENDER_COMPLETE,
468
475
  );
@@ -505,6 +512,7 @@ export class TsEmbed {
505
512
  data: {
506
513
  timestamp: loadTimestamp,
507
514
  },
515
+ type: EmbedEvent.Load,
508
516
  });
509
517
  uploadMixpanelEvent(
510
518
  MIXPANEL_EVENT.VISUAL_SDK_IFRAME_LOAD_PERFORMANCE,
@@ -558,6 +566,8 @@ export class TsEmbed {
558
566
  eventPort?: MessagePort | void,
559
567
  ): void {
560
568
  const callbacks = this.eventHandlerMap.get(eventType) || [];
569
+ const allHandlers = this.eventHandlerMap.get(EmbedEvent.ALL) || [];
570
+ callbacks.push(...allHandlers);
561
571
  const dataStatus = data?.status || embedEventStatus.END;
562
572
  callbacks.forEach((callbackObj) => {
563
573
  if (
@@ -760,9 +770,10 @@ export class V1Embed extends TsEmbed {
760
770
  public on(
761
771
  messageType: EmbedEvent,
762
772
  callback: MessageCallback,
773
+ options: MessageOptions = { start: false },
763
774
  ): typeof TsEmbed.prototype {
764
775
  const eventType = this.getCompatibleEventType(messageType);
765
776
 
766
- return super.on(eventType, callback);
777
+ return super.on(eventType, callback, options);
767
778
  }
768
779
  }
package/src/types.ts CHANGED
@@ -416,6 +416,12 @@ export enum EmbedEvent {
416
416
  * @version 1.9.1 or later
417
417
  */
418
418
  LiveboardRendered = 'PinboardRendered',
419
+ /**
420
+ * This can be used to register an event listener which
421
+ * is triggered on all events.
422
+ * @version SDK: 1.10.0 | ThoughtSpot: any
423
+ */
424
+ ALL = '*',
419
425
  /**
420
426
  * Emitted when answer is saved in the app
421
427
  * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl
@@ -21,6 +21,7 @@ describe('Unit test for authService', () => {
21
21
  Promise.resolve({
22
22
  json: () => ({ success: true }),
23
23
  status: 200,
24
+ ok: true,
24
25
  }),
25
26
  );
26
27
  const response = await fetchSessionInfoService(authVerificationUrl);
@@ -32,6 +33,7 @@ describe('Unit test for authService', () => {
32
33
  global.fetch = jest.fn(() =>
33
34
  Promise.resolve({
34
35
  text: () => ({ success: true }),
36
+ ok: true,
35
37
  }),
36
38
  );
37
39
  const response = await fetchAuthTokenService(authEndpoint);
@@ -40,13 +42,17 @@ describe('Unit test for authService', () => {
40
42
  });
41
43
 
42
44
  test('fetchAuthService', async () => {
43
- global.fetch = jest.fn(() => Promise.resolve({ success: true }));
45
+ global.fetch = jest.fn(() =>
46
+ Promise.resolve({ success: true, ok: true }),
47
+ );
44
48
  await fetchAuthService(authVerificationUrl, username, authToken);
45
49
  expect(fetch).toBeCalled();
46
50
  });
47
51
 
48
52
  test('fetchBasicAuthService', async () => {
49
- global.fetch = jest.fn(() => Promise.resolve({ success: true }));
53
+ global.fetch = jest.fn(() =>
54
+ Promise.resolve({ success: true, ok: true }),
55
+ );
50
56
  await fetchBasicAuthService(thoughtSpotHost, username, password);
51
57
  expect(fetch).toBeCalled();
52
58
  });
@@ -1,10 +1,22 @@
1
1
  // eslint-disable-next-line import/no-cycle
2
2
  import { EndPoints } from '../auth';
3
3
 
4
+ function errorLoggedFetch(
5
+ url: string,
6
+ options: RequestInit,
7
+ ): Promise<Response> {
8
+ return fetch(url, options).then(async (r) => {
9
+ if (!r.ok) {
10
+ console.error('Failure', await r.json());
11
+ }
12
+ return r;
13
+ });
14
+ }
15
+
4
16
  export function fetchSessionInfoService(
5
17
  authVerificationUrl: string,
6
18
  ): Promise<any> {
7
- return fetch(authVerificationUrl, {
19
+ return errorLoggedFetch(authVerificationUrl, {
8
20
  credentials: 'include',
9
21
  });
10
22
  }
@@ -20,7 +32,7 @@ export async function fetchAuthService(
20
32
  username: string,
21
33
  authToken: string,
22
34
  ): Promise<any> {
23
- return fetch(
35
+ return errorLoggedFetch(
24
36
  `${thoughtSpotHost}${EndPoints.TOKEN_LOGIN}?username=${username}&auth_token=${authToken}`,
25
37
  {
26
38
  credentials: 'include',
@@ -33,7 +45,7 @@ export async function fetchBasicAuthService(
33
45
  username: string,
34
46
  password: string,
35
47
  ): Promise<any> {
36
- return fetch(`${thoughtSpotHost}${EndPoints.BASIC_LOGIN}`, {
48
+ return errorLoggedFetch(`${thoughtSpotHost}${EndPoints.BASIC_LOGIN}`, {
37
49
  method: 'POST',
38
50
  headers: {
39
51
  'content-type': 'application/x-www-form-urlencoded',
@@ -1,3 +0,0 @@
1
- import { SessionInterface } from '../types';
2
- declare function FetchAnswers(session: SessionInterface, query: string, operation: string, thoughtSpotHost: string): any;
3
- export default FetchAnswers;
@@ -1,49 +0,0 @@
1
- import { OperationType } from '../types';
2
- function FetchAnswers(session, query, operation, thoughtSpotHost) {
3
- let variable;
4
- const fetchQuery = async (variables) => {
5
- try {
6
- const response = await fetch(`${thoughtSpotHost}/prism/?op=${operation}`, {
7
- method: 'POST',
8
- headers: {
9
- 'content-type': 'application/json;charset=UTF-8',
10
- 'x-requested-by': 'ThoughtSpot',
11
- accept: '*/*',
12
- 'accept-language': 'en-us',
13
- },
14
- body: JSON.stringify({
15
- operationName: operation,
16
- query,
17
- variables,
18
- }),
19
- credentials: 'include'
20
- });
21
- const result = await response.json();
22
- return result.data;
23
- }
24
- catch (error) {
25
- return error;
26
- }
27
- };
28
- const getAnswer = (offset, batchSize) => {
29
- if (operation === OperationType.GetChartWithData) {
30
- variable = { batchSize, offset };
31
- }
32
- else {
33
- variable = {
34
- dataPaginationParams: {
35
- isClientPaginated: true,
36
- offset,
37
- size: batchSize,
38
- },
39
- };
40
- }
41
- return fetchQuery({
42
- session,
43
- ...variable,
44
- });
45
- };
46
- return getAnswer;
47
- }
48
- export default FetchAnswers;
49
- //# sourceMappingURL=fetchAnswers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fetchAnswers.js","sourceRoot":"","sources":["../../../src/utils/fetchAnswers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,aAAa,EAAE,MAAM,UAAU,CAAC;AAE3D,SAAS,YAAY,CACjB,OAAyB,EACzB,KAAa,EACb,SAAiB,EACjB,eAAuB;IAEvB,IAAI,QAAa,CAAC;IAElB,MAAM,UAAU,GAAG,KAAK,EAAE,SAAc,EAAE,EAAE;QACxC,IAAI;YACA,MAAM,QAAQ,GAAG,MAAM,KAAK,CACxB,GAAG,eAAe,cAAc,SAAS,EAAE,EAC3C;gBACI,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACL,cAAc,EAAE,gCAAgC;oBAChD,gBAAgB,EAAE,aAAa;oBAC/B,MAAM,EAAE,KAAK;oBACb,iBAAiB,EAAE,OAAO;iBAC7B;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACjB,aAAa,EAAE,SAAS;oBACxB,KAAK;oBACL,SAAS;iBACZ,CAAC;gBACF,WAAW,EAAE,SAAS;aACzB,CACA,CAAC;YACF,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACrC,OAAO,MAAM,CAAC,IAAI,CAAC;SACtB;QAAC,OAAO,KAAK,EAAE;YACZ,OAAO,KAAK,CAAC;SAChB;IACL,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,CAAC,MAAc,EAAE,SAAiB,EAAE,EAAE;QACpD,IAAI,SAAS,KAAK,aAAa,CAAC,gBAAgB,EAAE;YAC9C,QAAQ,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;SACpC;aAAM;YACH,QAAQ,GAAG;gBACP,oBAAoB,EAAE;oBAClB,iBAAiB,EAAE,IAAI;oBACvB,MAAM;oBACN,IAAI,EAAE,SAAS;iBAClB;aACJ,CAAC;SACL;QACD,OAAO,UAAU,CAAC;YACd,OAAO;YACP,GAAG,QAAQ;SACd,CAAC,CAAC;IACP,CAAC,CAAC;IAEF,OAAO,SAAS,CAAC;AACrB,CAAC;AAED,eAAe,YAAY,CAAC"}