@wowlabtech/mini-app-adapter 0.2.2 → 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.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;
@@ -2376,7 +2376,7 @@ var VKMiniAppAdapter = class extends BaseMiniAppAdapter {
2376
2376
  };
2377
2377
 
2378
2378
  // src/adapters/webAdapter.ts
2379
- import { Html5Qrcode } from "html5-qrcode";
2379
+ import jsQR from "jsqr";
2380
2380
  var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2381
2381
  constructor() {
2382
2382
  super("web", {
@@ -2446,14 +2446,31 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2446
2446
  closeBtn.style.cursor = "pointer";
2447
2447
  closeBtn.style.zIndex = "9999999999";
2448
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);
2449
2458
  const scanArea = document.createElement("div");
2450
- scanArea.id = "qr-reader";
2451
- scanArea.style.width = "300px";
2452
- scanArea.style.height = "300px";
2453
- scanArea.style.borderRadius = "18px";
2454
- scanArea.style.overflow = "hidden";
2455
- scanArea.style.position = "relative";
2456
- 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);
2457
2474
  const frame = document.createElement("div");
2458
2475
  frame.style.position = "absolute";
2459
2476
  frame.style.top = "0";
@@ -2463,8 +2480,8 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2463
2480
  frame.style.border = "3px solid rgba(255,255,255,0.9)";
2464
2481
  frame.style.borderRadius = "18px";
2465
2482
  frame.style.pointerEvents = "none";
2466
- frame.style.zIndex = "10";
2467
- scanArea.appendChild(frame);
2483
+ frame.style.zIndex = "3";
2484
+ scanBox.appendChild(frame);
2468
2485
  const line = document.createElement("div");
2469
2486
  line.style.position = "absolute";
2470
2487
  line.style.left = "0";
@@ -2473,8 +2490,8 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2473
2490
  line.style.background = "rgba(255,255,255,0.85)";
2474
2491
  line.style.borderRadius = "2px";
2475
2492
  line.style.animation = "qr-line 2s infinite";
2476
- line.style.zIndex = "11";
2477
- scanArea.appendChild(line);
2493
+ line.style.zIndex = "4";
2494
+ scanBox.appendChild(line);
2478
2495
  const styleTag = document.createElement("style");
2479
2496
  styleTag.innerHTML = `
2480
2497
  @keyframes qr-line {
@@ -2491,17 +2508,26 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2491
2508
  hint.style.fontSize = "17px";
2492
2509
  hint.style.opacity = "0.9";
2493
2510
  overlay.appendChild(hint);
2494
- 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;
2495
2516
  let closed = false;
2496
2517
  const finalize = async (result) => {
2497
2518
  if (closed) {
2498
2519
  return;
2499
2520
  }
2500
2521
  closed = true;
2501
- try {
2502
- await scanner.stop();
2503
- } catch {
2522
+ if (rafId !== null) {
2523
+ cancelAnimationFrame(rafId);
2524
+ rafId = null;
2504
2525
  }
2526
+ if (stream) {
2527
+ stream.getTracks().forEach((track) => track.stop());
2528
+ stream = null;
2529
+ }
2530
+ video.srcObject = null;
2505
2531
  overlay.remove();
2506
2532
  styleTag.remove();
2507
2533
  document.body.style.overflow = prevOverflow;
@@ -2520,19 +2546,50 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2520
2546
  };
2521
2547
  closeBtn.onclick = () => closeScanner(null);
2522
2548
  try {
2523
- await scanner.start(
2524
- { facingMode: "environment" },
2525
- {
2526
- fps: 10,
2527
- qrbox: { width: 250, height: 250 },
2528
- aspectRatio: 1
2529
- },
2530
- (decodedText) => {
2531
- closeScanner(decodedText);
2532
- },
2533
- () => {
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;
2561
+ }
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;
2534
2571
  }
2535
- );
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);
2536
2593
  } catch (error) {
2537
2594
  console.error("QR Start error", error);
2538
2595
  closeScanner(null);
@@ -2585,6 +2642,8 @@ ${url}` : url;
2585
2642
  };
2586
2643
 
2587
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;
2588
2647
  function detectPlatform() {
2589
2648
  if (typeof window === "undefined") {
2590
2649
  return "web";
@@ -2596,30 +2655,76 @@ function detectPlatform() {
2596
2655
  })();
2597
2656
  const getParam = (name) => searchParams.get(name) ?? hashParams.get(name);
2598
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
+ };
2599
2688
  const shellPlatform = readShellPlatform();
2600
2689
  if (shellPlatform) {
2690
+ persistConfirmedPlatform(shellPlatform);
2601
2691
  return shellPlatform;
2602
2692
  }
2603
2693
  const userAgent = navigator.userAgent.toLowerCase();
2604
2694
  const hasNativeBridge = typeof window.NativeBridge?.postMessage === "function";
2605
2695
  if (hasNativeBridge) {
2606
2696
  if (userAgent.includes("android")) {
2697
+ persistConfirmedPlatform("shell_android");
2607
2698
  return "shell_android";
2608
2699
  }
2700
+ persistConfirmedPlatform("shell_ios");
2609
2701
  return "shell_ios";
2610
2702
  }
2611
- 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");
2612
2708
  return "telegram";
2613
2709
  }
2614
2710
  if (window.WebApp) {
2711
+ persistConfirmedPlatform("max");
2615
2712
  return "max";
2616
2713
  }
2617
2714
  if (window.MaxMiniApp) {
2715
+ persistConfirmedPlatform("max");
2618
2716
  return "max";
2619
2717
  }
2620
- 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");
2621
2722
  return "vk";
2622
2723
  }
2724
+ const confirmedPlatform = readConfirmedPlatform();
2725
+ if (confirmedPlatform && confirmedPlatform !== "web") {
2726
+ return confirmedPlatform;
2727
+ }
2623
2728
  return "web";
2624
2729
  }
2625
2730
  function createAdapter(input) {
@@ -2820,14 +2925,18 @@ var cachedPlatform = null;
2820
2925
  function getPlatform() {
2821
2926
  const adapter = getActiveAdapter();
2822
2927
  if (adapter) {
2823
- cachedPlatform = adapter.platform;
2928
+ if (adapter.platform !== "web") {
2929
+ cachedPlatform = adapter.platform;
2930
+ }
2824
2931
  return adapter.platform;
2825
2932
  }
2826
- if (cachedPlatform) {
2933
+ if (cachedPlatform && cachedPlatform !== "web") {
2827
2934
  return cachedPlatform;
2828
2935
  }
2829
2936
  const detectedPlatform = detectPlatform();
2830
- cachedPlatform = detectedPlatform;
2937
+ if (detectedPlatform !== "web") {
2938
+ cachedPlatform = detectedPlatform;
2939
+ }
2831
2940
  return detectedPlatform;
2832
2941
  }
2833
2942