@thoughtspot/visual-embed-sdk 1.29.0-alpha.3 → 1.29.0-alpha.authRefactor
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.
- package/cjs/package.json +1 -2
- package/cjs/src/embed/liveboard.js +1 -1
- package/cjs/src/embed/liveboard.js.map +1 -1
- package/cjs/src/embed/sage.d.ts +1 -1
- package/cjs/src/embed/ts-embed.d.ts.map +1 -1
- package/cjs/src/embed/ts-embed.js +0 -9
- package/cjs/src/embed/ts-embed.js.map +1 -1
- package/cjs/src/embed/ts-embed.spec.js +0 -18
- package/cjs/src/embed/ts-embed.spec.js.map +1 -1
- package/cjs/src/types.d.ts +1 -22
- package/cjs/src/types.d.ts.map +1 -1
- package/cjs/src/types.js +0 -3
- package/cjs/src/types.js.map +1 -1
- package/cjs/src/utils/sessionInfoService.d.ts +66 -0
- package/cjs/src/utils/sessionInfoService.d.ts.map +1 -0
- package/cjs/src/utils/sessionInfoService.js +92 -0
- package/cjs/src/utils/sessionInfoService.js.map +1 -0
- package/dist/src/embed/sage.d.ts +1 -1
- package/dist/src/embed/ts-embed.d.ts.map +1 -1
- package/dist/src/types.d.ts +1 -22
- package/dist/src/types.d.ts.map +1 -1
- package/dist/tsembed-react.es.js +2 -14
- package/dist/tsembed-react.js +2 -14
- package/dist/tsembed.es.js +2 -14
- package/dist/tsembed.js +2 -14
- package/dist/visual-embed-sdk-react-full.d.ts +2 -23
- package/dist/visual-embed-sdk-react.d.ts +2 -23
- package/dist/visual-embed-sdk.d.ts +2 -23
- package/lib/package.json +1 -2
- package/lib/src/embed/liveboard.js +1 -1
- package/lib/src/embed/liveboard.js.map +1 -1
- package/lib/src/embed/sage.d.ts +1 -1
- package/lib/src/embed/ts-embed.d.ts.map +1 -1
- package/lib/src/embed/ts-embed.js +0 -9
- package/lib/src/embed/ts-embed.js.map +1 -1
- package/lib/src/embed/ts-embed.spec.js +0 -18
- package/lib/src/embed/ts-embed.spec.js.map +1 -1
- package/lib/src/types.d.ts +1 -22
- package/lib/src/types.d.ts.map +1 -1
- package/lib/src/types.js +0 -3
- package/lib/src/types.js.map +1 -1
- package/lib/src/utils/sessionInfoService.d.ts +66 -0
- package/lib/src/utils/sessionInfoService.d.ts.map +1 -0
- package/lib/src/utils/sessionInfoService.js +85 -0
- package/lib/src/utils/sessionInfoService.js.map +1 -0
- package/lib/src/visual-embed-sdk.d.ts +2 -23
- package/package.json +1 -3
- package/src/auth.spec.ts +66 -72
- package/src/auth.ts +43 -59
- package/src/authToken.ts +10 -4
- package/src/embed/app.spec.ts +4 -2
- package/src/embed/base.spec.ts +1 -0
- package/src/embed/base.ts +3 -0
- package/src/embed/embed.spec.ts +2 -0
- package/src/embed/events.spec.ts +2 -0
- package/src/embed/liveboard.spec.ts +2 -0
- package/src/embed/pinboard.spec.ts +2 -0
- package/src/embed/sage.spec.ts +3 -0
- package/src/embed/sage.ts +1 -1
- package/src/embed/search.spec.ts +1 -0
- package/src/embed/ts-embed-trigger.spec.ts +3 -0
- package/src/embed/ts-embed.spec.ts +8 -22
- package/src/embed/ts-embed.ts +1 -9
- package/src/index.ts +2 -1
- package/src/mixpanel-service.spec.ts +4 -3
- package/src/mixpanel-service.ts +3 -1
- package/src/react/index.spec.tsx +7 -0
- package/src/react/index.tsx +1 -0
- package/src/types.ts +0 -21
- package/src/utils/authService/authService.spec.ts +9 -4
- package/src/utils/authService/authService.ts +1 -0
- package/src/utils/authService/tokenizedAuthService.ts +38 -8
- package/src/utils/processData.spec.ts +3 -2
- package/src/utils/processData.ts +1 -3
- package/src/utils/sessionInfoService.ts +101 -0
package/src/auth.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import EventEmitter from 'eventemitter3';
|
|
2
|
+
import { getAuthenticationToken, resetCachedAuthToken } from './authToken';
|
|
3
|
+
import { getEmbedConfig } from './embed/embedConfig';
|
|
2
4
|
import { initMixpanel } from './mixpanel-service';
|
|
3
5
|
import {
|
|
4
|
-
AuthType, DOMSelector, EmbedConfig, EmbedEvent,
|
|
6
|
+
AuthType, DOMSelector, EmbedConfig, EmbedEvent,
|
|
5
7
|
} from './types';
|
|
6
8
|
import { getDOMNode, getRedirectUrl } from './utils';
|
|
7
9
|
import {
|
|
8
|
-
|
|
10
|
+
EndPoints,
|
|
11
|
+
fetchAuthPostService,
|
|
9
12
|
fetchAuthService,
|
|
10
13
|
fetchBasicAuthService,
|
|
11
14
|
fetchLogoutService,
|
|
12
|
-
fetchAuthPostService,
|
|
13
|
-
EndPoints,
|
|
14
15
|
} from './utils/authService';
|
|
15
|
-
import {
|
|
16
|
+
import { isActiveService } from './utils/authService/tokenizedAuthService';
|
|
16
17
|
import { logger } from './utils/logger';
|
|
17
|
-
import {
|
|
18
|
+
import { getSessionInfo, getSessionInfoSync } from './utils/sessionInfoService';
|
|
18
19
|
|
|
19
20
|
// eslint-disable-next-line import/no-mutable-exports
|
|
20
21
|
export let loggedInStatus = false;
|
|
@@ -22,11 +23,7 @@ export let loggedInStatus = false;
|
|
|
22
23
|
export let samlAuthWindow: Window = null;
|
|
23
24
|
// eslint-disable-next-line import/no-mutable-exports
|
|
24
25
|
export let samlCompletionPromise: Promise<void> = null;
|
|
25
|
-
|
|
26
|
-
let sessionInfoResolver: (value: sessionInfoInterface) => void = null;
|
|
27
|
-
const sessionInfoPromise = new Promise((resolve: (value: sessionInfoInterface) => void) => {
|
|
28
|
-
sessionInfoResolver = resolve;
|
|
29
|
-
});
|
|
26
|
+
|
|
30
27
|
let releaseVersion = '';
|
|
31
28
|
|
|
32
29
|
export const SSO_REDIRECTION_MARKER_GUID = '5e16222e-ef02-43e9-9fbd-24226bf3ce5b';
|
|
@@ -185,6 +182,10 @@ export function notifyAuthSuccess(): void {
|
|
|
185
182
|
logger.error('SDK not initialized');
|
|
186
183
|
return;
|
|
187
184
|
}
|
|
185
|
+
const sessionInfo = getSessionInfoSync();
|
|
186
|
+
if (!sessionInfo) {
|
|
187
|
+
logger.error('Session info not available');
|
|
188
|
+
}
|
|
188
189
|
authEE.emit(AuthStatus.SUCCESS, sessionInfo);
|
|
189
190
|
}
|
|
190
191
|
|
|
@@ -211,70 +212,46 @@ export function notifyLogout(): void {
|
|
|
211
212
|
authEE.emit(AuthStatus.LOGOUT);
|
|
212
213
|
}
|
|
213
214
|
|
|
214
|
-
export const initSession = (sessionDetails: sessionInfoInterface) => {
|
|
215
|
-
const embedConfig = getEmbedConfig();
|
|
216
|
-
if (sessionInfo == null) {
|
|
217
|
-
sessionInfo = sessionDetails;
|
|
218
|
-
if (!embedConfig.disableSDKTracking) {
|
|
219
|
-
initMixpanel(sessionInfo);
|
|
220
|
-
}
|
|
221
|
-
sessionInfoResolver(sessionInfo);
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
export const getSessionDetails = (sessionInfoResp: any): sessionInfoInterface => {
|
|
226
|
-
const devMixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.devSdkKey;
|
|
227
|
-
const prodMixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.prodSdkKey;
|
|
228
|
-
const mixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.production
|
|
229
|
-
? prodMixpanelToken
|
|
230
|
-
: devMixpanelToken;
|
|
231
|
-
return {
|
|
232
|
-
userGUID: sessionInfoResp.userGUID,
|
|
233
|
-
mixpanelToken,
|
|
234
|
-
isPublicUser: sessionInfoResp.configInfo.isPublicUser,
|
|
235
|
-
releaseVersion: sessionInfoResp.releaseVersion,
|
|
236
|
-
clusterId: sessionInfoResp.configInfo.selfClusterId,
|
|
237
|
-
clusterName: sessionInfoResp.configInfo.selfClusterName,
|
|
238
|
-
...sessionInfoResp,
|
|
239
|
-
};
|
|
240
|
-
};
|
|
241
|
-
|
|
242
215
|
/**
|
|
243
216
|
* Check if we are logged into the ThoughtSpot cluster
|
|
244
217
|
*
|
|
245
218
|
* @param thoughtSpotHost The ThoughtSpot cluster hostname or IP
|
|
246
219
|
*/
|
|
247
220
|
async function isLoggedIn(thoughtSpotHost: string): Promise<boolean> {
|
|
248
|
-
const authVerificationUrl = `${thoughtSpotHost}${EndPoints.AUTH_VERIFICATION}`;
|
|
249
|
-
let response = null;
|
|
250
221
|
try {
|
|
251
|
-
response = await
|
|
252
|
-
|
|
253
|
-
const sessionDetails = getSessionDetails(sessionInfoResp);
|
|
254
|
-
// Store user session details from session info
|
|
255
|
-
initSession(sessionDetails);
|
|
256
|
-
releaseVersion = sessionInfoResp.releaseVersion;
|
|
222
|
+
const response = await isActiveService(thoughtSpotHost);
|
|
223
|
+
return response;
|
|
257
224
|
} catch (e) {
|
|
258
225
|
return false;
|
|
259
226
|
}
|
|
260
|
-
return response.status === 200;
|
|
261
227
|
}
|
|
262
228
|
|
|
263
229
|
/**
|
|
264
|
-
*
|
|
230
|
+
* Services to be called after the login is successful,
|
|
231
|
+
* This should be called after the cookie is set for cookie auth or
|
|
232
|
+
* after the token is set for cookieless.
|
|
233
|
+
*
|
|
234
|
+
* @return {Promise<void>}
|
|
235
|
+
* @example
|
|
236
|
+
* ```js
|
|
237
|
+
* await postLoginService();
|
|
238
|
+
* ```
|
|
239
|
+
* @version SDK: 1.28.3 | ThoughtSpot: *
|
|
265
240
|
*/
|
|
266
|
-
export function
|
|
267
|
-
|
|
241
|
+
export async function postLoginService(): Promise<void> {
|
|
242
|
+
const sessionInfo = await getSessionInfo();
|
|
243
|
+
releaseVersion = sessionInfo.releaseVersion;
|
|
244
|
+
const embedConfig = getEmbedConfig();
|
|
245
|
+
if (!embedConfig.disableSDKTracking) {
|
|
246
|
+
initMixpanel(sessionInfo);
|
|
247
|
+
}
|
|
268
248
|
}
|
|
269
249
|
|
|
270
250
|
/**
|
|
271
|
-
* Return
|
|
272
|
-
* authentication is successful. And info is available.
|
|
273
|
-
*
|
|
274
|
-
* @group Global methods
|
|
251
|
+
* Return releaseVersion if available
|
|
275
252
|
*/
|
|
276
|
-
export function
|
|
277
|
-
return
|
|
253
|
+
export function getReleaseVersion() {
|
|
254
|
+
return releaseVersion;
|
|
278
255
|
}
|
|
279
256
|
|
|
280
257
|
/**
|
|
@@ -309,8 +286,15 @@ export const doTokenAuth = async (embedConfig: EmbedConfig): Promise<boolean> =>
|
|
|
309
286
|
throw new Error('Either auth endpoint or getAuthToken function must be provided');
|
|
310
287
|
}
|
|
311
288
|
loggedInStatus = await isLoggedIn(thoughtSpotHost);
|
|
289
|
+
|
|
312
290
|
if (!loggedInStatus) {
|
|
313
|
-
|
|
291
|
+
let authToken: string;
|
|
292
|
+
try {
|
|
293
|
+
authToken = await getAuthenticationToken(embedConfig);
|
|
294
|
+
} catch (e) {
|
|
295
|
+
loggedInStatus = false;
|
|
296
|
+
throw e;
|
|
297
|
+
}
|
|
314
298
|
let resp;
|
|
315
299
|
try {
|
|
316
300
|
resp = await fetchAuthPostService(thoughtSpotHost, username, authToken);
|
|
@@ -492,7 +476,7 @@ export const logout = async (embedConfig: EmbedConfig): Promise<boolean> => {
|
|
|
492
476
|
const { thoughtSpotHost } = embedConfig;
|
|
493
477
|
await fetchLogoutService(thoughtSpotHost);
|
|
494
478
|
resetCachedAuthToken();
|
|
495
|
-
const thoughtspotIframes = document.querySelectorAll(
|
|
479
|
+
const thoughtspotIframes = document.querySelectorAll("[data-ts-iframe='true']");
|
|
496
480
|
if (thoughtspotIframes?.length) {
|
|
497
481
|
thoughtspotIframes.forEach((el) => {
|
|
498
482
|
el.parentElement.innerHTML = embedConfig.loginFailedMessage;
|
package/src/authToken.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EmbedConfig } from './types';
|
|
2
2
|
import { fetchAuthTokenService, verifyTokenService } from './utils/authService/authService';
|
|
3
|
+
import { logger } from './utils/logger';
|
|
3
4
|
|
|
4
5
|
const DUPLICATE_TOKEN_ERR = 'Duplicate token, please issue a new token every time getAuthToken callback is called.'
|
|
5
6
|
+ 'See https://developers.thoughtspot.com/docs/?pageid=embed-auth#trusted-auth-embed for more details.';
|
|
@@ -9,7 +10,7 @@ const INVALID_TOKEN_ERR = 'Invalid token received form token callback or authTok
|
|
|
9
10
|
let cachedAuthToken: string | null = null;
|
|
10
11
|
|
|
11
12
|
// This method can be used to get the authToken using the embedConfig
|
|
12
|
-
export
|
|
13
|
+
export async function getAuthenticationToken(embedConfig: EmbedConfig): Promise<string> {
|
|
13
14
|
if (cachedAuthToken) {
|
|
14
15
|
let isCachedTokenStillValid;
|
|
15
16
|
try {
|
|
@@ -31,12 +32,17 @@ export const getAuthenticationToken = async (embedConfig: EmbedConfig): Promise<
|
|
|
31
32
|
authToken = await response.text();
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
try {
|
|
36
|
+
// this will throw error if the token is not valid
|
|
37
|
+
await validateAuthToken(embedConfig, authToken);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
logger.error(`Received invalid token from getAuthToken callback or authToken endpoint. Error : ${e.message}`);
|
|
40
|
+
throw e;
|
|
41
|
+
}
|
|
36
42
|
|
|
37
43
|
cachedAuthToken = authToken;
|
|
38
44
|
return authToken;
|
|
39
|
-
}
|
|
45
|
+
}
|
|
40
46
|
|
|
41
47
|
const validateAuthToken = async (
|
|
42
48
|
embedConfig: EmbedConfig,
|
package/src/embed/app.spec.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { version } from '../../package.json';
|
|
|
19
19
|
import * as config from '../config';
|
|
20
20
|
import { TsEmbed, V1Embed } from './ts-embed';
|
|
21
21
|
import { logger } from '../utils/logger';
|
|
22
|
+
import * as auth from '../auth';
|
|
22
23
|
|
|
23
24
|
const defaultViewConfig = {
|
|
24
25
|
frameParams: {
|
|
@@ -35,6 +36,7 @@ beforeAll(() => {
|
|
|
35
36
|
thoughtSpotHost,
|
|
36
37
|
authType: AuthType.None,
|
|
37
38
|
});
|
|
39
|
+
jest.spyOn(auth, 'postLoginService').mockImplementation(() => Promise.resolve({}));
|
|
38
40
|
(window as any).ResizeObserver = window.ResizeObserver
|
|
39
41
|
|| jest.fn().mockImplementation(() => ({
|
|
40
42
|
disconnect: jest.fn(),
|
|
@@ -67,13 +69,13 @@ describe('App embed tests', () => {
|
|
|
67
69
|
test('should hide the primary nav bar', async () => {
|
|
68
70
|
const appEmbed = new AppEmbed(getRootEl(), {
|
|
69
71
|
...defaultViewConfig,
|
|
70
|
-
showPrimaryNavbar:
|
|
72
|
+
showPrimaryNavbar: false,
|
|
71
73
|
} as AppViewConfig);
|
|
72
74
|
appEmbed.render();
|
|
73
75
|
await executeAfterWait(() => {
|
|
74
76
|
expectUrlMatchesWithParams(
|
|
75
77
|
getIFrameSrc(),
|
|
76
|
-
`http://${thoughtSpotHost}/?embedApp=true&primaryNavHidden=
|
|
78
|
+
`http://${thoughtSpotHost}/?embedApp=true&primaryNavHidden=true&profileAndHelpInNavBarHidden=false${defaultParams}${defaultParamsPost}#/home`,
|
|
77
79
|
);
|
|
78
80
|
});
|
|
79
81
|
});
|
package/src/embed/base.spec.ts
CHANGED
package/src/embed/base.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
notifyLogout,
|
|
31
31
|
setAuthEE,
|
|
32
32
|
AuthEventEmitter,
|
|
33
|
+
postLoginService,
|
|
33
34
|
} from '../auth';
|
|
34
35
|
import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service';
|
|
35
36
|
import { getEmbedConfig, setEmbedConfig } from './embedConfig';
|
|
@@ -75,6 +76,8 @@ export const handleAuth = (): Promise<boolean> => {
|
|
|
75
76
|
if (!isLoggedIn) {
|
|
76
77
|
notifyAuthFailure(AuthFailureType.SDK);
|
|
77
78
|
} else {
|
|
79
|
+
// Post login service is called after successful login.
|
|
80
|
+
postLoginService();
|
|
78
81
|
notifyAuthSDKSuccess();
|
|
79
82
|
}
|
|
80
83
|
},
|
package/src/embed/embed.spec.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getIFrameEl,
|
|
10
10
|
getRootEl,
|
|
11
11
|
} from '../test/test-utils';
|
|
12
|
+
import * as authInstance from '../auth';
|
|
12
13
|
|
|
13
14
|
const thoughtSpotHost = 'tshost';
|
|
14
15
|
const defaultViewConfig = {
|
|
@@ -24,6 +25,7 @@ beforeAll(() => {
|
|
|
24
25
|
authType: AuthType.None,
|
|
25
26
|
});
|
|
26
27
|
spyOn(window, 'alert');
|
|
28
|
+
jest.spyOn(authInstance, 'postLoginService').mockResolvedValue(true);
|
|
27
29
|
});
|
|
28
30
|
|
|
29
31
|
describe('test view config', () => {
|
package/src/embed/events.spec.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
postMessageToParent,
|
|
21
21
|
} from '../test/test-utils';
|
|
22
22
|
import { LiveboardViewConfig } from './liveboard';
|
|
23
|
+
import * as authInstance from '../auth';
|
|
23
24
|
|
|
24
25
|
const thoughtSpotHost = 'tshost';
|
|
25
26
|
const defaultViewConfig = {
|
|
@@ -36,6 +37,7 @@ beforeAll(() => {
|
|
|
36
37
|
authType: AuthType.None,
|
|
37
38
|
});
|
|
38
39
|
spyOn(window, 'alert');
|
|
40
|
+
jest.spyOn(authInstance, 'postLoginService').mockReturnValue(true);
|
|
39
41
|
});
|
|
40
42
|
|
|
41
43
|
describe('test communication between host app and ThoughtSpot', () => {
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
} from '../test/test-utils';
|
|
24
24
|
import * as tsEmbed from './ts-embed';
|
|
25
25
|
import * as processTriggerInstance from '../utils/processTrigger';
|
|
26
|
+
import * as auth from '../auth';
|
|
26
27
|
|
|
27
28
|
const defaultViewConfig = {
|
|
28
29
|
frameParams: {
|
|
@@ -43,6 +44,7 @@ beforeAll(() => {
|
|
|
43
44
|
thoughtSpotHost,
|
|
44
45
|
authType: AuthType.None,
|
|
45
46
|
});
|
|
47
|
+
jest.spyOn(auth, 'postLoginService').mockImplementation(() => Promise.resolve({}));
|
|
46
48
|
});
|
|
47
49
|
|
|
48
50
|
describe('Liveboard/viz embed tests', () => {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
expectUrlMatchesWithParams,
|
|
14
14
|
} from '../test/test-utils';
|
|
15
15
|
import { version } from '../../package.json';
|
|
16
|
+
import * as auth from '../auth';
|
|
16
17
|
|
|
17
18
|
const defaultViewConfig = {
|
|
18
19
|
frameParams: {
|
|
@@ -30,6 +31,7 @@ beforeAll(() => {
|
|
|
30
31
|
thoughtSpotHost,
|
|
31
32
|
authType: AuthType.None,
|
|
32
33
|
});
|
|
34
|
+
jest.spyOn(auth, 'postLoginService').mockReturnValue(true);
|
|
33
35
|
});
|
|
34
36
|
|
|
35
37
|
describe('Pinboard/viz embed tests', () => {
|
package/src/embed/sage.spec.ts
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
getRootEl,
|
|
10
10
|
} from '../test/test-utils';
|
|
11
11
|
|
|
12
|
+
import * as authInstance from '../auth';
|
|
13
|
+
|
|
12
14
|
const defaultConfig: SageViewConfig = {
|
|
13
15
|
disableWorksheetChange: false,
|
|
14
16
|
hideWorksheetSelector: false,
|
|
@@ -27,6 +29,7 @@ beforeAll(() => {
|
|
|
27
29
|
authType: AuthType.None,
|
|
28
30
|
});
|
|
29
31
|
spyOn(window, 'alert');
|
|
32
|
+
jest.spyOn(authInstance, 'postLoginService').mockResolvedValue(true);
|
|
30
33
|
});
|
|
31
34
|
|
|
32
35
|
describe('Sage embed tests', () => {
|
package/src/embed/sage.ts
CHANGED
|
@@ -54,7 +54,7 @@ export interface SageViewConfig
|
|
|
54
54
|
showObjectResults?: boolean;
|
|
55
55
|
/**
|
|
56
56
|
* flag used by the TS product tour page to show the blue search bar
|
|
57
|
-
* even after the search is completed. This is different from
|
|
57
|
+
* even after the search is completed. This is different from TSE Sage Embed
|
|
58
58
|
* experience where it mimics closer to the non-embed case.
|
|
59
59
|
* The Sample questions container is collapsed when this value is set after
|
|
60
60
|
* does a search.
|
package/src/embed/search.spec.ts
CHANGED
|
@@ -8,9 +8,12 @@ import {
|
|
|
8
8
|
getRootEl,
|
|
9
9
|
} from '../test/test-utils';
|
|
10
10
|
|
|
11
|
+
import * as authInstance from '../auth';
|
|
12
|
+
|
|
11
13
|
describe('Trigger', () => {
|
|
12
14
|
beforeEach(() => {
|
|
13
15
|
document.body.innerHTML = getDocumentBody();
|
|
16
|
+
jest.spyOn(authInstance, 'postLoginService').mockResolvedValue(true);
|
|
14
17
|
});
|
|
15
18
|
test('should trigger the event', async (done) => {
|
|
16
19
|
init({
|
|
@@ -83,6 +83,10 @@ describe('Unit test case for ts embed', () => {
|
|
|
83
83
|
resetCachedAuthToken();
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
+
beforeAll(() => {
|
|
87
|
+
jest.spyOn(authInstance, 'postLoginService').mockResolvedValue(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
86
90
|
describe('AuthExpire embedEvent in cookieless authentication authType', () => {
|
|
87
91
|
beforeAll(() => {
|
|
88
92
|
jest.spyOn(authInstance, 'doCookielessTokenAuth').mockResolvedValueOnce(true);
|
|
@@ -680,6 +684,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
680
684
|
const mockPort: any = {
|
|
681
685
|
postMessage: jest.fn(),
|
|
682
686
|
};
|
|
687
|
+
const loggerSpy = jest.spyOn(logger, 'error').mockResolvedValueOnce(true);
|
|
683
688
|
await executeAfterWait(() => {
|
|
684
689
|
const iframe = getIFrameEl();
|
|
685
690
|
postMessageToParent(iframe.contentWindow, mockEmbedEventPayload, mockPort);
|
|
@@ -689,6 +694,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
689
694
|
expect(baseInstance.notifyAuthFailure).toBeCalledWith(
|
|
690
695
|
authInstance.AuthFailureType.EXPIRY,
|
|
691
696
|
);
|
|
697
|
+
expect(loggerSpy).toHaveBeenCalledTimes(1);
|
|
692
698
|
});
|
|
693
699
|
|
|
694
700
|
jest.spyOn(authService, 'verifyTokenService').mockClear();
|
|
@@ -707,6 +713,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
707
713
|
const searchEmbed = new SearchEmbed(getRootEl(), { ...defaultViewConfig, preRenderId: 'test' });
|
|
708
714
|
jest.spyOn(baseInstance, 'notifyAuthFailure');
|
|
709
715
|
searchEmbed.preRender();
|
|
716
|
+
const loggerSpy = jest.spyOn(logger, 'error').mockResolvedValueOnce(true);
|
|
710
717
|
const mockPort: any = {
|
|
711
718
|
postMessage: jest.fn(),
|
|
712
719
|
};
|
|
@@ -720,6 +727,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
720
727
|
expect(baseInstance.notifyAuthFailure).toBeCalledWith(
|
|
721
728
|
authInstance.AuthFailureType.EXPIRY,
|
|
722
729
|
);
|
|
730
|
+
expect(loggerSpy).toHaveBeenCalledTimes(1);
|
|
723
731
|
});
|
|
724
732
|
|
|
725
733
|
jest.spyOn(authService, 'verifyTokenService').mockClear();
|
|
@@ -1323,28 +1331,6 @@ describe('Unit test case for ts embed', () => {
|
|
|
1323
1331
|
done();
|
|
1324
1332
|
});
|
|
1325
1333
|
});
|
|
1326
|
-
|
|
1327
|
-
it('Should set the override locale for number/date and currency format', async () => {
|
|
1328
|
-
jest.spyOn(baseInstance, 'getAuthPromise').mockResolvedValue(true);
|
|
1329
|
-
init({
|
|
1330
|
-
thoughtSpotHost: 'tshost',
|
|
1331
|
-
authType: AuthType.None,
|
|
1332
|
-
numberFormatLocale: 'en-US',
|
|
1333
|
-
dateFormatLocale: 'en-IN',
|
|
1334
|
-
currencyFormat: 'USD',
|
|
1335
|
-
});
|
|
1336
|
-
const appEmbed = new AppEmbed(getRootEl(), {
|
|
1337
|
-
frameParams: {
|
|
1338
|
-
width: '100%',
|
|
1339
|
-
height: '100%',
|
|
1340
|
-
},
|
|
1341
|
-
});
|
|
1342
|
-
await appEmbed.render();
|
|
1343
|
-
expectUrlMatchesWithParams(
|
|
1344
|
-
getIFrameSrc(),
|
|
1345
|
-
`http://${thoughtSpotHost}/?embedApp=true&primaryNavHidden=true&profileAndHelpInNavBarHidden=false&numberFormatLocale=en-US&dateFormatLocale=en-IN¤cyFormat=USD${defaultParamsPost}#/home`,
|
|
1346
|
-
);
|
|
1347
|
-
});
|
|
1348
1334
|
});
|
|
1349
1335
|
|
|
1350
1336
|
describe('When destroyed', () => {
|
package/src/embed/ts-embed.ts
CHANGED
|
@@ -349,6 +349,7 @@ export class TsEmbed {
|
|
|
349
349
|
data: { authToken },
|
|
350
350
|
});
|
|
351
351
|
} catch (e) {
|
|
352
|
+
logger.error(`Received invalid token. Error : ${e?.message}`);
|
|
352
353
|
processAuthFailure(e, this.isPreRendered ? this.preRenderWrapper : this.el);
|
|
353
354
|
}
|
|
354
355
|
} else if (autoLogin) {
|
|
@@ -417,15 +418,6 @@ export class TsEmbed {
|
|
|
417
418
|
if (this.embedConfig.pendoTrackingKey) {
|
|
418
419
|
queryParams[Param.PendoTrackingKey] = this.embedConfig.pendoTrackingKey;
|
|
419
420
|
}
|
|
420
|
-
if (this.embedConfig.numberFormatLocale) {
|
|
421
|
-
queryParams[Param.NumberFormatLocale] = this.embedConfig.numberFormatLocale;
|
|
422
|
-
}
|
|
423
|
-
if (this.embedConfig.dateFormatLocale) {
|
|
424
|
-
queryParams[Param.DateFormatLocale] = this.embedConfig.dateFormatLocale;
|
|
425
|
-
}
|
|
426
|
-
if (this.embedConfig.currencyFormat) {
|
|
427
|
-
queryParams[Param.CurrencyFormat] = this.embedConfig.currencyFormat;
|
|
428
|
-
}
|
|
429
421
|
|
|
430
422
|
const {
|
|
431
423
|
disabledActions,
|
package/src/index.ts
CHANGED
|
@@ -21,8 +21,9 @@ import { PinboardEmbed, LiveboardViewConfig, LiveboardEmbed } from './embed/live
|
|
|
21
21
|
import { SearchEmbed, SearchViewConfig } from './embed/search';
|
|
22
22
|
import { SearchBarEmbed, SearchBarViewConfig } from './embed/search-bar';
|
|
23
23
|
import {
|
|
24
|
-
AuthFailureType, AuthStatus, AuthEvent, AuthEventEmitter,
|
|
24
|
+
AuthFailureType, AuthStatus, AuthEvent, AuthEventEmitter,
|
|
25
25
|
} from './auth';
|
|
26
|
+
import { getSessionInfo } from './utils/sessionInfoService';
|
|
26
27
|
import {
|
|
27
28
|
AuthType,
|
|
28
29
|
RuntimeFilter,
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
testResetMixpanel,
|
|
7
7
|
} from './mixpanel-service';
|
|
8
8
|
import { AuthType } from './types';
|
|
9
|
+
import { SessionInfo } from './utils/sessionInfoService';
|
|
9
10
|
|
|
10
11
|
const config = {
|
|
11
12
|
thoughtSpotHost: 'https://10.87.89.232',
|
|
@@ -29,7 +30,7 @@ describe('Unit test for mixpanel', () => {
|
|
|
29
30
|
mixpanelToken: 'abc123',
|
|
30
31
|
userGUID: '12345',
|
|
31
32
|
isPublicUser: false,
|
|
32
|
-
};
|
|
33
|
+
} as SessionInfo;
|
|
33
34
|
initMixpanel(sessionInfo);
|
|
34
35
|
expect(mixpanel.init).toHaveBeenCalledWith(sessionInfo.mixpanelToken, undefined, 'tsEmbed');
|
|
35
36
|
expect(mixpanel.identify).toHaveBeenCalledWith(sessionInfo.userGUID);
|
|
@@ -49,7 +50,7 @@ describe('Unit test for mixpanel', () => {
|
|
|
49
50
|
clusterId: 'newClusterId',
|
|
50
51
|
clusterName: 'newClusterName',
|
|
51
52
|
releaseVersion: 'newReleaseVersion',
|
|
52
|
-
};
|
|
53
|
+
} as SessionInfo;
|
|
53
54
|
initMixpanel(sessionInfo);
|
|
54
55
|
|
|
55
56
|
expect(mixpanel.init).toHaveBeenCalledWith(sessionInfo.mixpanelToken, undefined, 'tsEmbed');
|
|
@@ -74,7 +75,7 @@ describe('Unit test for mixpanel', () => {
|
|
|
74
75
|
mixpanelToken: 'abc123',
|
|
75
76
|
userGUID: '12345',
|
|
76
77
|
isPublicUser: false,
|
|
77
|
-
};
|
|
78
|
+
} as SessionInfo;
|
|
78
79
|
initMixpanel(sessionInfo);
|
|
79
80
|
expect(mixpanel.track).toHaveBeenCalledTimes(2);
|
|
80
81
|
});
|
package/src/mixpanel-service.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as mixpanel from 'mixpanel-browser';
|
|
2
2
|
import { logger } from './utils/logger';
|
|
3
|
+
import { SessionInfo } from './utils/sessionInfoService';
|
|
3
4
|
|
|
4
5
|
export const EndPoints = {
|
|
5
6
|
CONFIG: '/callosum/v1/system/config',
|
|
@@ -55,8 +56,9 @@ function emptyQueue() {
|
|
|
55
56
|
*
|
|
56
57
|
* @param sessionInfo
|
|
57
58
|
*/
|
|
58
|
-
export function initMixpanel(sessionInfo:
|
|
59
|
+
export function initMixpanel(sessionInfo: SessionInfo): void {
|
|
59
60
|
if (!sessionInfo || !sessionInfo.mixpanelToken) {
|
|
61
|
+
logger.error('Mixpanel token not found in session info');
|
|
60
62
|
return;
|
|
61
63
|
}
|
|
62
64
|
// On a public cluster the user is anonymous, so don't set the identify to
|
package/src/react/index.spec.tsx
CHANGED
|
@@ -24,6 +24,9 @@ import {
|
|
|
24
24
|
|
|
25
25
|
import { version } from '../../package.json';
|
|
26
26
|
|
|
27
|
+
import * as auth from '../auth';
|
|
28
|
+
import * as sessionService from '../utils/sessionInfoService';
|
|
29
|
+
|
|
27
30
|
const thoughtSpotHost = 'localhost';
|
|
28
31
|
|
|
29
32
|
beforeAll(() => {
|
|
@@ -31,6 +34,10 @@ beforeAll(() => {
|
|
|
31
34
|
thoughtSpotHost,
|
|
32
35
|
authType: AuthType.None,
|
|
33
36
|
});
|
|
37
|
+
jest.spyOn(auth, 'postLoginService').mockReturnValue(true);
|
|
38
|
+
jest.spyOn(sessionService, 'getSessionInfoSync').mockReturnValue({
|
|
39
|
+
userGUID: 'abcd',
|
|
40
|
+
});
|
|
34
41
|
spyOn(window, 'alert');
|
|
35
42
|
});
|
|
36
43
|
|
package/src/react/index.tsx
CHANGED
package/src/types.ts
CHANGED
|
@@ -592,24 +592,6 @@ export interface EmbedConfig {
|
|
|
592
592
|
* @version SDK: 1.27.9
|
|
593
593
|
*/
|
|
594
594
|
disableSDKTracking?: boolean;
|
|
595
|
-
/**
|
|
596
|
-
* Overrides default/user preffered locale for date formatting
|
|
597
|
-
*
|
|
598
|
-
* @version SDK: 1.28.4 | Thoughtspot: 10.0.0.cl, 9.5.0.sw
|
|
599
|
-
*/
|
|
600
|
-
dateFormatLocale?: string;
|
|
601
|
-
/**
|
|
602
|
-
* Overrides default/user preffered locale for number formatting
|
|
603
|
-
*
|
|
604
|
-
* @version SDK: 1.28.4 | Thoughtspot: 10.0.0.cl, 9.5.0.sw
|
|
605
|
-
*/
|
|
606
|
-
numberFormatLocale?: string;
|
|
607
|
-
/**
|
|
608
|
-
* Format to be used for currency when currency format is set to infer from browser
|
|
609
|
-
*
|
|
610
|
-
* @version SDK: 1.28.4 | Thoughtspot: 10.0.0.cl, 9.5.0.sw
|
|
611
|
-
*/
|
|
612
|
-
currencyFormat?: string;
|
|
613
595
|
}
|
|
614
596
|
|
|
615
597
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
@@ -3101,9 +3083,6 @@ export enum Param {
|
|
|
3101
3083
|
OverrideNativeConsole = 'overrideConsoleLogs',
|
|
3102
3084
|
enableAskSage = 'enableAskSage',
|
|
3103
3085
|
CollapseSearchBarInitially= 'collapseSearchBarInitially',
|
|
3104
|
-
DateFormatLocale = 'dateFormatLocale',
|
|
3105
|
-
NumberFormatLocale = 'numberFormatLocale',
|
|
3106
|
-
CurrencyFormat = 'currencyFormat',
|
|
3107
3086
|
}
|
|
3108
3087
|
|
|
3109
3088
|
/**
|
|
@@ -26,9 +26,10 @@ describe('Unit test for authService', () => {
|
|
|
26
26
|
status: 200,
|
|
27
27
|
ok: true,
|
|
28
28
|
}));
|
|
29
|
-
const response = await fetchSessionInfoService(
|
|
30
|
-
expect(response.
|
|
29
|
+
const response = await fetchSessionInfoService(thoughtSpotHost);
|
|
30
|
+
expect(response.success).toBe(true);
|
|
31
31
|
expect(fetch).toHaveBeenCalledTimes(1);
|
|
32
|
+
expect(fetch).toBeCalledWith(`${thoughtSpotHost}${EndPoints.SESSION_INFO}`, {});
|
|
32
33
|
});
|
|
33
34
|
|
|
34
35
|
test('fetchAuthTokenService', async () => {
|
|
@@ -108,8 +109,12 @@ describe('Unit test for authService', () => {
|
|
|
108
109
|
status: 500,
|
|
109
110
|
ok: false,
|
|
110
111
|
}));
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
try {
|
|
113
|
+
await fetchSessionInfoService(authVerificationUrl);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
expect(e.message).toContain('Failed to fetch session info');
|
|
116
|
+
}
|
|
117
|
+
expect(logger.error).toHaveBeenCalledWith('Failed to fetch http://localhost:3000/callosum/v1/session/info', 'error');
|
|
113
118
|
});
|
|
114
119
|
|
|
115
120
|
test('verifyTokenService', async () => {
|
|
@@ -2,6 +2,7 @@ import { logger } from '../logger';
|
|
|
2
2
|
|
|
3
3
|
export const EndPoints = {
|
|
4
4
|
AUTH_VERIFICATION: '/callosum/v1/session/info',
|
|
5
|
+
SESSION_INFO: '/callosum/v1/session/info',
|
|
5
6
|
SAML_LOGIN_TEMPLATE: (targetUrl: string) => `/callosum/v1/saml/login?targetURLPath=${targetUrl}`,
|
|
6
7
|
OIDC_LOGIN_TEMPLATE: (targetUrl: string) => `/callosum/v1/oidc/login?targetURLPath=${targetUrl}`,
|
|
7
8
|
TOKEN_LOGIN: '/callosum/v1/session/login/token',
|