@thoughtspot/visual-embed-sdk 1.42.0 → 1.42.1-alpha.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.
- package/cjs/package.json +2 -2
- package/cjs/src/api-intercept.d.ts +27 -0
- package/cjs/src/api-intercept.d.ts.map +1 -0
- package/cjs/src/api-intercept.js +115 -0
- package/cjs/src/api-intercept.js.map +1 -0
- package/cjs/src/api-intercept.spec.d.ts +2 -0
- package/cjs/src/api-intercept.spec.d.ts.map +1 -0
- package/cjs/src/api-intercept.spec.js +122 -0
- package/cjs/src/api-intercept.spec.js.map +1 -0
- package/cjs/src/embed/app.d.ts.map +1 -1
- package/cjs/src/embed/app.js +7 -2
- package/cjs/src/embed/app.js.map +1 -1
- package/cjs/src/embed/app.spec.js +20 -0
- package/cjs/src/embed/app.spec.js.map +1 -1
- package/cjs/src/embed/hostEventClient/contracts.d.ts +11 -1
- package/cjs/src/embed/hostEventClient/contracts.d.ts.map +1 -1
- package/cjs/src/embed/hostEventClient/contracts.js +1 -0
- package/cjs/src/embed/hostEventClient/contracts.js.map +1 -1
- package/cjs/src/embed/liveboard.d.ts.map +1 -1
- package/cjs/src/embed/liveboard.js +4 -1
- package/cjs/src/embed/liveboard.js.map +1 -1
- package/cjs/src/embed/liveboard.spec.js +22 -0
- package/cjs/src/embed/liveboard.spec.js.map +1 -1
- package/cjs/src/embed/search.d.ts.map +1 -1
- package/cjs/src/embed/search.js +3 -1
- package/cjs/src/embed/search.js.map +1 -1
- package/cjs/src/embed/ts-embed.d.ts +15 -0
- package/cjs/src/embed/ts-embed.d.ts.map +1 -1
- package/cjs/src/embed/ts-embed.js +94 -26
- package/cjs/src/embed/ts-embed.js.map +1 -1
- package/cjs/src/embed/ts-embed.spec.d.ts.map +1 -1
- package/cjs/src/embed/ts-embed.spec.js +168 -0
- package/cjs/src/embed/ts-embed.spec.js.map +1 -1
- package/cjs/src/errors.d.ts +1 -0
- package/cjs/src/errors.d.ts.map +1 -1
- package/cjs/src/errors.js +1 -0
- package/cjs/src/errors.js.map +1 -1
- package/cjs/src/index.d.ts +2 -2
- package/cjs/src/index.d.ts.map +1 -1
- package/cjs/src/index.js +2 -1
- package/cjs/src/index.js.map +1 -1
- package/cjs/src/react/all-types-export.d.ts +1 -1
- package/cjs/src/react/all-types-export.d.ts.map +1 -1
- package/cjs/src/react/all-types-export.js +2 -1
- package/cjs/src/react/all-types-export.js.map +1 -1
- package/cjs/src/types.d.ts +100 -4
- package/cjs/src/types.d.ts.map +1 -1
- package/cjs/src/types.js +39 -1
- package/cjs/src/types.js.map +1 -1
- package/cjs/src/utils/processData.d.ts +1 -1
- package/cjs/src/utils/processData.d.ts.map +1 -1
- package/cjs/src/utils/processData.js +8 -8
- package/cjs/src/utils/processData.js.map +1 -1
- package/dist/index-BCC3Z072.js +7371 -0
- package/dist/index-BEzW4MDA.js +7371 -0
- package/dist/{index-BpSohedu.js → index-DvNA626T.js} +1 -1
- package/dist/src/api-intercept.d.ts +27 -0
- package/dist/src/api-intercept.d.ts.map +1 -0
- package/dist/src/api-intercept.spec.d.ts +2 -0
- package/dist/src/api-intercept.spec.d.ts.map +1 -0
- package/dist/src/embed/app.d.ts.map +1 -1
- package/dist/src/embed/hostEventClient/contracts.d.ts +11 -1
- package/dist/src/embed/hostEventClient/contracts.d.ts.map +1 -1
- package/dist/src/embed/liveboard.d.ts.map +1 -1
- package/dist/src/embed/search.d.ts.map +1 -1
- package/dist/src/embed/ts-embed.d.ts +15 -0
- package/dist/src/embed/ts-embed.d.ts.map +1 -1
- package/dist/src/embed/ts-embed.spec.d.ts.map +1 -1
- package/dist/src/errors.d.ts +1 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/react/all-types-export.d.ts +1 -1
- package/dist/src/react/all-types-export.d.ts.map +1 -1
- package/dist/src/types.d.ts +100 -4
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/processData.d.ts +1 -1
- package/dist/src/utils/processData.d.ts.map +1 -1
- package/dist/tsembed-react.es.js +266 -51
- package/dist/tsembed-react.js +265 -50
- package/dist/tsembed.es.js +267 -52
- package/dist/tsembed.js +265 -50
- package/dist/visual-embed-sdk-react-full.d.ts +124 -4
- package/dist/visual-embed-sdk-react.d.ts +121 -4
- package/dist/visual-embed-sdk.d.ts +124 -4
- package/lib/package.json +2 -2
- package/lib/src/api-intercept.d.ts +27 -0
- package/lib/src/api-intercept.d.ts.map +1 -0
- package/lib/src/api-intercept.js +108 -0
- package/lib/src/api-intercept.js.map +1 -0
- package/lib/src/api-intercept.spec.d.ts +2 -0
- package/lib/src/api-intercept.spec.d.ts.map +1 -0
- package/lib/src/api-intercept.spec.js +119 -0
- package/lib/src/api-intercept.spec.js.map +1 -0
- package/lib/src/embed/app.d.ts.map +1 -1
- package/lib/src/embed/app.js +7 -2
- package/lib/src/embed/app.js.map +1 -1
- package/lib/src/embed/app.spec.js +20 -0
- package/lib/src/embed/app.spec.js.map +1 -1
- package/lib/src/embed/hostEventClient/contracts.d.ts +11 -1
- package/lib/src/embed/hostEventClient/contracts.d.ts.map +1 -1
- package/lib/src/embed/hostEventClient/contracts.js +1 -0
- package/lib/src/embed/hostEventClient/contracts.js.map +1 -1
- package/lib/src/embed/liveboard.d.ts.map +1 -1
- package/lib/src/embed/liveboard.js +4 -1
- package/lib/src/embed/liveboard.js.map +1 -1
- package/lib/src/embed/liveboard.spec.js +22 -0
- package/lib/src/embed/liveboard.spec.js.map +1 -1
- package/lib/src/embed/search.d.ts.map +1 -1
- package/lib/src/embed/search.js +3 -1
- package/lib/src/embed/search.js.map +1 -1
- package/lib/src/embed/ts-embed.d.ts +15 -0
- package/lib/src/embed/ts-embed.d.ts.map +1 -1
- package/lib/src/embed/ts-embed.js +94 -26
- package/lib/src/embed/ts-embed.js.map +1 -1
- package/lib/src/embed/ts-embed.spec.d.ts.map +1 -1
- package/lib/src/embed/ts-embed.spec.js +168 -0
- package/lib/src/embed/ts-embed.spec.js.map +1 -1
- package/lib/src/errors.d.ts +1 -0
- package/lib/src/errors.d.ts.map +1 -1
- package/lib/src/errors.js +1 -0
- package/lib/src/errors.js.map +1 -1
- package/lib/src/index.d.ts +2 -2
- package/lib/src/index.d.ts.map +1 -1
- package/lib/src/index.js +2 -2
- package/lib/src/index.js.map +1 -1
- package/lib/src/react/all-types-export.d.ts +1 -1
- package/lib/src/react/all-types-export.d.ts.map +1 -1
- package/lib/src/react/all-types-export.js +1 -1
- package/lib/src/react/all-types-export.js.map +1 -1
- package/lib/src/types.d.ts +100 -4
- package/lib/src/types.d.ts.map +1 -1
- package/lib/src/types.js +38 -0
- package/lib/src/types.js.map +1 -1
- package/lib/src/utils/processData.d.ts +1 -1
- package/lib/src/utils/processData.d.ts.map +1 -1
- package/lib/src/utils/processData.js +8 -8
- package/lib/src/utils/processData.js.map +1 -1
- package/package.json +2 -2
- package/src/api-intercept.spec.ts +147 -0
- package/src/api-intercept.ts +134 -0
- package/src/embed/app.spec.ts +28 -0
- package/src/embed/app.ts +9 -1
- package/src/embed/hostEventClient/contracts.ts +10 -0
- package/src/embed/liveboard.spec.ts +30 -0
- package/src/embed/liveboard.ts +5 -0
- package/src/embed/search.ts +3 -1
- package/src/embed/ts-embed.spec.ts +221 -5
- package/src/embed/ts-embed.ts +129 -43
- package/src/errors.ts +1 -0
- package/src/index.ts +2 -0
- package/src/react/all-types-export.ts +1 -0
- package/src/types.ts +102 -3
- package/src/utils/processData.ts +11 -11
|
@@ -61,6 +61,14 @@ import { UIPassthroughEvent } from './hostEventClient/contracts';
|
|
|
61
61
|
import * as sessionInfoService from '../utils/sessionInfoService';
|
|
62
62
|
import * as authToken from '../authToken';
|
|
63
63
|
|
|
64
|
+
// Mock api-intercept by default to avoid altering existing APP_INIT payload
|
|
65
|
+
// expectations
|
|
66
|
+
jest.mock('../api-intercept', () => ({
|
|
67
|
+
getInterceptInitData: jest.fn(() => ({})),
|
|
68
|
+
handleInterceptEvent: jest.fn(),
|
|
69
|
+
processLegacyInterceptResponse: jest.fn((x: any) => x),
|
|
70
|
+
}));
|
|
71
|
+
|
|
64
72
|
jest.mock('../utils/processTrigger');
|
|
65
73
|
|
|
66
74
|
const mockProcessTrigger = processTrigger as jest.Mock;
|
|
@@ -1045,7 +1053,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
1045
1053
|
type: EmbedEvent.APP_INIT,
|
|
1046
1054
|
data: {},
|
|
1047
1055
|
};
|
|
1048
|
-
|
|
1056
|
+
|
|
1049
1057
|
// Create a SearchEmbed with valid custom actions to test
|
|
1050
1058
|
// CustomActionsValidationResult
|
|
1051
1059
|
const searchEmbed = new SearchEmbed(getRootEl(), {
|
|
@@ -1067,7 +1075,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
1067
1075
|
}
|
|
1068
1076
|
]
|
|
1069
1077
|
});
|
|
1070
|
-
|
|
1078
|
+
|
|
1071
1079
|
searchEmbed.render();
|
|
1072
1080
|
const mockPort: any = {
|
|
1073
1081
|
postMessage: jest.fn(),
|
|
@@ -1116,7 +1124,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
1116
1124
|
customVariablesForThirdPartyTools: {},
|
|
1117
1125
|
},
|
|
1118
1126
|
});
|
|
1119
|
-
|
|
1127
|
+
|
|
1120
1128
|
// Verify that CustomActionsValidationResult structure is
|
|
1121
1129
|
// correct
|
|
1122
1130
|
const appInitData = mockPort.postMessage.mock.calls[0][0].data;
|
|
@@ -1137,7 +1145,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
1137
1145
|
})
|
|
1138
1146
|
])
|
|
1139
1147
|
);
|
|
1140
|
-
|
|
1148
|
+
|
|
1141
1149
|
// Verify actions are sorted by name (alphabetically)
|
|
1142
1150
|
expect(appInitData.customActions[0].name).toBe('Another Valid Action');
|
|
1143
1151
|
expect(appInitData.customActions[1].name).toBe('Valid Action');
|
|
@@ -2488,7 +2496,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
2488
2496
|
});
|
|
2489
2497
|
|
|
2490
2498
|
afterAll((): void => {
|
|
2491
|
-
window.location = location
|
|
2499
|
+
(window.location as any) = location;
|
|
2492
2500
|
});
|
|
2493
2501
|
|
|
2494
2502
|
it('get url params for TS', () => {
|
|
@@ -3345,4 +3353,212 @@ describe('Unit test case for ts embed', () => {
|
|
|
3345
3353
|
expect(searchEmbed.isEmbedContainerLoaded).toBe(true);
|
|
3346
3354
|
});
|
|
3347
3355
|
});
|
|
3356
|
+
|
|
3357
|
+
describe('Online event listener registration after auth failure', () => {
|
|
3358
|
+
beforeAll(() => {
|
|
3359
|
+
init({
|
|
3360
|
+
thoughtSpotHost: 'tshost',
|
|
3361
|
+
authType: AuthType.None,
|
|
3362
|
+
loginFailedMessage: 'Not logged in',
|
|
3363
|
+
});
|
|
3364
|
+
});
|
|
3365
|
+
|
|
3366
|
+
test('should register online event listener when authentication fails', async () => {
|
|
3367
|
+
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
|
|
3368
|
+
jest.spyOn(baseInstance, 'getAuthPromise').mockRejectedValueOnce(
|
|
3369
|
+
new Error('Auth failed'),
|
|
3370
|
+
);
|
|
3371
|
+
const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
|
|
3372
|
+
addEventListenerSpy.mockClear();
|
|
3373
|
+
await searchEmbed.render();
|
|
3374
|
+
await executeAfterWait(() => {
|
|
3375
|
+
expect(getRootEl().innerHTML).toContain('Not logged in');
|
|
3376
|
+
const onlineListenerCalls = addEventListenerSpy.mock.calls.filter(
|
|
3377
|
+
(call) => call[0] === 'online',
|
|
3378
|
+
);
|
|
3379
|
+
expect(onlineListenerCalls).toHaveLength(1);
|
|
3380
|
+
const offlineListenerCalls = addEventListenerSpy.mock.calls.filter(
|
|
3381
|
+
(call) => call[0] === 'offline',
|
|
3382
|
+
);
|
|
3383
|
+
expect(offlineListenerCalls).toHaveLength(1);
|
|
3384
|
+
const messageListenerCalls = addEventListenerSpy.mock.calls.filter(
|
|
3385
|
+
(call) => call[0] === 'message',
|
|
3386
|
+
);
|
|
3387
|
+
expect(messageListenerCalls).toHaveLength(0);
|
|
3388
|
+
});
|
|
3389
|
+
|
|
3390
|
+
addEventListenerSpy.mockRestore();
|
|
3391
|
+
});
|
|
3392
|
+
|
|
3393
|
+
test('should attempt to trigger reload when online event occurs after auth failure', async () => {
|
|
3394
|
+
jest.spyOn(baseInstance, 'getAuthPromise').mockRejectedValueOnce(
|
|
3395
|
+
new Error('Auth failed'),
|
|
3396
|
+
);
|
|
3397
|
+
const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
|
|
3398
|
+
const triggerSpy = jest.spyOn(searchEmbed, 'trigger').mockResolvedValue(null);
|
|
3399
|
+
await searchEmbed.render();
|
|
3400
|
+
|
|
3401
|
+
await executeAfterWait(() => {
|
|
3402
|
+
expect(getRootEl().innerHTML).toContain('Not logged in');
|
|
3403
|
+
triggerSpy.mockClear();
|
|
3404
|
+
const onlineEvent = new Event('online');
|
|
3405
|
+
window.dispatchEvent(onlineEvent);
|
|
3406
|
+
expect(triggerSpy).toHaveBeenCalledWith(HostEvent.Reload);
|
|
3407
|
+
});
|
|
3408
|
+
|
|
3409
|
+
triggerSpy.mockReset();
|
|
3410
|
+
});
|
|
3411
|
+
|
|
3412
|
+
test('should handle online event gracefully when no iframe exists', async () => {
|
|
3413
|
+
jest.spyOn(baseInstance, 'getAuthPromise').mockRejectedValueOnce(
|
|
3414
|
+
new Error('Auth failed'),
|
|
3415
|
+
);
|
|
3416
|
+
const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
|
|
3417
|
+
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
3418
|
+
await searchEmbed.render();
|
|
3419
|
+
await executeAfterWait(() => {
|
|
3420
|
+
expect(getRootEl().innerHTML).toContain('Not logged in');
|
|
3421
|
+
const onlineEvent = new Event('online');
|
|
3422
|
+
expect(() => {
|
|
3423
|
+
window.dispatchEvent(onlineEvent);
|
|
3424
|
+
}).not.toThrow();
|
|
3425
|
+
});
|
|
3426
|
+
|
|
3427
|
+
errorSpy.mockReset();
|
|
3428
|
+
});
|
|
3429
|
+
|
|
3430
|
+
test('should register all event listeners when authentication succeeds', async () => {
|
|
3431
|
+
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
|
|
3432
|
+
jest.spyOn(baseInstance, 'getAuthPromise').mockResolvedValueOnce(true);
|
|
3433
|
+
const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
|
|
3434
|
+
addEventListenerSpy.mockClear();
|
|
3435
|
+
await searchEmbed.render();
|
|
3436
|
+
await executeAfterWait(() => {
|
|
3437
|
+
const onlineListenerCalls = addEventListenerSpy.mock.calls.filter(
|
|
3438
|
+
(call) => call[0] === 'online',
|
|
3439
|
+
);
|
|
3440
|
+
expect(onlineListenerCalls).toHaveLength(1);
|
|
3441
|
+
const offlineListenerCalls = addEventListenerSpy.mock.calls.filter(
|
|
3442
|
+
(call) => call[0] === 'offline',
|
|
3443
|
+
);
|
|
3444
|
+
expect(offlineListenerCalls).toHaveLength(1);
|
|
3445
|
+
const messageListenerCalls = addEventListenerSpy.mock.calls.filter(
|
|
3446
|
+
(call) => call[0] === 'message',
|
|
3447
|
+
);
|
|
3448
|
+
expect(messageListenerCalls).toHaveLength(1);
|
|
3449
|
+
});
|
|
3450
|
+
|
|
3451
|
+
addEventListenerSpy.mockRestore();
|
|
3452
|
+
});
|
|
3453
|
+
test('should successfully trigger reload when online event occurs after auth success', async () => {
|
|
3454
|
+
jest.spyOn(baseInstance, 'getAuthPromise').mockResolvedValueOnce(true);
|
|
3455
|
+
const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
|
|
3456
|
+
const triggerSpy = jest.spyOn(searchEmbed, 'trigger').mockResolvedValue({} as any);
|
|
3457
|
+
await searchEmbed.render();
|
|
3458
|
+
await executeAfterWait(() => {
|
|
3459
|
+
triggerSpy.mockClear();
|
|
3460
|
+
const onlineEvent = new Event('online');
|
|
3461
|
+
window.dispatchEvent(onlineEvent);
|
|
3462
|
+
expect(triggerSpy).toHaveBeenCalledWith(HostEvent.Reload);
|
|
3463
|
+
});
|
|
3464
|
+
triggerSpy.mockReset();
|
|
3465
|
+
});
|
|
3466
|
+
});
|
|
3467
|
+
});
|
|
3468
|
+
|
|
3469
|
+
describe('API Intercept integration in TsEmbed', () => {
|
|
3470
|
+
beforeEach(() => {
|
|
3471
|
+
document.body.innerHTML = getDocumentBody();
|
|
3472
|
+
jest.clearAllMocks();
|
|
3473
|
+
});
|
|
3474
|
+
|
|
3475
|
+
test('APP_INIT includes API intercept init data when enabled', async () => {
|
|
3476
|
+
init({
|
|
3477
|
+
thoughtSpotHost: 'tshost',
|
|
3478
|
+
authType: AuthType.None,
|
|
3479
|
+
enableApiIntercept: true,
|
|
3480
|
+
interceptTimeout: 1234,
|
|
3481
|
+
} as any);
|
|
3482
|
+
|
|
3483
|
+
// Include intercept setup via viewConfig too
|
|
3484
|
+
const { InterceptedApiType } = require('../types');
|
|
3485
|
+
|
|
3486
|
+
// Override mocked getInterceptInitData for this test only
|
|
3487
|
+
const apiInterceptModule = require('../api-intercept');
|
|
3488
|
+
apiInterceptModule.getInterceptInitData.mockReturnValue({
|
|
3489
|
+
enableApiIntercept: true,
|
|
3490
|
+
interceptTimeout: 1234,
|
|
3491
|
+
interceptUrls: [
|
|
3492
|
+
'http://tshost/prism/?op=CreateAnswerSession',
|
|
3493
|
+
'http://tshost/prism/?op=GetV2SourceDetail',
|
|
3494
|
+
'http://tshost/custom/path',
|
|
3495
|
+
],
|
|
3496
|
+
});
|
|
3497
|
+
const searchEmbed = new SearchEmbed(getRootEl(), {
|
|
3498
|
+
...defaultViewConfig,
|
|
3499
|
+
enableApiIntercept: true,
|
|
3500
|
+
interceptUrls: [InterceptedApiType.METADATA, '/custom/path'],
|
|
3501
|
+
} as any);
|
|
3502
|
+
|
|
3503
|
+
searchEmbed.render();
|
|
3504
|
+
|
|
3505
|
+
const mockPort: any = { postMessage: jest.fn() };
|
|
3506
|
+
const mockEmbedEventPayload = { type: EmbedEvent.APP_INIT, data: {} };
|
|
3507
|
+
|
|
3508
|
+
await executeAfterWait(() => {
|
|
3509
|
+
const iframe = getIFrameEl();
|
|
3510
|
+
postMessageToParent(iframe.contentWindow, mockEmbedEventPayload, mockPort);
|
|
3511
|
+
});
|
|
3512
|
+
|
|
3513
|
+
await executeAfterWait(() => {
|
|
3514
|
+
expect(mockPort.postMessage).toHaveBeenCalledWith(
|
|
3515
|
+
expect.objectContaining({
|
|
3516
|
+
type: EmbedEvent.APP_INIT,
|
|
3517
|
+
data: expect.objectContaining({
|
|
3518
|
+
enableApiIntercept: true,
|
|
3519
|
+
interceptTimeout: 1234,
|
|
3520
|
+
interceptUrls: expect.arrayContaining([
|
|
3521
|
+
expect.stringContaining('CreateAnswerSession'),
|
|
3522
|
+
expect.stringContaining('GetV2SourceDetail'),
|
|
3523
|
+
expect.stringContaining('custom/path'),
|
|
3524
|
+
]),
|
|
3525
|
+
}),
|
|
3526
|
+
}),
|
|
3527
|
+
);
|
|
3528
|
+
});
|
|
3529
|
+
});
|
|
3530
|
+
|
|
3531
|
+
test('ApiIntercept event delegates to handleInterceptEvent and bypasses default callbacks', async () => {
|
|
3532
|
+
init({ thoughtSpotHost: 'tshost', authType: AuthType.None } as any);
|
|
3533
|
+
|
|
3534
|
+
const apiInterceptModule = require('../api-intercept');
|
|
3535
|
+
const handleInterceptSpy = jest.spyOn(apiInterceptModule, 'handleInterceptEvent').mockResolvedValue(undefined);
|
|
3536
|
+
|
|
3537
|
+
const searchEmbed = new SearchEmbed(getRootEl(), {
|
|
3538
|
+
...defaultViewConfig,
|
|
3539
|
+
enableApiIntercept: true,
|
|
3540
|
+
} as any);
|
|
3541
|
+
|
|
3542
|
+
const apiCb = jest.fn();
|
|
3543
|
+
searchEmbed.on(EmbedEvent.ApiIntercept as any, apiCb as any);
|
|
3544
|
+
|
|
3545
|
+
searchEmbed.render();
|
|
3546
|
+
|
|
3547
|
+
const mockPort: any = { postMessage: jest.fn() };
|
|
3548
|
+
const eventPayload = {
|
|
3549
|
+
type: EmbedEvent.ApiIntercept,
|
|
3550
|
+
data: JSON.stringify({ input: '/prism/?op=GetChartWithData', init: { body: '{}' } }),
|
|
3551
|
+
};
|
|
3552
|
+
|
|
3553
|
+
await executeAfterWait(() => {
|
|
3554
|
+
const iframe = getIFrameEl();
|
|
3555
|
+
postMessageToParent(iframe.contentWindow, eventPayload, mockPort);
|
|
3556
|
+
});
|
|
3557
|
+
|
|
3558
|
+
await executeAfterWait(() => {
|
|
3559
|
+
expect(handleInterceptSpy).toHaveBeenCalled();
|
|
3560
|
+
expect(apiCb).not.toHaveBeenCalled();
|
|
3561
|
+
});
|
|
3562
|
+
});
|
|
3348
3563
|
});
|
|
3564
|
+
|
package/src/embed/ts-embed.ts
CHANGED
|
@@ -71,6 +71,7 @@ import { getEmbedConfig } from './embedConfig';
|
|
|
71
71
|
import { ERROR_MESSAGE } from '../errors';
|
|
72
72
|
import { getPreauthInfo } from '../utils/sessionInfoService';
|
|
73
73
|
import { HostEventClient } from './hostEventClient/host-event-client';
|
|
74
|
+
import { getInterceptInitData, handleInterceptEvent, processLegacyInterceptResponse } from '../api-intercept';
|
|
74
75
|
|
|
75
76
|
const { version } = pkgInfo;
|
|
76
77
|
|
|
@@ -201,7 +202,7 @@ export class TsEmbed {
|
|
|
201
202
|
});
|
|
202
203
|
const embedConfig = getEmbedConfig();
|
|
203
204
|
this.embedConfig = embedConfig;
|
|
204
|
-
|
|
205
|
+
|
|
205
206
|
this.hostEventClient = new HostEventClient(this.iFrame);
|
|
206
207
|
this.isReadyForRenderPromise = getInitPromise().then(async () => {
|
|
207
208
|
if (!embedConfig.authTriggerContainer && !embedConfig.useEventForSAMLPopup) {
|
|
@@ -312,31 +313,11 @@ export class TsEmbed {
|
|
|
312
313
|
private subscribedListeners: Record<string, any> = {};
|
|
313
314
|
|
|
314
315
|
/**
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
* embed instance through an identifier contained in the payload,
|
|
318
|
-
* and executes the registered callbacks accordingly.
|
|
316
|
+
* Subscribe to network events (online/offline) that should
|
|
317
|
+
* work regardless of auth status
|
|
319
318
|
*/
|
|
320
|
-
private
|
|
321
|
-
this.
|
|
322
|
-
const messageEventListener = (event: MessageEvent<any>) => {
|
|
323
|
-
const eventType = this.getEventType(event);
|
|
324
|
-
const eventPort = this.getEventPort(event);
|
|
325
|
-
const eventData = this.formatEventData(event, eventType);
|
|
326
|
-
if (event.source === this.iFrame.contentWindow) {
|
|
327
|
-
this.executeCallbacks(
|
|
328
|
-
eventType,
|
|
329
|
-
processEventData(
|
|
330
|
-
eventType,
|
|
331
|
-
eventData,
|
|
332
|
-
this.thoughtSpotHost,
|
|
333
|
-
this.isPreRendered ? this.preRenderWrapper : this.el,
|
|
334
|
-
),
|
|
335
|
-
eventPort,
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
window.addEventListener('message', messageEventListener);
|
|
319
|
+
private subscribeToNetworkEvents() {
|
|
320
|
+
this.unsubscribeToNetworkEvents();
|
|
340
321
|
|
|
341
322
|
const onlineEventListener = (e: Event) => {
|
|
342
323
|
this.trigger(HostEvent.Reload);
|
|
@@ -344,7 +325,7 @@ export class TsEmbed {
|
|
|
344
325
|
window.addEventListener('online', onlineEventListener);
|
|
345
326
|
|
|
346
327
|
const offlineEventListener = (e: Event) => {
|
|
347
|
-
const offlineWarning =
|
|
328
|
+
const offlineWarning = ERROR_MESSAGE.OFFLINE_WARNING;
|
|
348
329
|
this.executeCallbacks(EmbedEvent.Error, {
|
|
349
330
|
offlineWarning,
|
|
350
331
|
});
|
|
@@ -352,11 +333,84 @@ export class TsEmbed {
|
|
|
352
333
|
};
|
|
353
334
|
window.addEventListener('offline', offlineEventListener);
|
|
354
335
|
|
|
355
|
-
this.subscribedListeners =
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
336
|
+
this.subscribedListeners.online = onlineEventListener;
|
|
337
|
+
this.subscribedListeners.offline = offlineEventListener;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private messageEventListener = async (event: MessageEvent<any>) => {
|
|
341
|
+
const eventType = this.getEventType(event);
|
|
342
|
+
const eventPort = this.getEventPort(event);
|
|
343
|
+
const eventData = this.formatEventData(event, eventType);
|
|
344
|
+
if (event.source === this.iFrame.contentWindow) {
|
|
345
|
+
const processedEventData = await processEventData(
|
|
346
|
+
eventType,
|
|
347
|
+
eventData,
|
|
348
|
+
this.thoughtSpotHost,
|
|
349
|
+
this.isPreRendered ? this.preRenderWrapper : this.el,
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const executeEvent = (_eventType: EmbedEvent, data: any) => {
|
|
353
|
+
this.executeCallbacks(_eventType, data, eventPort);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (eventType === EmbedEvent.ApiIntercept && this.viewConfig.enableApiIntercept) {
|
|
357
|
+
const getUnsavedAnswerTml = async (props: { sessionId?: string, vizId?: string }) => {
|
|
358
|
+
const response = await this.triggerUIPassThrough(UIPassthroughEvent.GetUnsavedAnswerTML, props);
|
|
359
|
+
return response[0]?.value;
|
|
360
|
+
}
|
|
361
|
+
handleInterceptEvent({ eventData: processedEventData, executeEvent, embedConfig: this.embedConfig, viewConfig: this.viewConfig, getUnsavedAnswerTml });
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
this.executeCallbacks(
|
|
366
|
+
eventType,
|
|
367
|
+
processedEventData,
|
|
368
|
+
eventPort,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
/**
|
|
373
|
+
* Subscribe to message events that depend on successful iframe setup
|
|
374
|
+
*/
|
|
375
|
+
private subscribeToMessageEvents() {
|
|
376
|
+
this.unsubscribeToMessageEvents();
|
|
377
|
+
|
|
378
|
+
window.addEventListener('message', this.messageEventListener);
|
|
379
|
+
|
|
380
|
+
this.subscribedListeners.message = this.messageEventListener;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Adds event listeners for both network and message events.
|
|
386
|
+
* This maintains backward compatibility with the existing method.
|
|
387
|
+
* Adds a global event listener to window for "message" events.
|
|
388
|
+
* ThoughtSpot detects if a particular event is targeted to this
|
|
389
|
+
* embed instance through an identifier contained in the payload,
|
|
390
|
+
* and executes the registered callbacks accordingly.
|
|
391
|
+
*/
|
|
392
|
+
private subscribeToEvents() {
|
|
393
|
+
this.subscribeToNetworkEvents();
|
|
394
|
+
this.subscribeToMessageEvents();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
private unsubscribeToNetworkEvents() {
|
|
399
|
+
if (this.subscribedListeners.online) {
|
|
400
|
+
window.removeEventListener('online', this.subscribedListeners.online);
|
|
401
|
+
delete this.subscribedListeners.online;
|
|
402
|
+
}
|
|
403
|
+
if (this.subscribedListeners.offline) {
|
|
404
|
+
window.removeEventListener('offline', this.subscribedListeners.offline);
|
|
405
|
+
delete this.subscribedListeners.offline;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private unsubscribeToMessageEvents() {
|
|
410
|
+
if (this.subscribedListeners.message) {
|
|
411
|
+
window.removeEventListener('message', this.subscribedListeners.message);
|
|
412
|
+
delete this.subscribedListeners.message;
|
|
413
|
+
}
|
|
360
414
|
}
|
|
361
415
|
|
|
362
416
|
private unsubscribeToEvents() {
|
|
@@ -391,7 +445,7 @@ export class TsEmbed {
|
|
|
391
445
|
message: customActionsResult.errors,
|
|
392
446
|
});
|
|
393
447
|
}
|
|
394
|
-
|
|
448
|
+
const baseInitData = {
|
|
395
449
|
customisations: getCustomisations(this.embedConfig, this.viewConfig),
|
|
396
450
|
authToken,
|
|
397
451
|
runtimeFilterParams: this.viewConfig.excludeRuntimeFiltersfromURL
|
|
@@ -410,7 +464,10 @@ export class TsEmbed {
|
|
|
410
464
|
this.embedConfig.customVariablesForThirdPartyTools || {},
|
|
411
465
|
hiddenListColumns: this.viewConfig.hiddenListColumns || [],
|
|
412
466
|
customActions: customActionsResult.actions,
|
|
467
|
+
...getInterceptInitData(this.embedConfig, this.viewConfig),
|
|
413
468
|
};
|
|
469
|
+
|
|
470
|
+
return baseInitData;
|
|
414
471
|
}
|
|
415
472
|
|
|
416
473
|
protected async getAppInitData() {
|
|
@@ -798,6 +855,9 @@ export class TsEmbed {
|
|
|
798
855
|
|
|
799
856
|
uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_START);
|
|
800
857
|
|
|
858
|
+
// Always subscribe to network events, regardless of auth status
|
|
859
|
+
this.subscribeToNetworkEvents();
|
|
860
|
+
|
|
801
861
|
return getAuthPromise()
|
|
802
862
|
?.then((isLoggedIn: boolean) => {
|
|
803
863
|
if (!isLoggedIn) {
|
|
@@ -843,7 +903,9 @@ export class TsEmbed {
|
|
|
843
903
|
el.remove();
|
|
844
904
|
});
|
|
845
905
|
}
|
|
846
|
-
|
|
906
|
+
// Subscribe to message events only after successful
|
|
907
|
+
// auth and iframe setup
|
|
908
|
+
this.subscribeToMessageEvents();
|
|
847
909
|
})
|
|
848
910
|
.catch((error) => {
|
|
849
911
|
nextInQueue();
|
|
@@ -983,6 +1045,21 @@ export class TsEmbed {
|
|
|
983
1045
|
this.iFrame.style.height = getCssDimension(height);
|
|
984
1046
|
}
|
|
985
1047
|
|
|
1048
|
+
protected createEmbedEventResponder = (eventPort: MessagePort | void, eventType: EmbedEvent) => {
|
|
1049
|
+
|
|
1050
|
+
const { enableApiIntercept } = getInterceptInitData(this.embedConfig, this.viewConfig);
|
|
1051
|
+
if (eventType === EmbedEvent.OnBeforeGetVizDataIntercept && enableApiIntercept) {
|
|
1052
|
+
return (payload: any) => {
|
|
1053
|
+
const payloadToSend = processLegacyInterceptResponse(payload);
|
|
1054
|
+
this.triggerEventOnPort(eventPort, payloadToSend);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
return (payload: any) => {
|
|
1059
|
+
this.triggerEventOnPort(eventPort, payload);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
986
1063
|
/**
|
|
987
1064
|
* Executes all registered event handlers for a particular event type
|
|
988
1065
|
* @param eventType The event type
|
|
@@ -1007,9 +1084,8 @@ export class TsEmbed {
|
|
|
1007
1084
|
// payload
|
|
1008
1085
|
|| (!callbackObj.options.start && dataStatus === embedEventStatus.END)
|
|
1009
1086
|
) {
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
});
|
|
1087
|
+
const responder = this.createEmbedEventResponder(eventPort, eventType);
|
|
1088
|
+
callbackObj.callback(data, responder);
|
|
1013
1089
|
}
|
|
1014
1090
|
});
|
|
1015
1091
|
}
|
|
@@ -1151,12 +1227,12 @@ export class TsEmbed {
|
|
|
1151
1227
|
}
|
|
1152
1228
|
}
|
|
1153
1229
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1230
|
+
/**
|
|
1231
|
+
* @hidden
|
|
1232
|
+
* Internal state to track if the embed container is loaded.
|
|
1233
|
+
* This is used to trigger events after the embed container is loaded.
|
|
1234
|
+
*/
|
|
1235
|
+
public isEmbedContainerLoaded = false;
|
|
1160
1236
|
|
|
1161
1237
|
/**
|
|
1162
1238
|
* @hidden
|
|
@@ -1243,6 +1319,16 @@ export class TsEmbed {
|
|
|
1243
1319
|
this.handleError('Host event type is undefined');
|
|
1244
1320
|
return null;
|
|
1245
1321
|
}
|
|
1322
|
+
|
|
1323
|
+
// Check if iframe exists before triggering -
|
|
1324
|
+
// this prevents the error when auth fails
|
|
1325
|
+
if (!this.iFrame) {
|
|
1326
|
+
logger.debug(
|
|
1327
|
+
`Cannot trigger ${messageType} - iframe not available (likely due to auth failure)`,
|
|
1328
|
+
);
|
|
1329
|
+
return null;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1246
1332
|
// send an empty object, this is needed for liveboard default handlers
|
|
1247
1333
|
return this.hostEventClient.triggerHostEvent(messageType, data);
|
|
1248
1334
|
}
|
|
@@ -1298,7 +1384,7 @@ export class TsEmbed {
|
|
|
1298
1384
|
}
|
|
1299
1385
|
this.isPreRendered = true;
|
|
1300
1386
|
this.showPreRenderByDefault = showPreRenderByDefault;
|
|
1301
|
-
|
|
1387
|
+
|
|
1302
1388
|
const isAlreadyRendered = this.connectPreRendered();
|
|
1303
1389
|
if (isAlreadyRendered && !replaceExistingPreRender) {
|
|
1304
1390
|
return this;
|
package/src/errors.ts
CHANGED
|
@@ -19,6 +19,7 @@ export const ERROR_MESSAGE = {
|
|
|
19
19
|
MISSING_REPORTING_OBSERVER: 'ReportingObserver not supported',
|
|
20
20
|
RENDER_CALLED_BEFORE_INIT: 'Looks like render was called before calling init, the render won\'t start until init is called.\nFor more info check\n1. https://developers.thoughtspot.com/docs/Function_init#_init\n2.https://developers.thoughtspot.com/docs/getting-started#initSdk',
|
|
21
21
|
SPOTTER_AGENT_NOT_INITIALIZED: 'SpotterAgent not initialized',
|
|
22
|
+
OFFLINE_WARNING : 'Network not Detected. Embed is offline. Please reconnect and refresh',
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
export const CUSTOM_ACTIONS_ERROR_MESSAGE = {
|
package/src/index.ts
CHANGED
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
ListPageColumns,
|
|
65
65
|
CustomActionsPosition,
|
|
66
66
|
CustomActionTarget,
|
|
67
|
+
InterceptedApiType,
|
|
67
68
|
} from './types';
|
|
68
69
|
import { CustomCssVariables } from './css-variables';
|
|
69
70
|
import { SageEmbed, SageViewConfig } from './embed/sage';
|
|
@@ -152,6 +153,7 @@ export {
|
|
|
152
153
|
DataPanelCustomColumnGroupsAccordionState,
|
|
153
154
|
CustomActionsPosition,
|
|
154
155
|
CustomActionTarget,
|
|
156
|
+
InterceptedApiType,
|
|
155
157
|
};
|
|
156
158
|
|
|
157
159
|
export { resetCachedAuthToken } from './authToken';
|