• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

thoughtspot / visual-embed-sdk / #1466

17 Jan 2025 05:14AM UTC coverage: 41.223% (-52.6%) from 93.86%
#1466

Pull #65

Prashant.patil
SCAL-233454-exp removing all extra config and checking
Pull Request #65: test-exported memb

266 of 1114 branches covered (23.88%)

Branch coverage included in aggregate %.

74 of 367 new or added lines in 15 files covered. (20.16%)

1012 existing lines in 23 files now uncovered.

1291 of 2663 relevant lines covered (48.48%)

9.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.57
/src/native/commonUtils.ts
1
import EventEmitter from 'eventemitter3';
2
import { AuthEvent, AuthStatus, setAuthEE } from '../auth';
3
import { setMobileEmbedConfig } from '../embed/embedConfig';
4
import { getCustomisationsMobileEmbed, getQueryParamString } from '../utils';
1✔
5
import { Param } from '../types';
1✔
6
import pkgInfo from '../../package.json';
1✔
7
import { WebViewConfig } from './types';
8
import { logger } from '../utils/logger';
1✔
9
import { handleAuth } from './handleAuth';
10

11
/**
12
 * This method constructs the webview URL with given config.
13
 * @param config To get the webviewURL pass the necessary config options.
14
 * host: string;
15
 * authType: AuthType;
16
 * liveboardId: string;
17
 * getAuthToken: () => Promise<string>;
18
 * These four are necessary arguments.
19
 * @returns The Promise for WebView URL.
20
 */
21
export const getWebViewUrl = async (config: WebViewConfig): Promise<string> => {
1✔
NEW
22
    if (typeof config.getAuthToken !== 'function') {
×
NEW
23
        throw new Error('`getAuthToken` must be a function that returns a Promise.');
×
24
    }
25

NEW
26
    const authToken = await config.getAuthToken();
×
NEW
27
    if (!authToken) {
×
NEW
28
        throw new Error('Failed to fetch initial authentication token.');
×
29
    }
30

NEW
31
    const hostAppUrl = encodeURIComponent(
×
32
        config.thoughtSpotHost.includes('localhost')
×
33
        || config.thoughtSpotHost.includes('127.0.0.1')
34
        || config.thoughtSpotHost.includes('10.0.2.2')
35
            ? 'local-host'
36
            : config.thoughtSpotHost,
37
    );
38

NEW
39
    const queryParams = {
×
40
        [Param.EmbedApp]: true,
41
        [Param.HostAppUrl]: hostAppUrl,
42
        [Param.Version]: pkgInfo.version,
43
        [Param.AuthType]: config.authType,
44
        [Param.livedBoardEmbed]: true,
45
        [Param.EnableFlipTooltipToContextMenu]: true,
46
        [Param.ContextMenuTrigger]: true,
47
    };
48

NEW
49
    const queryString = getQueryParamString(queryParams);
×
NEW
50
    const webViewUrl = `${config.thoughtSpotHost}/v2/?${queryString}#/embed/viz/${encodeURIComponent(config.liveboardId)}`;
×
51

NEW
52
    return webViewUrl;
×
53
};
54

55
/**
56
 * setting up message handling for the message replies to TS instances.
57
 * @param config The webview config
58
 * @param event The message event from the WebView.
59
 * @param WebViewRef Ref to use and inject javascript
60
 * @param webViewRef
61
 */
62
export const setupWebViewMessageHandler = async (
1✔
63
    config: WebViewConfig,
64
    event: any,
65
    webViewRef: any,
66
) => {
67
    const message = JSON.parse(event.nativeEvent.data);
6✔
68

69
    const injectJavaScript = (codeSnip: string) => {
6✔
70
        if (webViewRef?.current) {
4!
71
            webViewRef.current.injectJavaScript(codeSnip);
4✔
72
        } else {
NEW
73
            logger.error('Reference for Webview not found!!');
×
74
        }
75
    };
76

77
    // scaligent -> some page -> sending some data to scaligent
78
    // static page -> we have send to scaligent ->
79
    // iframe -> .onMessage(()) ->
80

81
    const defaultHandleMessage = async () => {
6✔
82
        switch (message.type) {
5✔
83
            case 'appInit': {
84
                try {
2✔
85
                    const authToken = await config.getAuthToken();
2✔
86
                    const initPayload = {
1✔
87
                        type: 'appInit',
88
                        data: {
89
                            host: config.thoughtSpotHost,
90
                            authToken,
91
                            customisations: getCustomisationsMobileEmbed(config),
92
                        },
93
                    };
94
                    injectJavaScript(jsCodeToHandleInteractionsForContextMenu);
1✔
95
                    injectJavaScript(`window.postMessage(${JSON.stringify(initPayload)}, '*');`);
1✔
96
                } catch (error) {
97
                    console.error('Error handling appInit:', error);
1✔
98
                }
99
                break;
1✔
100
            }
101

102
            case 'ThoughtspotAuthExpired': {
103
                try {
1✔
104
                    const newAuthToken = await config.getAuthToken();
1✔
105
                    if (newAuthToken) {
1✔
106
                        const authExpirePayload = {
1✔
107
                            type: 'ThoughtspotAuthExpired',
108
                            data: { authToken: newAuthToken },
109
                        };
110
                        injectJavaScript(`window.postMessage(${JSON.stringify(authExpirePayload)}, '*');`);
1✔
111
                    }
112
                } catch (error) {
NEW
113
                    console.error('Error refreshing token on expiry:', error);
×
114
                }
115
                break;
1✔
116
            }
117

118
            case 'ThoughtspotAuthFailure': {
119
                try {
1✔
120
                    const newAuthToken = await config.getAuthToken();
1✔
121
                    if (newAuthToken) {
1✔
122
                        const authFailurePayload = {
1✔
123
                            type: 'ThoughtspotAuthFailure',
124
                            data: { authToken: newAuthToken },
125
                        };
126
                        injectJavaScript(`window.postMessage(${JSON.stringify(authFailurePayload)}, '*');`);
1✔
127
                    }
128
                } catch (error) {
NEW
129
                    console.error('Error refreshing token on failure:', error);
×
130
                }
131
                break;
1✔
132
            }
133

134
            default:
135
                console.warn('Unhandled message type:', message.type);
1✔
136
        }
137
    };
138

139
    if (config.handleMessage) {
6✔
140
        await config.handleMessage(event);
1✔
141
    } else {
142
        await defaultHandleMessage();
5✔
143
    }
144
};
145

146
// /**
147
//  *
148
//  * @param embedConfig
149
//  * @param webViewRef
150
//  */
151
// export function initMobile(embedConfig: WebViewConfig, webViewRef?: any)
152
// : EventEmitter<AuthStatus | AuthEvent> {
153
//     const authEE = new EventEmitter<AuthStatus | AuthEvent>();
154
//     setMobileEmbedConfig(embedConfig);
155
//     setAuthEE(authEE);
156

157
//     handleAuth();
158

159
//     if (embedConfig.autoAttachWebViewHandler && webViewRef?.current) {
160
//         const originalOnMessage = webViewRef.current.props?.onMessage;
161
//         webViewRef.current.props.onMessage = (event: any) => {
162
//             // If the user has some onMessage Added.
163
//             if (originalOnMessage) {
164
//                 originalOnMessage(event);
165
//             }
166
//             // Then we execute ours.
167
//             setupWebViewMessageHandler(embedConfig, event, webViewRef);
168
//         };
169
//     }
170

171
//     return authEE;
172
// }
173

174
const jsCodeToHandleInteractionsForContextMenu = `
1✔
175
// Disabling auofocus
176
document.querySelectorAll('input[autofocus], textarea[autofocus]').forEach(el => el.removeAttribute('autofocus'));
177

178
// adding meta tag to keep fixed viewport scalign
179
const meta = document.createElement('meta');
180
meta.name = 'viewport';
181
meta.content = 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no';
182
document.head.appendChild(meta);
183

184
// input focus problem. -> we can just force it inside our view. 
185
document.addEventListener('focusin', (event) => {
186
  const target = event.target;
187

188
  if (
189
    target.tagName === 'INPUT' || 
190
    target.tagName === 'TEXTAREA'
191
  ) {
192
    const rect = target.getBoundingClientRect();
193
    if (
194
      rect.top < 0 || 
195
      rect.bottom > window.innerHeight || 
196
      rect.left < 0 || 
197
      rect.right > window.innerWidth
198
    ) {
199
      event.preventDefault();
200
      // target.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'end' });
201
      const horizontalPadding = 10;
202

203
      let scrollX = 0;
204

205
      if (rect.left < horizontalPadding) {
206
        scrollX = rect.left - horizontalPadding;
207
      }  
208
      if (rect.right > window.innerWidth - horizontalPadding) {
209
        scrollX = rect.right - window.innerWidth + horizontalPadding;
210
      }
211
      const scrollY = rect.top - (window.innerHeight / 2 - rect.height / 2);
212

213
      window.scrollBy({
214
        top: scrollY,
215
        left: scrollX,
216
        behavior: 'smooth',
217
      })
218
    }
219
  }
220
});
221
`;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc