in js/src/launchAsync.ts [55:273]
export function launchAsync(token: string, subdomain: string, content: Content, options?: Options): Promise<LaunchResponse> {
if (isLoading) {
return Promise.reject('Immersive Reader is already launching');
}
return new Promise((resolve, reject: (reason: Error) => void): void => {
if (!token) {
reject({ code: ErrorCode.BadArgument, message: 'Token must not be null' });
return;
}
if (!content) {
reject({ code: ErrorCode.BadArgument, message: 'Content must not be null' });
return;
}
if (!content.chunks) {
reject({ code: ErrorCode.BadArgument, message: 'Chunks must not be null' });
return;
}
if (!content.chunks.length) {
reject({ code: ErrorCode.BadArgument, message: 'Chunks must not be empty' });
return;
}
if (!isValidSubdomain(subdomain) && (!options || !options.customDomain)) {
reject({ code: ErrorCode.InvalidSubdomain, message: errorMessageMap[ErrorCode.InvalidSubdomain] });
return;
}
isLoading = true;
const startTime = Date.now();
options = {
uiZIndex: 1000,
timeout: 15000, // Default to 15 seconds
useWebview: false,
allowFullscreen: true,
hideExitButton: false,
cookiePolicy: CookiePolicy.Disable,
...options
};
// Ensure that we were given a number for the UI z-index
if (!options.uiZIndex || typeof options.uiZIndex !== 'number') {
options.uiZIndex = 1000;
}
let timeoutId: number | null = null;
const iframeContainer: HTMLDivElement = document.createElement('div');
const iframe: HTMLIFrameElement = options.useWebview ? <HTMLIFrameElement>document.createElement('webview') : document.createElement('iframe');
iframe.allow = 'autoplay';
iframe.title = 'Immersive Reader Frame';
iframe.setAttribute('aria-modal', 'true');
const noscroll: HTMLStyleElement = document.createElement('style');
noscroll.innerHTML = 'body{height:100%;overflow:hidden;}';
const resetTimeout = (): void => {
if (timeoutId) {
window.clearTimeout(timeoutId);
timeoutId = null;
}
};
const parent = options.parent && document.contains(options.parent) ? options.parent : document.body;
const reset = (): void => {
// Remove container along with the iframe inside of it
if (parent.contains(iframeContainer)) {
parent.removeChild(iframeContainer);
}
window.removeEventListener('message', messageHandler);
// Clear the timeout timer
resetTimeout();
// Re-enable scrolling
if (noscroll.parentNode) {
noscroll.parentNode.removeChild(noscroll);
}
};
const exit = (): void => {
reset();
isLoading = false;
// Execute exit callback if we have one
if (options.onExit) {
try {
options.onExit();
} catch {
// No-op
}
}
};
// Reset variables
reset();
const messageHandler = (e: any): void => {
// Don't process the message if the data is not a string
if (!e || !e.data || typeof e.data !== 'string') { return; }
if (e.data === 'ImmersiveReader-ReadyForContent') {
resetTimeout(); // Reset the timeout once the reader page loads successfully. The Reader page will report further errors through PostMessage if there is an issue obtaining the ContentModel from the server
const message: Message = {
cogSvcsAccessToken: token,
cogSvcsSubdomain: subdomain,
request: content,
launchToPostMessageSentDurationInMs: Date.now() - startTime,
disableFirstRun: options.disableFirstRun,
readAloudOptions: options.readAloudOptions,
translationOptions: options.translationOptions,
displayOptions: options.displayOptions,
sendPreferences: !!options.onPreferencesChanged,
preferences: options.preferences,
disableTranslation: options.disableTranslation,
disableGrammar: options.disableGrammar,
disableLanguageDetection: options.disableLanguageDetection
};
iframe.contentWindow!.postMessage(JSON.stringify({ messageType: 'Content', messageValue: message }), '*');
} else if (e.data === 'ImmersiveReader-Exit') {
exit();
} else if (e.data.startsWith(PostMessageLaunchResponse)) {
let launchResponse: LaunchResponse = null;
let error: Error = null;
let response: LaunchResponseMessage = null;
try {
response = JSON.parse(e.data.substring(PostMessageLaunchResponse.length));
} catch {
// No-op
}
if (response && response.success) {
launchResponse = {
container: iframeContainer,
sessionId: response.sessionId,
charactersProcessed: response.meteredContentSize
};
} else if (response && !response.success) {
error = {
code: response.errorCode,
message: errorMessageMap[response.errorCode],
sessionId: response.sessionId
};
} else {
error = {
code: ErrorCode.ServerError,
message: errorMessageMap[ErrorCode.ServerError]
};
}
isLoading = false;
if (launchResponse) {
resetTimeout();
resolve(launchResponse);
} else if (error) {
exit();
reject(error);
}
} else if (e.data.startsWith(PostMessagePreferences)) {
if (options.onPreferencesChanged && typeof options.onPreferencesChanged === 'function') {
try {
options.onPreferencesChanged(e.data.substring(PostMessagePreferences.length));
} catch {
// No-op
}
}
}
};
window.addEventListener('message', messageHandler);
// Reject the promise if the Immersive Reader page fails to load.
timeoutId = window.setTimeout((): void => {
reset();
isLoading = false;
reject({ code: ErrorCode.Timeout, message: `Page failed to load after timeout (${options.timeout} ms)` });
}, options.timeout);
// Create and style iframe
if (options.allowFullscreen) {
iframe.setAttribute('allowfullscreen', '');
}
iframe.style.cssText = options.parent ? 'position: static; width: 100%; height: 100%; left: 0; top: 0; border-width: 0' : 'position: static; width: 100vw; height: 100vh; left: 0; top: 0; border-width: 0';
// Send an initial message to the webview so it has a reference to this parent window
if (options.useWebview) {
iframe.addEventListener('loadstop', () => {
iframe.contentWindow.postMessage(JSON.stringify({ messageType: 'WebviewHost' }), '*');
});
}
const domain = options.customDomain ? options.customDomain : `https://${subdomain}.cognitiveservices.azure.com/immersivereader/webapp/v1.0/`;
let src = domain + 'reader?exitCallback=ImmersiveReader-Exit&sdkPlatform=' + sdkPlatform + '&sdkVersion=' + sdkVersion;
src += '&cookiePolicy=' + ((options.cookiePolicy === CookiePolicy.Enable) ? 'enable' : 'disable');
if (options.hideExitButton) {
src += '&hideExitButton=true';
}
if (options.uiLang) {
src += '&omkt=' + options.uiLang;
}
iframe.src = src;
iframeContainer.style.cssText = options.parent ? `position: relative; width: 100%; height: 100%; border-width: 0; -webkit-perspective: 1px; z-index: ${options.uiZIndex}; background: white; overflow: hidden` : `position: fixed; width: 100vw; height: 100vh; left: 0; top: 0; border-width: 0; -webkit-perspective: 1px; z-index: ${options.uiZIndex}; background: white; overflow: hidden`;
iframeContainer.appendChild(iframe);
parent.appendChild(iframeContainer);
// Disable body scrolling
document.head.appendChild(noscroll);
});
}