@wowlabtech/mini-app-adapter 0.2.1 → 0.2.3

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/dist/index.d.cts CHANGED
@@ -56,6 +56,10 @@ interface MiniAppViewportInsets {
56
56
  safeArea: MiniAppSafeAreaInsets;
57
57
  contentSafeArea: MiniAppSafeAreaInsets;
58
58
  }
59
+ interface MiniAppViewportState {
60
+ height: number;
61
+ stableHeight: number;
62
+ }
59
63
  interface MiniAppInitOptions {
60
64
  /**
61
65
  * Enables verbose logs for platforms that support it.
@@ -170,6 +174,10 @@ interface MiniAppAdapter {
170
174
  * Requests fullscreen mode if supported by the platform.
171
175
  */
172
176
  requestFullscreen?(): void;
177
+ /**
178
+ * Subscribes to viewport size changes. Returns disposer.
179
+ */
180
+ onViewportChange?(callback: (state: MiniAppViewportState) => void): () => void;
173
181
  /**
174
182
  * Returns viewport safe area insets if supported by the platform.
175
183
  */
@@ -307,6 +315,10 @@ declare abstract class BaseMiniAppAdapter implements MiniAppAdapter {
307
315
  getLaunchParams(): unknown;
308
316
  decodeStartParam(_param: string): unknown;
309
317
  requestFullscreen(): void;
318
+ onViewportChange(callback: (state: {
319
+ height: number;
320
+ stableHeight: number;
321
+ }) => void): () => void;
310
322
  getViewportInsets(): MiniAppViewportInsets | undefined;
311
323
  shareMessage(_message: string): Promise<void>;
312
324
  shareUrl(_url: string, _text: string): void;
@@ -402,6 +414,10 @@ declare class TelegramMiniAppAdapter extends BaseMiniAppAdapter {
402
414
  decodeStartParam(param: string): unknown;
403
415
  requestFullscreen(): void;
404
416
  getViewportInsets(): MiniAppViewportInsets | undefined;
417
+ onViewportChange(callback: (state: {
418
+ height: number;
419
+ stableHeight: number;
420
+ }) => void): () => void;
405
421
  onAppearanceChange(callback: (appearance: 'dark' | 'light' | undefined) => void): () => void;
406
422
  setBackButtonVisibility(visible: boolean): void;
407
423
  enableVerticalSwipes(): void;
@@ -420,6 +436,8 @@ declare class TelegramMiniAppAdapter extends BaseMiniAppAdapter {
420
436
  private prepareViewport;
421
437
  private requestFullscreenInternal;
422
438
  private getViewportMountOptions;
439
+ private safeHeightFromSdk;
440
+ private stableHeightFromSdk;
423
441
  private notifyViewHide;
424
442
  private notifyViewRestore;
425
443
  protected onDestroy(): void;
@@ -536,4 +554,4 @@ declare function trackPixelEvent(event: string, payload?: Record<string, unknown
536
554
 
537
555
  declare function getPlatform(): MiniAppPlatform;
538
556
 
539
- export { AdapterProvider, BaseMiniAppAdapter, type CreateAdapterOptions, MaxMiniAppAdapter, type MiniAppAdapter, type MiniAppCapability, type MiniAppEnvironmentInfo, type MiniAppInitOptions, type MiniAppPlatform, type MiniAppPopupOptions, type MiniAppQrScanOptions, ShellMiniAppAdapter, TelegramMiniAppAdapter, VKMiniAppAdapter, WebMiniAppAdapter, configureVkPixel, createAdapter, createShellAPI, detectPlatform, getActiveAdapter, getPlatform, isShell, isShellAndroid, isShellIOS, readShellPlatform, requestShellPushPermission, shell, storeShellToken, trackConversionEvent, trackPixelEvent, useAdapterTheme, useMiniAppAdapter, useSafeArea };
557
+ export { AdapterProvider, BaseMiniAppAdapter, type CreateAdapterOptions, MaxMiniAppAdapter, type MiniAppAdapter, type MiniAppCapability, type MiniAppEnvironmentInfo, type MiniAppInitOptions, type MiniAppPlatform, type MiniAppPopupOptions, type MiniAppQrScanOptions, type MiniAppViewportState, ShellMiniAppAdapter, TelegramMiniAppAdapter, VKMiniAppAdapter, WebMiniAppAdapter, configureVkPixel, createAdapter, createShellAPI, detectPlatform, getActiveAdapter, getPlatform, isShell, isShellAndroid, isShellIOS, readShellPlatform, requestShellPushPermission, shell, storeShellToken, trackConversionEvent, trackPixelEvent, useAdapterTheme, useMiniAppAdapter, useSafeArea };
package/dist/index.d.ts CHANGED
@@ -56,6 +56,10 @@ interface MiniAppViewportInsets {
56
56
  safeArea: MiniAppSafeAreaInsets;
57
57
  contentSafeArea: MiniAppSafeAreaInsets;
58
58
  }
59
+ interface MiniAppViewportState {
60
+ height: number;
61
+ stableHeight: number;
62
+ }
59
63
  interface MiniAppInitOptions {
60
64
  /**
61
65
  * Enables verbose logs for platforms that support it.
@@ -170,6 +174,10 @@ interface MiniAppAdapter {
170
174
  * Requests fullscreen mode if supported by the platform.
171
175
  */
172
176
  requestFullscreen?(): void;
177
+ /**
178
+ * Subscribes to viewport size changes. Returns disposer.
179
+ */
180
+ onViewportChange?(callback: (state: MiniAppViewportState) => void): () => void;
173
181
  /**
174
182
  * Returns viewport safe area insets if supported by the platform.
175
183
  */
@@ -307,6 +315,10 @@ declare abstract class BaseMiniAppAdapter implements MiniAppAdapter {
307
315
  getLaunchParams(): unknown;
308
316
  decodeStartParam(_param: string): unknown;
309
317
  requestFullscreen(): void;
318
+ onViewportChange(callback: (state: {
319
+ height: number;
320
+ stableHeight: number;
321
+ }) => void): () => void;
310
322
  getViewportInsets(): MiniAppViewportInsets | undefined;
311
323
  shareMessage(_message: string): Promise<void>;
312
324
  shareUrl(_url: string, _text: string): void;
@@ -402,6 +414,10 @@ declare class TelegramMiniAppAdapter extends BaseMiniAppAdapter {
402
414
  decodeStartParam(param: string): unknown;
403
415
  requestFullscreen(): void;
404
416
  getViewportInsets(): MiniAppViewportInsets | undefined;
417
+ onViewportChange(callback: (state: {
418
+ height: number;
419
+ stableHeight: number;
420
+ }) => void): () => void;
405
421
  onAppearanceChange(callback: (appearance: 'dark' | 'light' | undefined) => void): () => void;
406
422
  setBackButtonVisibility(visible: boolean): void;
407
423
  enableVerticalSwipes(): void;
@@ -420,6 +436,8 @@ declare class TelegramMiniAppAdapter extends BaseMiniAppAdapter {
420
436
  private prepareViewport;
421
437
  private requestFullscreenInternal;
422
438
  private getViewportMountOptions;
439
+ private safeHeightFromSdk;
440
+ private stableHeightFromSdk;
423
441
  private notifyViewHide;
424
442
  private notifyViewRestore;
425
443
  protected onDestroy(): void;
@@ -536,4 +554,4 @@ declare function trackPixelEvent(event: string, payload?: Record<string, unknown
536
554
 
537
555
  declare function getPlatform(): MiniAppPlatform;
538
556
 
539
- export { AdapterProvider, BaseMiniAppAdapter, type CreateAdapterOptions, MaxMiniAppAdapter, type MiniAppAdapter, type MiniAppCapability, type MiniAppEnvironmentInfo, type MiniAppInitOptions, type MiniAppPlatform, type MiniAppPopupOptions, type MiniAppQrScanOptions, ShellMiniAppAdapter, TelegramMiniAppAdapter, VKMiniAppAdapter, WebMiniAppAdapter, configureVkPixel, createAdapter, createShellAPI, detectPlatform, getActiveAdapter, getPlatform, isShell, isShellAndroid, isShellIOS, readShellPlatform, requestShellPushPermission, shell, storeShellToken, trackConversionEvent, trackPixelEvent, useAdapterTheme, useMiniAppAdapter, useSafeArea };
557
+ export { AdapterProvider, BaseMiniAppAdapter, type CreateAdapterOptions, MaxMiniAppAdapter, type MiniAppAdapter, type MiniAppCapability, type MiniAppEnvironmentInfo, type MiniAppInitOptions, type MiniAppPlatform, type MiniAppPopupOptions, type MiniAppQrScanOptions, type MiniAppViewportState, ShellMiniAppAdapter, TelegramMiniAppAdapter, VKMiniAppAdapter, WebMiniAppAdapter, configureVkPixel, createAdapter, createShellAPI, detectPlatform, getActiveAdapter, getPlatform, isShell, isShellAndroid, isShellIOS, readShellPlatform, requestShellPushPermission, shell, storeShellToken, trackConversionEvent, trackPixelEvent, useAdapterTheme, useMiniAppAdapter, useSafeArea };
package/dist/index.js CHANGED
@@ -421,7 +421,7 @@ async function startHtml5Qrcode() {
421
421
  if (typeof document === "undefined") {
422
422
  throw new Error("QR scanning requires a browser environment.");
423
423
  }
424
- const [{ Html5Qrcode: Html5Qrcode2 }] = await Promise.all([import("html5-qrcode")]);
424
+ const [{ Html5Qrcode }] = await Promise.all([import("html5-qrcode")]);
425
425
  return new Promise((resolve, reject) => {
426
426
  const elementId = `native-shell-qr-${Date.now()}`;
427
427
  const overlay = document.createElement("div");
@@ -458,7 +458,7 @@ async function startHtml5Qrcode() {
458
458
  const previousOverflow = document.body.style.overflow;
459
459
  document.body.style.overflow = "hidden";
460
460
  let disposed = false;
461
- const scanner = new Html5Qrcode2(elementId);
461
+ const scanner = new Html5Qrcode(elementId);
462
462
  const cleanup = async (result, error) => {
463
463
  if (disposed) {
464
464
  return;
@@ -607,6 +607,27 @@ var BaseMiniAppAdapter = class {
607
607
  }
608
608
  requestFullscreen() {
609
609
  }
610
+ onViewportChange(callback) {
611
+ if (typeof window === "undefined") {
612
+ return () => {
613
+ };
614
+ }
615
+ const fallbackHeight = () => window.visualViewport?.height ?? window.innerHeight;
616
+ const notify = () => {
617
+ const height = fallbackHeight();
618
+ callback({ height, stableHeight: height });
619
+ };
620
+ notify();
621
+ const onResize = () => notify();
622
+ window.visualViewport?.addEventListener("resize", onResize);
623
+ window.visualViewport?.addEventListener("scroll", onResize);
624
+ window.addEventListener("resize", onResize);
625
+ return () => {
626
+ window.visualViewport?.removeEventListener("resize", onResize);
627
+ window.visualViewport?.removeEventListener("scroll", onResize);
628
+ window.removeEventListener("resize", onResize);
629
+ };
630
+ }
610
631
  getViewportInsets() {
611
632
  return void 0;
612
633
  }
@@ -1379,6 +1400,63 @@ var TelegramMiniAppAdapter = class extends BaseMiniAppAdapter {
1379
1400
  return void 0;
1380
1401
  }
1381
1402
  }
1403
+ onViewportChange(callback) {
1404
+ const disposers = [];
1405
+ const fallbackHeight = () => typeof window !== "undefined" ? window.visualViewport?.height ?? window.innerHeight : 0;
1406
+ const notify = (state) => {
1407
+ const heightCandidate = state?.height ?? this.safeHeightFromSdk();
1408
+ const stableCandidate = state?.stableHeight ?? this.stableHeightFromSdk();
1409
+ const height = Number.isFinite(heightCandidate) ? heightCandidate : fallbackHeight();
1410
+ const stableHeight = Number.isFinite(stableCandidate) && stableCandidate > 0 ? stableCandidate : height;
1411
+ callback({ height, stableHeight });
1412
+ };
1413
+ const ensureMounted = async () => {
1414
+ try {
1415
+ await ensureViewportMounted(this.getViewportMountOptions());
1416
+ } catch (error) {
1417
+ console.warn("[tvm-app-adapter] ensureViewportMounted failed:", error);
1418
+ }
1419
+ };
1420
+ void ensureMounted().finally(() => notify());
1421
+ const { sdkViewport } = this.getViewportMountOptions();
1422
+ if (typeof sdkViewport.on === "function") {
1423
+ try {
1424
+ const off2 = sdkViewport.on("change", (next) => notify(next));
1425
+ if (typeof off2 === "function") {
1426
+ disposers.push(off2);
1427
+ }
1428
+ } catch (error) {
1429
+ console.warn("[tvm-app-adapter] viewport.on(change) subscription failed:", error);
1430
+ }
1431
+ }
1432
+ try {
1433
+ if (typeof sdkViewport.height?.sub === "function") {
1434
+ disposers.push(sdkViewport.height.sub(() => notify()));
1435
+ }
1436
+ if (typeof sdkViewport.stableHeight?.sub === "function") {
1437
+ disposers.push(sdkViewport.stableHeight.sub(() => notify()));
1438
+ }
1439
+ } catch (error) {
1440
+ console.warn("[tvm-app-adapter] viewport signal subscriptions failed:", error);
1441
+ }
1442
+ if (typeof window !== "undefined") {
1443
+ const onResize = () => notify();
1444
+ window.visualViewport?.addEventListener("resize", onResize);
1445
+ window.addEventListener("resize", onResize);
1446
+ disposers.push(() => {
1447
+ window.visualViewport?.removeEventListener("resize", onResize);
1448
+ window.removeEventListener("resize", onResize);
1449
+ });
1450
+ }
1451
+ return () => {
1452
+ disposers.forEach((dispose) => {
1453
+ try {
1454
+ dispose();
1455
+ } catch {
1456
+ }
1457
+ });
1458
+ };
1459
+ }
1382
1460
  onAppearanceChange(callback) {
1383
1461
  this.appearanceListeners.add(callback);
1384
1462
  callback(this.environment.appearance);
@@ -1609,6 +1687,26 @@ var TelegramMiniAppAdapter = class extends BaseMiniAppAdapter {
1609
1687
  }
1610
1688
  };
1611
1689
  }
1690
+ safeHeightFromSdk() {
1691
+ try {
1692
+ if (typeof rawViewport.height === "function") {
1693
+ return rawViewport.height();
1694
+ }
1695
+ } catch {
1696
+ return void 0;
1697
+ }
1698
+ return void 0;
1699
+ }
1700
+ stableHeightFromSdk() {
1701
+ try {
1702
+ if (typeof rawViewport.stableHeight === "function") {
1703
+ return rawViewport.stableHeight();
1704
+ }
1705
+ } catch {
1706
+ return void 0;
1707
+ }
1708
+ return void 0;
1709
+ }
1612
1710
  notifyViewHide() {
1613
1711
  for (const listener of this.viewHideListeners) {
1614
1712
  try {
@@ -2278,7 +2376,7 @@ var VKMiniAppAdapter = class extends BaseMiniAppAdapter {
2278
2376
  };
2279
2377
 
2280
2378
  // src/adapters/webAdapter.ts
2281
- import { Html5Qrcode } from "html5-qrcode";
2379
+ import jsQR from "jsqr";
2282
2380
  var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2283
2381
  constructor() {
2284
2382
  super("web", {
@@ -2348,14 +2446,31 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2348
2446
  closeBtn.style.cursor = "pointer";
2349
2447
  closeBtn.style.zIndex = "9999999999";
2350
2448
  overlay.appendChild(closeBtn);
2449
+ const scanSize = Math.min(Math.floor(Math.min(window.innerWidth, window.innerHeight) * 0.72), 320);
2450
+ const scanBox = document.createElement("div");
2451
+ scanBox.style.width = `${scanSize}px`;
2452
+ scanBox.style.height = `${scanSize}px`;
2453
+ scanBox.style.position = "relative";
2454
+ scanBox.style.flex = "0 0 auto";
2455
+ scanBox.style.borderRadius = "18px";
2456
+ scanBox.style.overflow = "hidden";
2457
+ overlay.appendChild(scanBox);
2351
2458
  const scanArea = document.createElement("div");
2352
- scanArea.id = "qr-reader";
2353
- scanArea.style.width = "300px";
2354
- scanArea.style.height = "300px";
2355
- scanArea.style.borderRadius = "18px";
2356
- scanArea.style.overflow = "hidden";
2357
- scanArea.style.position = "relative";
2358
- overlay.appendChild(scanArea);
2459
+ scanArea.style.position = "absolute";
2460
+ scanArea.style.inset = "0";
2461
+ scanArea.style.zIndex = "1";
2462
+ scanArea.style.background = "#000";
2463
+ scanBox.appendChild(scanArea);
2464
+ const video = document.createElement("video");
2465
+ video.setAttribute("playsinline", "true");
2466
+ video.autoplay = true;
2467
+ video.muted = true;
2468
+ video.style.width = "100%";
2469
+ video.style.height = "100%";
2470
+ video.style.objectFit = "cover";
2471
+ video.style.position = "absolute";
2472
+ video.style.inset = "0";
2473
+ scanArea.appendChild(video);
2359
2474
  const frame = document.createElement("div");
2360
2475
  frame.style.position = "absolute";
2361
2476
  frame.style.top = "0";
@@ -2365,8 +2480,8 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2365
2480
  frame.style.border = "3px solid rgba(255,255,255,0.9)";
2366
2481
  frame.style.borderRadius = "18px";
2367
2482
  frame.style.pointerEvents = "none";
2368
- frame.style.zIndex = "10";
2369
- scanArea.appendChild(frame);
2483
+ frame.style.zIndex = "3";
2484
+ scanBox.appendChild(frame);
2370
2485
  const line = document.createElement("div");
2371
2486
  line.style.position = "absolute";
2372
2487
  line.style.left = "0";
@@ -2375,8 +2490,8 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2375
2490
  line.style.background = "rgba(255,255,255,0.85)";
2376
2491
  line.style.borderRadius = "2px";
2377
2492
  line.style.animation = "qr-line 2s infinite";
2378
- line.style.zIndex = "11";
2379
- scanArea.appendChild(line);
2493
+ line.style.zIndex = "4";
2494
+ scanBox.appendChild(line);
2380
2495
  const styleTag = document.createElement("style");
2381
2496
  styleTag.innerHTML = `
2382
2497
  @keyframes qr-line {
@@ -2393,17 +2508,26 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2393
2508
  hint.style.fontSize = "17px";
2394
2509
  hint.style.opacity = "0.9";
2395
2510
  overlay.appendChild(hint);
2396
- const scanner = new Html5Qrcode("qr-reader");
2511
+ const canvas = document.createElement("canvas");
2512
+ const context = canvas.getContext("2d", { willReadFrequently: true });
2513
+ let stream = null;
2514
+ let rafId = null;
2515
+ let lastScanAt = 0;
2397
2516
  let closed = false;
2398
2517
  const finalize = async (result) => {
2399
2518
  if (closed) {
2400
2519
  return;
2401
2520
  }
2402
2521
  closed = true;
2403
- try {
2404
- await scanner.stop();
2405
- } catch {
2522
+ if (rafId !== null) {
2523
+ cancelAnimationFrame(rafId);
2524
+ rafId = null;
2406
2525
  }
2526
+ if (stream) {
2527
+ stream.getTracks().forEach((track) => track.stop());
2528
+ stream = null;
2529
+ }
2530
+ video.srcObject = null;
2407
2531
  overlay.remove();
2408
2532
  styleTag.remove();
2409
2533
  document.body.style.overflow = prevOverflow;
@@ -2422,19 +2546,50 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2422
2546
  };
2423
2547
  closeBtn.onclick = () => closeScanner(null);
2424
2548
  try {
2425
- await scanner.start(
2426
- { facingMode: "environment" },
2427
- {
2428
- fps: 10,
2429
- qrbox: { width: 250, height: 250 },
2430
- aspectRatio: 1
2431
- },
2432
- (decodedText) => {
2433
- closeScanner(decodedText);
2434
- },
2435
- () => {
2549
+ const constraints = [
2550
+ { video: { facingMode: { ideal: "environment" } }, audio: false },
2551
+ { video: { facingMode: "environment" }, audio: false },
2552
+ { video: true, audio: false }
2553
+ ];
2554
+ let lastError = null;
2555
+ for (const constraint of constraints) {
2556
+ try {
2557
+ stream = await navigator.mediaDevices.getUserMedia(constraint);
2558
+ break;
2559
+ } catch (error) {
2560
+ lastError = error;
2436
2561
  }
2437
- );
2562
+ }
2563
+ if (!stream) {
2564
+ throw lastError instanceof Error ? lastError : new Error("Unable to access camera");
2565
+ }
2566
+ video.srcObject = stream;
2567
+ await video.play();
2568
+ const scanFrame = (now) => {
2569
+ if (closed) {
2570
+ return;
2571
+ }
2572
+ if (now - lastScanAt >= 100 && context && video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
2573
+ const width = video.videoWidth;
2574
+ const height = video.videoHeight;
2575
+ if (width > 0 && height > 0) {
2576
+ canvas.width = width;
2577
+ canvas.height = height;
2578
+ context.drawImage(video, 0, 0, width, height);
2579
+ const imageData = context.getImageData(0, 0, width, height);
2580
+ const result = jsQR(imageData.data, width, height, {
2581
+ inversionAttempts: "attemptBoth"
2582
+ });
2583
+ if (result?.data) {
2584
+ closeScanner(result.data);
2585
+ return;
2586
+ }
2587
+ lastScanAt = now;
2588
+ }
2589
+ }
2590
+ rafId = requestAnimationFrame(scanFrame);
2591
+ };
2592
+ rafId = requestAnimationFrame(scanFrame);
2438
2593
  } catch (error) {
2439
2594
  console.error("QR Start error", error);
2440
2595
  closeScanner(null);
@@ -2487,6 +2642,8 @@ ${url}` : url;
2487
2642
  };
2488
2643
 
2489
2644
  // src/adapters/index.ts
2645
+ var CONFIRMED_PLATFORM_STORAGE_KEY = "mini-app-adapter:confirmed-platform";
2646
+ var CONFIRMED_PLATFORM_TTL_MS = 30 * 60 * 1e3;
2490
2647
  function detectPlatform() {
2491
2648
  if (typeof window === "undefined") {
2492
2649
  return "web";
@@ -2498,30 +2655,76 @@ function detectPlatform() {
2498
2655
  })();
2499
2656
  const getParam = (name) => searchParams.get(name) ?? hashParams.get(name);
2500
2657
  const hasParam = (...names) => names.some((name) => getParam(name));
2658
+ const readConfirmedPlatform = () => {
2659
+ try {
2660
+ const raw = window.sessionStorage.getItem(CONFIRMED_PLATFORM_STORAGE_KEY);
2661
+ if (!raw) {
2662
+ return null;
2663
+ }
2664
+ const parsed = JSON.parse(raw);
2665
+ if (!parsed?.platform || typeof parsed.ts !== "number") {
2666
+ return null;
2667
+ }
2668
+ if (Date.now() - parsed.ts > CONFIRMED_PLATFORM_TTL_MS) {
2669
+ return null;
2670
+ }
2671
+ return parsed.platform;
2672
+ } catch {
2673
+ return null;
2674
+ }
2675
+ };
2676
+ const persistConfirmedPlatform = (platform) => {
2677
+ if (platform === "web") {
2678
+ return;
2679
+ }
2680
+ try {
2681
+ window.sessionStorage.setItem(
2682
+ CONFIRMED_PLATFORM_STORAGE_KEY,
2683
+ JSON.stringify({ platform, ts: Date.now() })
2684
+ );
2685
+ } catch {
2686
+ }
2687
+ };
2501
2688
  const shellPlatform = readShellPlatform();
2502
2689
  if (shellPlatform) {
2690
+ persistConfirmedPlatform(shellPlatform);
2503
2691
  return shellPlatform;
2504
2692
  }
2505
2693
  const userAgent = navigator.userAgent.toLowerCase();
2506
2694
  const hasNativeBridge = typeof window.NativeBridge?.postMessage === "function";
2507
2695
  if (hasNativeBridge) {
2508
2696
  if (userAgent.includes("android")) {
2697
+ persistConfirmedPlatform("shell_android");
2509
2698
  return "shell_android";
2510
2699
  }
2700
+ persistConfirmedPlatform("shell_ios");
2511
2701
  return "shell_ios";
2512
2702
  }
2513
- if (window.Telegram?.WebApp || hasParam("tgWebAppPlatform", "tgWebAppVersion", "tgWebAppData", "tgWebAppLanguage") || userAgent.includes("telegram")) {
2703
+ const telegramGlobals = window;
2704
+ const hasTelegramGlobal = Boolean(window.Telegram?.WebApp) || typeof telegramGlobals.TelegramWebviewProxy !== "undefined" || typeof telegramGlobals.TelegramGameProxy !== "undefined";
2705
+ const hasTelegramParams = hasParam("tgWebAppPlatform", "tgWebAppVersion", "tgWebAppData", "tgWebAppLanguage");
2706
+ if (hasTelegramGlobal || hasTelegramParams || userAgent.includes("telegram")) {
2707
+ persistConfirmedPlatform("telegram");
2514
2708
  return "telegram";
2515
2709
  }
2516
2710
  if (window.WebApp) {
2711
+ persistConfirmedPlatform("max");
2517
2712
  return "max";
2518
2713
  }
2519
2714
  if (window.MaxMiniApp) {
2715
+ persistConfirmedPlatform("max");
2520
2716
  return "max";
2521
2717
  }
2522
- if (hasParam("vk_app_id", "vk_platform")) {
2718
+ const hasVkParams = hasParam("vk_app_id", "vk_platform", "vk_user_id", "vk_language", "sign");
2719
+ const hasVkUserAgentSignal = userAgent.includes("vkclient") || userAgent.includes("vk-android") || userAgent.includes("vkontakte");
2720
+ if (hasVkParams || hasVkUserAgentSignal) {
2721
+ persistConfirmedPlatform("vk");
2523
2722
  return "vk";
2524
2723
  }
2724
+ const confirmedPlatform = readConfirmedPlatform();
2725
+ if (confirmedPlatform && confirmedPlatform !== "web") {
2726
+ return confirmedPlatform;
2727
+ }
2525
2728
  return "web";
2526
2729
  }
2527
2730
  function createAdapter(input) {
@@ -2722,14 +2925,18 @@ var cachedPlatform = null;
2722
2925
  function getPlatform() {
2723
2926
  const adapter = getActiveAdapter();
2724
2927
  if (adapter) {
2725
- cachedPlatform = adapter.platform;
2928
+ if (adapter.platform !== "web") {
2929
+ cachedPlatform = adapter.platform;
2930
+ }
2726
2931
  return adapter.platform;
2727
2932
  }
2728
- if (cachedPlatform) {
2933
+ if (cachedPlatform && cachedPlatform !== "web") {
2729
2934
  return cachedPlatform;
2730
2935
  }
2731
2936
  const detectedPlatform = detectPlatform();
2732
- cachedPlatform = detectedPlatform;
2937
+ if (detectedPlatform !== "web") {
2938
+ cachedPlatform = detectedPlatform;
2939
+ }
2733
2940
  return detectedPlatform;
2734
2941
  }
2735
2942