ZaStoGram_desktop/Telegram/Resources/bot_webview_shell_html/page.js

1063 lines
29 KiB
JavaScript

(function() {
'use strict';
const root = document.getElementById('root');
const header = document.getElementById('header');
const frameShell = document.getElementById('frame-shell');
const frameWrap = document.getElementById('frame-wrap');
const footer = document.getElementById('footer');
const buttonsWrap = document.getElementById('buttons-wrap');
const buttons = document.getElementById('buttons');
const badge = document.getElementById('badge');
const pointerShield = document.getElementById('pointer-shield');
const menu = document.getElementById('menu');
const menuList = document.getElementById('menu-list');
const title = document.getElementById('title');
const controls = {
back: document.getElementById('back'),
menu: document.getElementById('menu-toggle'),
menuIcon: document.getElementById('menu-toggle-icon'),
close: document.getElementById('close')
};
const shellState = {
backVisible: false,
badgeVisible: false,
isFullscreen: false,
menuOpen: false,
menuItems: [],
buttons: {
main: null,
secondary: null
}
};
if (window.TelegramDesktopWindowAlphaSupported === false) {
root.classList.add('no-window-alpha');
}
const shellAssets = {
icons: Object.create(null),
titleMenuIcon: null,
verifiedBadge: null,
menuPalette: null
};
const shellToken = TDESKTOP_SHELL_TOKEN_PLACEHOLDER;
const nativeMessageType = 'tdesktop_external_bot_webapp';
const frameSandbox = [
'allow-scripts',
'allow-same-origin',
'allow-popups',
'allow-forms',
'allow-modals',
'allow-storage-access-by-user-activation'
].join(' ');
const maxPendingEvents = 64;
let iframe = null;
let frameLoaded = false;
let frameUrl = 'about:blank';
let frameOrigin = '';
let sameOrigin = false;
let frameGeneration = 0;
let reloadSupported = false;
let reloadTimeout = null;
let viewportScheduled = false;
let sentViewportHeight = -1;
let dragRegionsScheduled = false;
let sentDragRegions = '';
let resizeObserver = null;
const pendingEvents = [];
function normalizeEventData(eventData) {
if (typeof eventData === 'string') {
try {
const parsed = JSON.parse(eventData);
return parsed
&& typeof parsed === 'object'
&& !Array.isArray(parsed)
? parsed
: {};
} catch (e) {
return {};
}
}
return eventData
&& typeof eventData === 'object'
&& !Array.isArray(eventData)
? eventData
: {};
}
function isShellOrigin() {
return window.location.protocol === 'https:'
&& window.location.hostname === 'web.telegram.org'
&& (!window.location.port || window.location.port === '443');
}
function originFromUrl(url) {
try {
const origin = new URL(url, window.location.href).origin;
return origin && origin !== 'null' ? origin : '';
} catch (e) {
return '';
}
}
function invokeNative(source, eventType, eventData, origin) {
if (!window.external
|| typeof window.external.invoke !== 'function'
|| typeof shellToken !== 'string'
|| !shellToken
|| typeof eventType !== 'string') {
return;
}
if (source === 'shell' && !isShellOrigin()) {
return;
}
window.external.invoke(JSON.stringify({
type: nativeMessageType,
source: source,
token: shellToken,
origin: origin || window.location.origin,
eventType: eventType,
eventData: normalizeEventData(eventData)
}));
}
function invokeShell(eventType, eventData) {
invokeNative('shell', eventType, eventData);
}
function invokeWebApp(eventType, eventData, origin) {
invokeNative('webapp', eventType, eventData, origin);
}
function sendToFrame(eventType, eventData, generation) {
if (!iframe
|| !iframe.contentWindow
|| generation !== frameGeneration) {
return;
}
if (sameOrigin && !frameOrigin) {
return;
}
const targetOrigin = sameOrigin ? frameOrigin : '*';
iframe.contentWindow.postMessage(JSON.stringify({
eventType: eventType,
eventData: eventData || {}
}), targetOrigin);
}
function postToFrame(eventType, eventData) {
const generation = frameGeneration;
if (!iframe || !iframe.contentWindow || !frameLoaded) {
pendingEvents.push({
generation: generation,
eventType: eventType,
eventData: eventData || {}
});
while (pendingEvents.length > maxPendingEvents) {
pendingEvents.shift();
}
return;
}
sendToFrame(eventType, eventData, generation);
}
function regionOf(element) {
const rect = element.getBoundingClientRect();
return (rect.width > 0 && rect.height > 0)
? [rect.left, rect.top, rect.width, rect.height]
: null;
}
function sendDragRegions() {
const draggable = !shellState.isFullscreen && !shellState.menuOpen;
const titleControls = header.querySelectorAll('.title-control');
const regions = {
drag: draggable ? [regionOf(header)].filter(Boolean) : [],
noDrag: draggable
? Array.prototype.map.call(titleControls, regionOf)
.filter(Boolean)
: []
};
const serialized = JSON.stringify(regions);
if (serialized === sentDragRegions) {
return;
}
sentDragRegions = serialized;
invokeShell('shell_set_drag_regions', regions);
}
function scheduleDragRegions() {
if (dragRegionsScheduled) {
return;
}
dragRegionsScheduled = true;
window.requestAnimationFrame(function() {
dragRegionsScheduled = false;
sendDragRegions();
});
}
function flushPendingEvents() {
const pending = pendingEvents.splice(0);
for (const event of pending) {
if (event.generation === frameGeneration) {
sendToFrame(event.eventType, event.eventData, event.generation);
}
}
}
function sendViewportChanged() {
if (!iframe) {
return;
}
const height = Math.max(
0,
Math.round(frameShell.getBoundingClientRect().height));
if (height === sentViewportHeight) {
return;
}
sentViewportHeight = height;
postToFrame('viewport_changed', {
height: height,
is_state_stable: true,
is_expanded: true
});
}
function scheduleViewport() {
scheduleDragRegions();
if (viewportScheduled) {
return;
}
viewportScheduled = true;
window.requestAnimationFrame(function() {
viewportScheduled = false;
sendViewportChanged();
});
}
function setMetric(name, value) {
if (typeof value === 'number' && Number.isFinite(value)) {
root.style.setProperty(name, String(value) + 'px');
}
}
function applyMetrics(data) {
if (!data || typeof data !== 'object') {
return;
}
setMetric('--shell-radius', data.shellRadius);
setMetric('--shell-pad-top', data.shellPaddingTop);
setMetric('--shell-pad-right', data.shellPaddingRight);
setMetric('--shell-pad-bottom', data.shellPaddingBottom);
setMetric('--shell-pad-left', data.shellPaddingLeft);
setMetric('--shadow-pad-top', data.shadowPaddingTop);
setMetric('--shadow-pad-right', data.shadowPaddingRight);
setMetric('--shadow-pad-bottom', data.shadowPaddingBottom);
setMetric('--shadow-pad-left', data.shadowPaddingLeft);
setMetric('--header-height', data.headerHeight);
setMetric('--title-pad-top', data.titlePaddingTop);
setMetric('--title-pad-right', data.titlePaddingRight);
setMetric('--title-pad-bottom', data.titlePaddingBottom);
setMetric('--title-pad-left', data.titlePaddingLeft);
setMetric('--badge-skip', data.badgeSkip);
setMetric('--frame-radius', data.frameRadius);
setMetric('--control-width', data.controlWidth);
setMetric('--control-height', data.controlHeight);
setMetric('--button-height', data.buttonHeight);
setMetric('--button-gap-x', data.buttonGapX);
setMetric('--button-gap-y', data.buttonGapY);
setMetric('--fullscreen-control-width', data.fullscreenControlWidth);
setMetric('--fullscreen-control-height', data.fullscreenControlHeight);
setMetric('--fullscreen-control-top', data.fullscreenControlTop);
setMetric('--fullscreen-control-right', data.fullscreenControlRight);
setMetric('--fullscreen-control-gap', data.fullscreenControlGap);
}
function hexByte(value) {
const text = Math.max(
0,
Math.min(255, Math.round(value))).toString(16);
return (text.length < 2 ? '0' : '') + text;
}
function buttonRippleForBackground(value) {
if (!/^#[0-9a-f]{6}$/i.test(value || '')) {
return null;
}
const red = parseInt(value.slice(1, 3), 16);
const green = parseInt(value.slice(3, 5), 16);
const blue = parseInt(value.slice(5, 7), 16);
const maximum = Math.max(red, green, blue);
const minimum = Math.min(red, green, blue);
const delta = maximum - minimum;
let hue = 0;
if (delta !== 0 && maximum === red) {
hue = 60 * (((green - blue) / delta) % 6);
} else if (delta !== 0 && maximum === green) {
hue = 60 * ((blue - red) / delta + 2);
} else if (delta !== 0) {
hue = 60 * ((red - green) / delta + 4);
}
if (hue < 0) {
hue += 360;
}
const saturation = maximum === 0 ? 0 : delta / maximum;
const nextValue = Math.max(
0,
Math.min(255, maximum + (maximum > 128 ? -32 : 32)));
const chroma = nextValue * saturation;
const x = chroma * (1 - Math.abs((hue / 60) % 2 - 1));
const m = nextValue - chroma;
let nextRed = 0;
let nextGreen = 0;
let nextBlue = 0;
if (hue < 60) {
nextRed = chroma;
nextGreen = x;
} else if (hue < 120) {
nextRed = x;
nextGreen = chroma;
} else if (hue < 180) {
nextGreen = chroma;
nextBlue = x;
} else if (hue < 240) {
nextGreen = x;
nextBlue = chroma;
} else if (hue < 300) {
nextRed = x;
nextBlue = chroma;
} else {
nextRed = chroma;
nextBlue = x;
}
return '#'
+ hexByte(nextRed + m)
+ hexByte(nextGreen + m)
+ hexByte(nextBlue + m);
}
function applyColors(data) {
const next = (data && data.colors) ? data.colors : data;
if (!next || typeof next !== 'object') {
return;
}
const properties = {
bodyBg: '--body-bg',
titleBg: '--title-bg',
titleFg: '--title-fg',
titleControlFg: '--title-control-fg',
titleControlRipple: '--title-control-ripple',
bottomBg: '--bottom-bg'
};
for (const key in properties) {
if (next[key]) {
root.style.setProperty(properties[key], next[key]);
}
}
}
function applyAssets(data) {
if (!data || typeof data !== 'object') {
return;
}
if (data.icons && typeof data.icons === 'object') {
shellAssets.icons = data.icons;
}
if (data.titleMenuIcon && typeof data.titleMenuIcon === 'object') {
shellAssets.titleMenuIcon = data.titleMenuIcon;
}
if (data.verifiedBadge && typeof data.verifiedBadge === 'object') {
shellAssets.verifiedBadge = data.verifiedBadge;
}
if (data.menuPalette && typeof data.menuPalette === 'object') {
shellAssets.menuPalette = data.menuPalette;
if (data.menuPalette.bg) {
root.style.setProperty('--menu-bg', data.menuPalette.bg);
}
if (data.menuPalette.fg) {
root.style.setProperty('--menu-fg', data.menuPalette.fg);
}
if (data.menuPalette.hoverBg) {
root.style.setProperty(
'--menu-hover-bg',
data.menuPalette.hoverBg);
}
if (data.menuPalette.ripple) {
root.style.setProperty('--menu-ripple', data.menuPalette.ripple);
}
if (data.menuPalette.separator) {
root.style.setProperty(
'--menu-separator',
data.menuPalette.separator);
}
if (data.menuPalette.attention) {
root.style.setProperty(
'--menu-attention',
data.menuPalette.attention);
}
}
if (shellAssets.titleMenuIcon && shellAssets.titleMenuIcon.url) {
controls.menuIcon.style.webkitMaskImage = 'url('
+ shellAssets.titleMenuIcon.url + ')';
controls.menuIcon.style.maskImage = 'url('
+ shellAssets.titleMenuIcon.url + ')';
if (shellAssets.titleMenuIcon.width) {
controls.menuIcon.style.width
= String(shellAssets.titleMenuIcon.width) + 'px';
}
if (shellAssets.titleMenuIcon.height) {
controls.menuIcon.style.height
= String(shellAssets.titleMenuIcon.height) + 'px';
}
}
if (shellAssets.verifiedBadge && shellAssets.verifiedBadge.url) {
badge.style.backgroundImage = 'url('
+ shellAssets.verifiedBadge.url + ')';
if (shellAssets.verifiedBadge.width) {
badge.style.width = String(shellAssets.verifiedBadge.width) + 'px';
}
if (shellAssets.verifiedBadge.height) {
badge.style.height = String(shellAssets.verifiedBadge.height) + 'px';
}
if (shellAssets.verifiedBadge.alt) {
badge.setAttribute('aria-label', shellAssets.verifiedBadge.alt);
}
} else {
badge.style.backgroundImage = '';
badge.removeAttribute('aria-label');
}
applyChrome({});
renderMenu();
}
function applyChrome(data) {
if (!data || typeof data !== 'object') {
return;
}
if (Object.prototype.hasOwnProperty.call(data, 'backVisible')) {
shellState.backVisible = !!data.backVisible;
}
if (Object.prototype.hasOwnProperty.call(data, 'badgeVisible')) {
shellState.badgeVisible = !!data.badgeVisible;
}
controls.back.classList.toggle('hidden', !shellState.backVisible);
controls.menu.disabled = !shellState.menuItems.length;
badge.classList.toggle(
'hidden',
!shellState.badgeVisible
|| !(shellAssets.verifiedBadge && shellAssets.verifiedBadge.url));
badge.setAttribute(
'aria-hidden',
badge.classList.contains('hidden') ? 'true' : 'false');
if (controls.menu.disabled) {
closeMenu();
}
scheduleDragRegions();
}
function visibleButtons() {
const main = shellState.buttons.main && shellState.buttons.main.visible
? shellState.buttons.main
: null;
const secondary = shellState.buttons.secondary
&& shellState.buttons.secondary.visible
? shellState.buttons.secondary
: null;
const result = {
layout: 'single',
buttons: []
};
if (main && secondary) {
const position = secondary.position || 'left';
if (position === 'top') {
result.layout = 'vertical';
result.buttons = [secondary, main];
} else if (position === 'bottom') {
result.layout = 'vertical';
result.buttons = [main, secondary];
} else if (position === 'left') {
result.layout = 'horizontal';
result.buttons = [secondary, main];
} else {
result.layout = 'horizontal';
result.buttons = [main, secondary];
}
} else if (main) {
result.buttons = [main];
} else if (secondary) {
result.buttons = [secondary];
}
return result;
}
function requestButtonIcon(state) {
if (!state
|| !state.visible
|| !state.iconCustomEmojiId
|| state.iconResolvedGeneration === state.iconGeneration
|| state.iconRequestGeneration === state.iconGeneration) {
return;
}
state.iconRequestGeneration = state.iconGeneration;
invokeShell('shell_request_button_icon', {
name: state.name
});
}
function updateFooter() {
const visible = visibleButtons();
const hasButtons = !!visible.buttons.length;
buttonsWrap.classList.toggle('visible', hasButtons);
footer.classList.toggle('visible', hasButtons);
scheduleViewport();
}
function renderButtons() {
const visible = visibleButtons();
buttons.textContent = '';
buttons.dataset.layout = visible.layout;
for (const state of visible.buttons) {
const button = document.createElement('button');
button.type = 'button';
button.className = 'shell-button';
button.disabled = !state.active;
const buttonColor = state.color || '#40a7e3';
button.style.background = buttonColor;
button.style.color = state.textColor || '#ffffff';
const rippleColor = buttonRippleForBackground(buttonColor);
if (rippleColor) {
button.style.setProperty('--button-ripple', rippleColor);
}
const icon = document.createElement('span');
icon.className = 'button-icon';
const iconReady = state.iconResolvedGeneration === state.iconGeneration;
if (state.iconCustomEmojiId && state.iconUrl) {
icon.classList.add('visible');
icon.style.backgroundImage = 'url(' + state.iconUrl + ')';
} else if (state.iconCustomEmojiId && !iconReady) {
icon.classList.add('visible', 'pending');
requestButtonIcon(state);
}
button.appendChild(icon);
const label = document.createElement('span');
label.className = 'button-label';
label.textContent = state.text || '';
button.appendChild(label);
const spinner = document.createElement('span');
spinner.className = 'button-spinner';
if (state.progress) {
spinner.classList.add('visible');
}
button.appendChild(spinner);
button.addEventListener('click', function() {
if (!button.disabled) {
postToFrame(state.name + '_button_pressed', {});
}
});
setupRipple(button);
buttons.appendChild(button);
}
updateFooter();
}
function menuIconUrl(name) {
const asset = shellAssets.icons && name ? shellAssets.icons[name] : null;
return (asset && asset.url) ? asset.url : '';
}
function createMenuNode(item, className) {
const clickable = !!item.id;
const node = document.createElement(clickable ? 'button' : 'div');
node.className = className
+ (item.attention ? ' attention' : '')
+ (clickable ? '' : ' disabled');
if (clickable) {
node.type = 'button';
setupRipple(node);
node.addEventListener('click', function(event) {
if (event.isTrusted) {
invokeShell('shell_menu_action', { id: item.id });
closeMenu();
}
});
}
return node;
}
function createMenuCopy(item) {
const copy = document.createElement('span');
copy.className = 'menu-item-copy';
const titleNode = document.createElement('span');
titleNode.className = 'menu-item-title';
titleNode.textContent = item.text || '';
copy.appendChild(titleNode);
if (item.subtitle) {
const subtitleNode = document.createElement('span');
subtitleNode.className = 'menu-item-subtitle';
subtitleNode.textContent = item.subtitle;
copy.appendChild(subtitleNode);
}
return copy;
}
function renderMenu() {
menuList.textContent = '';
for (const item of shellState.menuItems) {
if (item.separator) {
const separator = document.createElement('div');
separator.className = 'menu-separator';
menuList.appendChild(separator);
continue;
}
if (Array.isArray(item.children) && item.children.length) {
const group = document.createElement('div');
group.className = 'menu-group';
const header = document.createElement('div');
header.className = 'menu-item menu-group-title';
const headerIcon = document.createElement('span');
headerIcon.className = 'menu-item-icon';
const headerIconUrl = menuIconUrl(item.icon);
if (headerIconUrl) {
headerIcon.style.backgroundImage = 'url('
+ headerIconUrl + ')';
}
header.appendChild(headerIcon);
header.appendChild(createMenuCopy(item));
group.appendChild(header);
const children = document.createElement('div');
children.className = 'menu-group-children';
for (const child of item.children) {
if (child.separator) {
const separator = document.createElement('div');
separator.className = 'menu-separator';
children.appendChild(separator);
continue;
}
const row = createMenuNode(child, 'menu-download');
row.appendChild(createMenuCopy(child));
if (child.actionLabel) {
const action = document.createElement('span');
action.className = 'menu-item-action';
action.textContent = child.actionLabel;
row.appendChild(action);
}
children.appendChild(row);
}
group.appendChild(children);
menuList.appendChild(group);
continue;
}
const row = createMenuNode(item, 'menu-item');
const icon = document.createElement('span');
icon.className = 'menu-item-icon';
const iconUrl = menuIconUrl(item.icon);
if (iconUrl) {
icon.style.backgroundImage = 'url(' + iconUrl + ')';
}
row.appendChild(icon);
row.appendChild(createMenuCopy(item));
menuList.appendChild(row);
}
menu.classList.toggle(
'visible',
shellState.menuOpen && !!shellState.menuItems.length);
pointerShield.classList.toggle(
'visible',
menu.classList.contains('visible'));
controls.menu.classList.toggle(
'active',
menu.classList.contains('visible'));
scheduleDragRegions();
}
function closeMenu() {
if (!shellState.menuOpen) {
return;
}
shellState.menuOpen = false;
renderMenu();
releaseRipple(controls.menu);
}
function toggleMenu(event) {
if (event && !event.isTrusted) {
return;
}
if (shellState.menuOpen) {
closeMenu();
return;
}
invokeShell('shell_menu_request', {});
shellState.menuOpen = true;
renderMenu();
holdRipple(controls.menu);
}
function parseFrameMessage(data) {
if (typeof data === 'string') {
try {
return JSON.parse(data);
} catch (e) {
return null;
}
}
return data && typeof data === 'object' && !Array.isArray(data)
? data
: null;
}
function addRipple(button, x, y) {
if (!button || button.disabled) {
return;
}
const rect = button.getBoundingClientRect();
const ripple = document.createElement('span');
ripple.className = 'ripple';
const inner = document.createElement('span');
inner.className = 'inner';
const size = 2 * Math.hypot(
Math.max(x, rect.width - x),
Math.max(y, rect.height - y));
inner.style.width = String(size) + 'px';
inner.style.height = String(size) + 'px';
inner.style.left = String(x - size / 2) + 'px';
inner.style.top = String(y - size / 2) + 'px';
ripple.appendChild(inner);
button.appendChild(ripple);
}
function stopRipples(button) {
const ripples = Array.prototype.slice.call(
button.querySelectorAll('.ripple:not(.hiding)'));
for (const ripple of ripples) {
ripple.classList.add('hiding');
ripple.removeTimeout = window.setTimeout(function() {
ripple.remove();
}, 200);
}
}
// Like RippleButton::setForceRippled in the native panel.
function holdRipple(button) {
button.rippleHeld = true;
const ripples = button.querySelectorAll('.ripple');
const last = ripples.length ? ripples[ripples.length - 1] : null;
if (last) {
window.clearTimeout(last.removeTimeout);
last.classList.remove('hiding');
} else {
const rect = button.getBoundingClientRect();
addRipple(button, rect.width / 2, rect.height / 2);
}
}
function releaseRipple(button) {
button.rippleHeld = false;
stopRipples(button);
}
function setupRipple(button) {
button.addEventListener('mousedown', function(event) {
if (event.button !== 0) {
return;
}
const rect = button.getBoundingClientRect();
addRipple(button, event.clientX - rect.left, event.clientY - rect.top);
// The shield keeps the release over the frame in this document.
button.classList.add('pressed');
pointerShield.classList.add('held');
const release = function(event) {
if (event.type === 'mouseup' && event.button !== 0) {
return;
}
window.removeEventListener('mouseup', release, true);
window.removeEventListener('blur', release);
button.classList.remove('pressed');
pointerShield.classList.remove('held');
if (!button.rippleHeld) {
stopRipples(button);
}
};
window.addEventListener('mouseup', release, true);
window.addEventListener('blur', release);
});
}
window.addEventListener('message', function(event) {
if (!iframe
|| !iframe.contentWindow
|| event.source !== iframe.contentWindow) {
return;
}
if (sameOrigin && (!frameOrigin || event.origin !== frameOrigin)) {
return;
}
const message = parseFrameMessage(event.data);
if (!message || typeof message.eventType !== 'string') {
return;
}
if (message.eventType === 'iframe_ready') {
reloadSupported = !!(message.eventData && message.eventData.reload_supported);
return;
} else if (message.eventType === 'iframe_will_reload') {
if (reloadTimeout) {
window.clearTimeout(reloadTimeout);
reloadTimeout = null;
}
setFrameLoaded(false);
return;
}
invokeWebApp(message.eventType, message.eventData, event.origin);
});
pointerShield.addEventListener('mousedown', closeMenu);
window.addEventListener('blur', closeMenu);
document.addEventListener('mousedown', function(event) {
if (!shellState.menuOpen) {
return;
}
const target = event.target;
if (menu.contains(target)
|| controls.menu.contains(target)
|| pointerShield.contains(target)) {
return;
}
closeMenu();
});
window.addEventListener('keydown', function(event) {
if (event.key === 'Escape' && shellState.menuOpen) {
event.preventDefault();
closeMenu();
}
});
window.addEventListener('resize', scheduleViewport);
if (window.ResizeObserver) {
resizeObserver = new window.ResizeObserver(scheduleViewport);
resizeObserver.observe(frameShell);
resizeObserver.observe(root);
}
controls.close.addEventListener('click', function(event) {
if (event.isTrusted) {
invokeShell('shell_close', {});
}
});
controls.back.addEventListener('click', function() {
postToFrame('back_button_pressed', {});
});
controls.menu.addEventListener('click', toggleMenu);
setupRipple(controls.close);
setupRipple(controls.back);
setupRipple(controls.menu);
header.addEventListener('selectstart', function(event) {
event.preventDefault();
});
document.addEventListener('contextmenu', function(event) {
event.preventDefault();
});
function setFrameLoaded(loaded) {
frameLoaded = loaded;
root.classList.toggle('loading', !loaded);
}
function createIframe(url) {
closeMenu();
if (reloadTimeout) {
window.clearTimeout(reloadTimeout);
reloadTimeout = null;
}
const generation = ++frameGeneration;
pendingEvents.splice(0);
const next = document.createElement('iframe');
next.setAttribute('sandbox', frameSandbox);
next.setAttribute('allow', [
'clipboard-read',
'clipboard-write',
'fullscreen',
'camera',
'microphone',
'geolocation'
].join('; '));
next.referrerPolicy = 'no-referrer';
next.addEventListener('load', function() {
if (iframe !== next || generation !== frameGeneration) {
return;
}
setFrameLoaded(true);
flushPendingEvents();
sentViewportHeight = -1;
scheduleViewport();
});
setFrameLoaded(false);
reloadSupported = false;
if (iframe) {
iframe.remove();
}
iframe = next;
iframe.src = url || 'about:blank';
frameWrap.appendChild(iframe);
}
function fallbackReloadFrame() {
createIframe(frameUrl || 'about:blank');
}
function reloadFrame() {
if (!iframe) {
return;
}
if (reloadSupported && frameLoaded && iframe.contentWindow) {
sendToFrame('reload_iframe', {}, frameGeneration);
if (reloadTimeout) {
window.clearTimeout(reloadTimeout);
}
reloadTimeout = window.setTimeout(function() {
reloadTimeout = null;
fallbackReloadFrame();
}, 500);
return;
}
fallbackReloadFrame();
}
function isNativeToken(token) {
return !!shellToken && token === shellToken;
}
const api = {
bootstrap: function(data, token) {
if (!isNativeToken(token)) {
return;
}
applyMetrics(data && data.metrics);
applyColors(data && data.colors);
applyChrome(data || {});
title.textContent = (data && data.title) || '';
document.title = (data && data.windowTitle) || 'Telegram';
sameOrigin = !!(data && data.sameOrigin);
frameUrl = (data && data.url) || 'about:blank';
frameOrigin = sameOrigin ? originFromUrl(frameUrl) : '';
createIframe(frameUrl);
renderButtons();
renderMenu();
},
nativeEvent: function(eventType, eventData, token) {
if (!isNativeToken(token) || typeof eventType !== 'string') {
return;
}
if (eventType === 'fullscreen_changed' && eventData) {
shellState.isFullscreen = !!eventData.is_fullscreen;
root.classList.toggle('fullscreen', shellState.isFullscreen);
updateFooter();
scheduleDragRegions();
}
postToFrame(eventType, eventData || {});
},
setTitle: function(data, token) {
if (!isNativeToken(token)) {
return;
}
title.textContent = (data && data.title) || '';
document.title = (data && data.windowTitle) || 'Telegram';
},
setChrome: function(data, token) {
if (!isNativeToken(token)) {
return;
}
applyChrome(data || {});
},
setColors: function(data, token) {
if (!isNativeToken(token)) {
return;
}
applyColors(data || {});
},
setAssets: function(data, token) {
if (!isNativeToken(token)) {
return;
}
applyAssets(data || {});
},
setMenu: function(data, token) {
if (!isNativeToken(token)) {
return;
}
shellState.menuItems = Array.isArray(data && data.items)
? data.items
: [];
applyChrome({});
renderMenu();
},
setButton: function(data, token) {
if (!isNativeToken(token)) {
return;
}
if (!data || !data.name) {
return;
}
const previous = shellState.buttons[data.name] || {};
const next = Object.assign({}, previous, data);
if (next.iconGeneration !== previous.iconGeneration
|| next.iconCustomEmojiId !== previous.iconCustomEmojiId) {
next.iconRequestGeneration = '';
next.iconResolvedGeneration = '';
next.iconUrl = '';
}
if (!next.iconCustomEmojiId) {
next.iconRequestGeneration = '';
next.iconResolvedGeneration = next.iconGeneration || '';
next.iconUrl = '';
}
shellState.buttons[data.name] = next;
renderButtons();
},
setButtonIcon: function(data, token) {
if (!isNativeToken(token)) {
return;
}
if (!data || !data.name) {
return;
}
const state = shellState.buttons[data.name];
if (!state || state.iconGeneration !== (data.generation || '')) {
return;
}
const icon = (data.icon && typeof data.icon === 'object')
? data.icon
: null;
state.iconRequestGeneration = '';
state.iconResolvedGeneration = state.iconGeneration;
state.iconUrl = (icon && icon.url) ? icon.url : '';
renderButtons();
},
reloadFrame: function(data, token) {
if (!isNativeToken(token)) {
return;
}
reloadFrame();
},
sendViewport: function(data, token) {
if (!isNativeToken(token)) {
return;
}
sentViewportHeight = -1;
scheduleViewport();
}
};
Object.defineProperty(window, 'TelegramDesktopShell', {
value: Object.freeze(api),
configurable: false,
writable: false
});
})();