Skip to content

Instantly share code, notes, and snippets.

@milksense
Created June 10, 2025 13:00
Show Gist options
  • Select an option

  • Save milksense/60a875f8e7e4ea1a2b5c6e83f530cde8 to your computer and use it in GitHub Desktop.

Select an option

Save milksense/60a875f8e7e4ea1a2b5c6e83f530cde8 to your computer and use it in GitHub Desktop.
Max bridge (beta)
var d = Object.defineProperty;
var v = (o, e, t) => e in o ? d(o, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : o[e] = t;
var n = (o, e, t) => v(o, typeof e != "symbol" ? e + "" : e, t);
class w {
constructor({
postBackButtonEvent: e,
onClick: t,
offClick: s
}) {
n(this, "visible", !1);
n(this, "postBackButtonEvent");
n(this, "onClick");
n(this, "offClick");
this.postBackButtonEvent = e, this.onClick = t, this.offClick = s;
}
get isVisible() {
return this.visible;
}
show() {
this.visible = !0, this.postBackButtonEvent(!0);
}
hide() {
this.visible = !1, this.postBackButtonEvent(!1);
}
}
class u {
constructor({
postSwipesBehaviorEvent: e
}) {
n(this, "enabled", !0);
n(this, "postSwipesBehaviorEvent");
this.postSwipesBehaviorEvent = e;
}
get isEnabled() {
return this.enabled;
}
enable() {
this.enabled = !0, this.postSwipesBehaviorEvent(!0);
}
disable() {
this.enabled = !1, this.postSwipesBehaviorEvent(!1);
}
}
const p = class p {
constructor() {
n(this, "rawInitData");
const e = this.extractInitData();
if (e)
this.rawInitData = e, sessionStorage.setItem(p.WEB_APP_DATA_KEY, e);
else {
const t = sessionStorage.getItem(p.WEB_APP_DATA_KEY);
this.rawInitData = t;
}
}
/**
* Геттер для получения сырых данных инициализации.
*/
get initData() {
return this.rawInitData;
}
/**
* Геттер для получения распарсенных данных инициализации.
*/
get initDataUnsafe() {
return this.parseInitData(this.rawInitData);
}
extractInitData() {
try {
const e = location.hash.toString().replace(/^#/, ""), s = new URLSearchParams(e).get(p.WEB_APP_DATA_KEY);
if (s) return s;
const r = performance.getEntriesByType("navigation")[0], i = r && new URL(r.name).searchParams.get(p.WEB_APP_DATA_KEY);
return i || null;
} catch (e) {
return console.warn("Ошибка при извлечении init data:", e), null;
}
}
getUserInitData(e) {
try {
const t = JSON.parse(e);
if (t && typeof t == "object")
return {
first_name: t.first_name,
last_name: t.last_name,
username: t.username,
language_code: t.language_code,
photo_url: t.photo_url,
id: Number(t.id)
};
} catch (t) {
console.warn("Ошибка при парсинге данных пользователя:", t);
}
}
getChatInitData(e) {
try {
const t = JSON.parse(e);
if (t && typeof t == "object")
return {
id: t.id,
type: t.type
};
} catch (t) {
console.warn("Ошибка при парсинге данных чата:", t);
}
}
parseInitData(e) {
const t = {};
if (!e)
return t;
try {
const s = decodeURIComponent(e);
new URLSearchParams(s).forEach((i, a) => {
switch (a) {
case "hash":
case "ip":
case "query_id": {
t[a] = i;
break;
}
case "auth_date": {
t[a] = Number(i);
break;
}
case "user": {
t[a] = this.getUserInitData(i);
break;
}
case "chat": {
t[a] = this.getChatInitData(i);
break;
}
default:
console.warn(`Неизвестный параметр: ${a}`);
}
});
} catch (s) {
console.warn("Ошибка при парсинге init data:", s);
}
return t;
}
};
n(p, "WEB_APP_DATA_KEY", "WebAppData");
let h = p;
const g = 1e4, f = /^https:\/\/.*\.(?:max|oneme)\.ru$/;
class E {
constructor() {
n(this, "eventHandlers", /* @__PURE__ */ new Map());
// Храним запросы, ожидающие ответа
n(this, "pendingRequests", /* @__PURE__ */ new Map());
n(this, "webviewBridge");
n(this, "swipesBehaviorManager");
n(this, "initDataManager");
n(this, "BackButton");
this.isIframe() ? window.addEventListener("message", async (e) => {
if (!(!f.test(e.origin) || typeof e.data != "string"))
try {
const { type: t, ...s } = JSON.parse(e.data);
t != null && t.startsWith("WebApp") && await this.receiveEvent(t, s);
} catch (t) {
console.error("Ошибка при обработке сообщения:", t);
}
}) : this.webviewBridge = window.WebViewHandler, this.BackButton = new w({
postBackButtonEvent: (e) => this.postEvent("WebAppSetupBackButton", { isVisible: e }),
onClick: (e) => this.onEvent("WebAppBackButtonPressed", e),
offClick: (e) => this.offEvent("WebAppBackButtonPressed", e)
}), this.swipesBehaviorManager = new u({
postSwipesBehaviorEvent: (e) => this.postEvent("WebAppSetupSwipesBehavior", { allowVerticalSwipes: e })
}), this.initDataManager = new h();
}
/**
* Сырые данные инициализации
*/
get initData() {
return this.initDataManager.initData;
}
/**
* Распарсенные данные инициализации
*/
get initDataUnsafe() {
return this.initDataManager.initDataUnsafe;
}
/**
* Состояние вертикальных свайпов
*/
get isVerticalSwipesEnabled() {
return this.swipesBehaviorManager.isEnabled;
}
isIframe() {
return window.self !== window.top;
}
/**
* Стандартная отправка событий (без ожидания ответа)
*/
postEvent(e, t = {}, s) {
this.sendEventByClientType(e, t), s == null || s();
}
/**
* Отправка события с ожиданием ответа
*/
postEventAsync(e, t = {}) {
return new Promise((s, r) => {
const i = crypto.randomUUID();
this.pendingRequests.set(i, { resolve: s, reject: r }), this.sendEventByClientType(e, { ...t, requestId: i }), setTimeout(r, g);
});
}
/**
* Отправка события в зависимости от типа
*/
sendEventByClientType(e, t) {
this.isIframe() ? window.parent.postMessage(JSON.stringify({ type: e, ...t }), "*") : this.webviewBridge && this.webviewBridge.postEvent(e, JSON.stringify(t));
}
/**
* Обработка полученного события
*/
async receiveEvent(e, { requestId: t, ...s }) {
var r, i;
if (t && "error" in s)
console.log(`[WebApp] Получена ошибка: ${e}`, s), (r = this.pendingRequests.get(t)) == null || r.reject(s), this.pendingRequests.delete(t);
else if (t && !("error" in s))
console.log(`[WebApp] Получено событие: ${e}`, s), (i = this.pendingRequests.get(t)) == null || i.resolve(s), this.pendingRequests.delete(t);
else {
const a = this.eventHandlers.get(e);
a == null || a.forEach((c) => {
try {
c(s);
} catch (l) {
console.error(`Ошибка в обработке события "${e}":`, l);
}
});
}
}
/**
* Функция, которую используют нативные клиенты для отправки ответа
*/
async sendEvent(e, t = "{}") {
try {
this.receiveEvent(e, JSON.parse(t));
} catch (s) {
console.warn(s);
}
}
/**
* Подписка на событие с использованием колбэка
*/
onEvent(e, t) {
let s = this.eventHandlers.get(e);
return s || (s = /* @__PURE__ */ new Set(), this.eventHandlers.set(e, s)), s.add(t), () => {
this.offEvent(e, t);
};
}
/**
* Удаление подписки на событие
*/
offEvent(e, t) {
const s = this.eventHandlers.get(e);
s && (s.delete(t), s.size === 0 && this.eventHandlers.delete(e));
}
/**
* Закрытие приложения
*/
close() {
this.postEvent("WebAppClose", {}, () => {
console.log("Приложение закрыто");
});
}
/**
* Инициализация WebApp API
*/
ready() {
this.postEvent("WebAppReady", {}, () => {
console.log("WebApp готово к работе");
});
}
/**
* Запрос номера телефона
*/
requestContact() {
return this.postEventAsync("WebAppRequestPhone");
}
/**
* Подтверждать закрытие миниаппа с помощью всплывающего окна
*/
enableClosingConfirmation() {
this.postEvent("WebAppSetupClosingBehavior", { needConfirmation: !0 });
}
/**
* Отключение подтверждения закрытия миниаппа
*/
disableClosingConfirmation() {
this.postEvent("WebAppSetupClosingBehavior", { needConfirmation: !1 });
}
/**
* Открытие ссылки во внешнем браузере
*/
openLink(e) {
this.postEvent("WebAppOpenLink", { url: e });
}
/**
* Открытие диплинка связанного с max.ru
*/
openMaxLink(e) {
this.postEvent("WebAppOpenMaxLink", { url: e });
}
/**
* Включение вертикальных свайпов
*/
enableVerticalSwipes() {
this.swipesBehaviorManager.enable();
}
/**
* Отключение вертикальных свайпов
*/
disableVerticalSwipes() {
this.swipesBehaviorManager.disable();
}
}
window.WebApp = new E();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment