@wowlabtech/mini-app-adapter 0.2.2 → 0.2.4

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.cjs CHANGED
@@ -479,7 +479,7 @@ async function startHtml5Qrcode() {
479
479
  if (typeof document === "undefined") {
480
480
  throw new Error("QR scanning requires a browser environment.");
481
481
  }
482
- const [{ Html5Qrcode: Html5Qrcode2 }] = await Promise.all([import("html5-qrcode")]);
482
+ const [{ Html5Qrcode }] = await Promise.all([import("html5-qrcode")]);
483
483
  return new Promise((resolve, reject) => {
484
484
  const elementId = `native-shell-qr-${Date.now()}`;
485
485
  const overlay = document.createElement("div");
@@ -516,7 +516,7 @@ async function startHtml5Qrcode() {
516
516
  const previousOverflow = document.body.style.overflow;
517
517
  document.body.style.overflow = "hidden";
518
518
  let disposed = false;
519
- const scanner = new Html5Qrcode2(elementId);
519
+ const scanner = new Html5Qrcode(elementId);
520
520
  const cleanup = async (result, error) => {
521
521
  if (disposed) {
522
522
  return;
@@ -638,7 +638,8 @@ var BaseMiniAppAdapter = class {
638
638
  async openExternalLink(url) {
639
639
  window.open(url, "_blank", "noopener,noreferrer");
640
640
  }
641
- async openInternalLink(_url) {
641
+ async openInternalLink(url) {
642
+ window.open(url, "_self", "noopener,noreferrer");
642
643
  }
643
644
  async closeApp() {
644
645
  if (window.history.length > 1) {
@@ -864,6 +865,10 @@ var MaxMiniAppAdapter = class extends BaseMiniAppAdapter {
864
865
  return Boolean(bridge2?.BackButton?.onClick);
865
866
  case "backButtonVisibility":
866
867
  return Boolean(bridge2?.BackButton?.show && bridge2.BackButton.hide);
868
+ case "openInternalLink":
869
+ return typeof bridge2?.openMaxLink === "function";
870
+ case "downloadFile":
871
+ return typeof bridge2?.downloadFile === "function";
867
872
  case "requestPhone":
868
873
  if (!bridge2) {
869
874
  return false;
@@ -918,6 +923,14 @@ var MaxMiniAppAdapter = class extends BaseMiniAppAdapter {
918
923
  }
919
924
  await super.openExternalLink(url);
920
925
  }
926
+ async openInternalLink(url) {
927
+ const bridge2 = getMaxBridge();
928
+ if (bridge2?.openMaxLink) {
929
+ bridge2.openMaxLink(url);
930
+ return;
931
+ }
932
+ await super.openInternalLink(url);
933
+ }
921
934
  async closeApp() {
922
935
  const bridge2 = getMaxBridge();
923
936
  if (bridge2?.close) {
@@ -1314,6 +1327,30 @@ var TelegramMiniAppAdapter = class extends BaseMiniAppAdapter {
1314
1327
  return import_sdk_react.backButton.hide.isSupported();
1315
1328
  case "bindCssVariables":
1316
1329
  return true;
1330
+ case "openInternalLink":
1331
+ return true;
1332
+ case "requestFullscreen":
1333
+ return Boolean(
1334
+ typeof import_sdk.viewport.requestFullscreen === "function" || import_sdk_react.viewport.requestFullscreen?.isAvailable?.()
1335
+ );
1336
+ case "verticalSwipes":
1337
+ return Boolean(
1338
+ import_sdk.swipeBehavior.enableVertical.isAvailable() || import_sdk.swipeBehavior.disableVertical.isAvailable()
1339
+ );
1340
+ case "viewVisibility":
1341
+ return true;
1342
+ case "shareUrl":
1343
+ return typeof import_sdk.shareURL === "function";
1344
+ case "shareStory":
1345
+ return typeof import_sdk.shareStory === "function";
1346
+ case "copyTextToClipboard":
1347
+ return typeof import_sdk.copyTextToClipboard === "function";
1348
+ case "downloadFile":
1349
+ return typeof import_sdk.downloadFile === "function";
1350
+ case "addToHomeScreen":
1351
+ return typeof import_sdk.addToHomeScreen?.isAvailable === "function" ? import_sdk.addToHomeScreen.isAvailable() : typeof import_sdk.addToHomeScreen === "function";
1352
+ case "checkHomeScreenStatus":
1353
+ return typeof import_sdk.checkHomeScreenStatus === "function";
1317
1354
  case "requestPhone": {
1318
1355
  return Boolean(isFeatureAvailable(import_sdk.requestPhoneAccess) || isFeatureAvailable(import_sdk.requestContact));
1319
1356
  }
@@ -1559,7 +1596,15 @@ var TelegramMiniAppAdapter = class extends BaseMiniAppAdapter {
1559
1596
  await super.downloadFile(url, filename);
1560
1597
  }
1561
1598
  async shareStory(mediaUrl, options) {
1562
- (0, import_sdk.shareStory)(mediaUrl, options);
1599
+ const text = options?.telegram?.text ?? options?.text;
1600
+ const widgetLink = options?.telegram?.widgetLink ?? (options?.link ? {
1601
+ url: options.link.url,
1602
+ ...options.link.name ? { name: options.link.name } : {}
1603
+ } : void 0);
1604
+ (0, import_sdk.shareStory)(mediaUrl, {
1605
+ ...text ? { text } : {},
1606
+ ...widgetLink ? { widgetLink } : {}
1607
+ });
1563
1608
  }
1564
1609
  async addToHomeScreen() {
1565
1610
  const isAvailable = typeof import_sdk.addToHomeScreen?.isAvailable === "function" ? import_sdk.addToHomeScreen.isAvailable() : true;
@@ -1914,17 +1959,41 @@ var VKMiniAppAdapter = class extends BaseMiniAppAdapter {
1914
1959
  };
1915
1960
  }
1916
1961
  async supports(capability) {
1917
- if (capability === "requestPhone") {
1918
- const [supportsPhoneNumber, supportsPersonalCard] = await Promise.all([
1919
- this.supportsBridgeMethod("VKWebAppGetPhoneNumber"),
1920
- this.supportsBridgeMethod("VKWebAppGetPersonalCard")
1921
- ]);
1922
- return supportsPhoneNumber || supportsPersonalCard;
1923
- }
1924
- if (capability === "notifications") {
1925
- return this.supportsBridgeMethod("VKWebAppAllowNotifications");
1962
+ switch (capability) {
1963
+ case "haptics": {
1964
+ const [impact, notification, selection] = await Promise.all([
1965
+ this.supportsBridgeMethod("VKWebAppTapticImpactOccurred"),
1966
+ this.supportsBridgeMethod("VKWebAppTapticNotificationOccurred"),
1967
+ this.supportsBridgeMethod("VKWebAppTapticSelectionChanged")
1968
+ ]);
1969
+ return impact || notification || selection;
1970
+ }
1971
+ case "qrScanner":
1972
+ return this.supportsBridgeMethod("VKWebAppOpenCodeReader");
1973
+ case "requestPhone": {
1974
+ const [supportsPhoneNumber, supportsPersonalCard] = await Promise.all([
1975
+ this.supportsBridgeMethod("VKWebAppGetPhoneNumber"),
1976
+ this.supportsBridgeMethod("VKWebAppGetPersonalCard")
1977
+ ]);
1978
+ return supportsPhoneNumber || supportsPersonalCard;
1979
+ }
1980
+ case "notifications":
1981
+ return this.supportsBridgeMethod("VKWebAppAllowNotifications");
1982
+ case "shareUrl":
1983
+ return this.supportsBridgeMethod("VKWebAppShare");
1984
+ case "shareStory":
1985
+ return this.supportsBridgeMethod("VKWebAppShowStoryBox");
1986
+ case "downloadFile":
1987
+ return this.supportsBridgeMethod("VKWebAppDownloadFile");
1988
+ case "addToHomeScreen":
1989
+ return this.supportsBridgeMethod("VKWebAppAddToHomeScreen");
1990
+ case "denyNotifications":
1991
+ return this.supportsBridgeMethod("VKWebAppDenyNotifications");
1992
+ case "viewVisibility":
1993
+ return true;
1994
+ default:
1995
+ return await super.supports(capability);
1926
1996
  }
1927
- return await super.supports(capability);
1928
1997
  }
1929
1998
  async requestPhone() {
1930
1999
  const [supportsPhoneNumber, supportsPersonalCard] = await Promise.all([
@@ -2026,9 +2095,34 @@ var VKMiniAppAdapter = class extends BaseMiniAppAdapter {
2026
2095
  };
2027
2096
  }
2028
2097
  async shareStory(mediaUrl, _options) {
2098
+ const options = _options;
2099
+ const vkOptions = options?.vk;
2100
+ const fallbackAttachment = options?.link ? {
2101
+ type: "url",
2102
+ text: "open",
2103
+ url: options.link.url
2104
+ } : void 0;
2105
+ const fallbackStickers = options?.text ? [{
2106
+ sticker_type: "native",
2107
+ sticker: {
2108
+ action_type: "text",
2109
+ action: {
2110
+ text: options.text,
2111
+ style: "classic",
2112
+ background_style: "none"
2113
+ },
2114
+ transform: {
2115
+ gravity: "center_bottom",
2116
+ translation_y: -0.2
2117
+ }
2118
+ }
2119
+ }] : void 0;
2029
2120
  const bridgeOptions = {
2030
- background_type: "image",
2031
- url: mediaUrl
2121
+ background_type: vkOptions?.backgroundType ?? "image",
2122
+ url: mediaUrl,
2123
+ locked: vkOptions?.locked ?? true,
2124
+ ...vkOptions?.attachment ?? fallbackAttachment ? { attachment: vkOptions?.attachment ?? fallbackAttachment } : {},
2125
+ ...vkOptions?.stickers ?? fallbackStickers ? { stickers: vkOptions?.stickers ?? fallbackStickers } : {}
2032
2126
  };
2033
2127
  await import_vk_bridge.default.send("VKWebAppShowStoryBox", bridgeOptions);
2034
2128
  }
@@ -2401,7 +2495,7 @@ var VKMiniAppAdapter = class extends BaseMiniAppAdapter {
2401
2495
  };
2402
2496
 
2403
2497
  // src/adapters/webAdapter.ts
2404
- var import_html5_qrcode = require("html5-qrcode");
2498
+ var import_jsqr = __toESM(require("jsqr"), 1);
2405
2499
  var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2406
2500
  constructor() {
2407
2501
  super("web", {
@@ -2417,6 +2511,22 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2417
2511
  });
2418
2512
  }
2419
2513
  }
2514
+ supports(capability) {
2515
+ switch (capability) {
2516
+ case "copyTextToClipboard":
2517
+ return typeof navigator !== "undefined" && Boolean(navigator.clipboard?.writeText);
2518
+ case "downloadFile":
2519
+ return typeof document !== "undefined";
2520
+ case "shareUrl":
2521
+ return typeof navigator !== "undefined" && (Boolean(navigator.share) || Boolean(navigator.clipboard?.writeText));
2522
+ case "addToHomeScreen":
2523
+ return /android/i.test(navigator.userAgent) && Boolean(this.deferredPrompt);
2524
+ case "checkHomeScreenStatus":
2525
+ return true;
2526
+ default:
2527
+ return super.supports(capability);
2528
+ }
2529
+ }
2420
2530
  async downloadFile(url, filename) {
2421
2531
  try {
2422
2532
  await triggerFileDownload(url, filename, { preferBlob: true });
@@ -2471,14 +2581,31 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2471
2581
  closeBtn.style.cursor = "pointer";
2472
2582
  closeBtn.style.zIndex = "9999999999";
2473
2583
  overlay.appendChild(closeBtn);
2584
+ const scanSize = Math.min(Math.floor(Math.min(window.innerWidth, window.innerHeight) * 0.72), 320);
2585
+ const scanBox = document.createElement("div");
2586
+ scanBox.style.width = `${scanSize}px`;
2587
+ scanBox.style.height = `${scanSize}px`;
2588
+ scanBox.style.position = "relative";
2589
+ scanBox.style.flex = "0 0 auto";
2590
+ scanBox.style.borderRadius = "18px";
2591
+ scanBox.style.overflow = "hidden";
2592
+ overlay.appendChild(scanBox);
2474
2593
  const scanArea = document.createElement("div");
2475
- scanArea.id = "qr-reader";
2476
- scanArea.style.width = "300px";
2477
- scanArea.style.height = "300px";
2478
- scanArea.style.borderRadius = "18px";
2479
- scanArea.style.overflow = "hidden";
2480
- scanArea.style.position = "relative";
2481
- overlay.appendChild(scanArea);
2594
+ scanArea.style.position = "absolute";
2595
+ scanArea.style.inset = "0";
2596
+ scanArea.style.zIndex = "1";
2597
+ scanArea.style.background = "#000";
2598
+ scanBox.appendChild(scanArea);
2599
+ const video = document.createElement("video");
2600
+ video.setAttribute("playsinline", "true");
2601
+ video.autoplay = true;
2602
+ video.muted = true;
2603
+ video.style.width = "100%";
2604
+ video.style.height = "100%";
2605
+ video.style.objectFit = "cover";
2606
+ video.style.position = "absolute";
2607
+ video.style.inset = "0";
2608
+ scanArea.appendChild(video);
2482
2609
  const frame = document.createElement("div");
2483
2610
  frame.style.position = "absolute";
2484
2611
  frame.style.top = "0";
@@ -2488,8 +2615,8 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2488
2615
  frame.style.border = "3px solid rgba(255,255,255,0.9)";
2489
2616
  frame.style.borderRadius = "18px";
2490
2617
  frame.style.pointerEvents = "none";
2491
- frame.style.zIndex = "10";
2492
- scanArea.appendChild(frame);
2618
+ frame.style.zIndex = "3";
2619
+ scanBox.appendChild(frame);
2493
2620
  const line = document.createElement("div");
2494
2621
  line.style.position = "absolute";
2495
2622
  line.style.left = "0";
@@ -2498,8 +2625,8 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2498
2625
  line.style.background = "rgba(255,255,255,0.85)";
2499
2626
  line.style.borderRadius = "2px";
2500
2627
  line.style.animation = "qr-line 2s infinite";
2501
- line.style.zIndex = "11";
2502
- scanArea.appendChild(line);
2628
+ line.style.zIndex = "4";
2629
+ scanBox.appendChild(line);
2503
2630
  const styleTag = document.createElement("style");
2504
2631
  styleTag.innerHTML = `
2505
2632
  @keyframes qr-line {
@@ -2516,17 +2643,26 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2516
2643
  hint.style.fontSize = "17px";
2517
2644
  hint.style.opacity = "0.9";
2518
2645
  overlay.appendChild(hint);
2519
- const scanner = new import_html5_qrcode.Html5Qrcode("qr-reader");
2646
+ const canvas = document.createElement("canvas");
2647
+ const context = canvas.getContext("2d", { willReadFrequently: true });
2648
+ let stream = null;
2649
+ let rafId = null;
2650
+ let lastScanAt = 0;
2520
2651
  let closed = false;
2521
2652
  const finalize = async (result) => {
2522
2653
  if (closed) {
2523
2654
  return;
2524
2655
  }
2525
2656
  closed = true;
2526
- try {
2527
- await scanner.stop();
2528
- } catch {
2657
+ if (rafId !== null) {
2658
+ cancelAnimationFrame(rafId);
2659
+ rafId = null;
2660
+ }
2661
+ if (stream) {
2662
+ stream.getTracks().forEach((track) => track.stop());
2663
+ stream = null;
2529
2664
  }
2665
+ video.srcObject = null;
2530
2666
  overlay.remove();
2531
2667
  styleTag.remove();
2532
2668
  document.body.style.overflow = prevOverflow;
@@ -2545,19 +2681,50 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
2545
2681
  };
2546
2682
  closeBtn.onclick = () => closeScanner(null);
2547
2683
  try {
2548
- await scanner.start(
2549
- { facingMode: "environment" },
2550
- {
2551
- fps: 10,
2552
- qrbox: { width: 250, height: 250 },
2553
- aspectRatio: 1
2554
- },
2555
- (decodedText) => {
2556
- closeScanner(decodedText);
2557
- },
2558
- () => {
2684
+ const constraints = [
2685
+ { video: { facingMode: { ideal: "environment" } }, audio: false },
2686
+ { video: { facingMode: "environment" }, audio: false },
2687
+ { video: true, audio: false }
2688
+ ];
2689
+ let lastError = null;
2690
+ for (const constraint of constraints) {
2691
+ try {
2692
+ stream = await navigator.mediaDevices.getUserMedia(constraint);
2693
+ break;
2694
+ } catch (error) {
2695
+ lastError = error;
2559
2696
  }
2560
- );
2697
+ }
2698
+ if (!stream) {
2699
+ throw lastError instanceof Error ? lastError : new Error("Unable to access camera");
2700
+ }
2701
+ video.srcObject = stream;
2702
+ await video.play();
2703
+ const scanFrame = (now) => {
2704
+ if (closed) {
2705
+ return;
2706
+ }
2707
+ if (now - lastScanAt >= 100 && context && video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
2708
+ const width = video.videoWidth;
2709
+ const height = video.videoHeight;
2710
+ if (width > 0 && height > 0) {
2711
+ canvas.width = width;
2712
+ canvas.height = height;
2713
+ context.drawImage(video, 0, 0, width, height);
2714
+ const imageData = context.getImageData(0, 0, width, height);
2715
+ const result = (0, import_jsqr.default)(imageData.data, width, height, {
2716
+ inversionAttempts: "attemptBoth"
2717
+ });
2718
+ if (result?.data) {
2719
+ closeScanner(result.data);
2720
+ return;
2721
+ }
2722
+ lastScanAt = now;
2723
+ }
2724
+ }
2725
+ rafId = requestAnimationFrame(scanFrame);
2726
+ };
2727
+ rafId = requestAnimationFrame(scanFrame);
2561
2728
  } catch (error) {
2562
2729
  console.error("QR Start error", error);
2563
2730
  closeScanner(null);
@@ -2610,6 +2777,8 @@ ${url}` : url;
2610
2777
  };
2611
2778
 
2612
2779
  // src/adapters/index.ts
2780
+ var CONFIRMED_PLATFORM_STORAGE_KEY = "mini-app-adapter:confirmed-platform";
2781
+ var CONFIRMED_PLATFORM_TTL_MS = 30 * 60 * 1e3;
2613
2782
  function detectPlatform() {
2614
2783
  if (typeof window === "undefined") {
2615
2784
  return "web";
@@ -2621,30 +2790,76 @@ function detectPlatform() {
2621
2790
  })();
2622
2791
  const getParam = (name) => searchParams.get(name) ?? hashParams.get(name);
2623
2792
  const hasParam = (...names) => names.some((name) => getParam(name));
2793
+ const readConfirmedPlatform = () => {
2794
+ try {
2795
+ const raw = window.sessionStorage.getItem(CONFIRMED_PLATFORM_STORAGE_KEY);
2796
+ if (!raw) {
2797
+ return null;
2798
+ }
2799
+ const parsed = JSON.parse(raw);
2800
+ if (!parsed?.platform || typeof parsed.ts !== "number") {
2801
+ return null;
2802
+ }
2803
+ if (Date.now() - parsed.ts > CONFIRMED_PLATFORM_TTL_MS) {
2804
+ return null;
2805
+ }
2806
+ return parsed.platform;
2807
+ } catch {
2808
+ return null;
2809
+ }
2810
+ };
2811
+ const persistConfirmedPlatform = (platform) => {
2812
+ if (platform === "web") {
2813
+ return;
2814
+ }
2815
+ try {
2816
+ window.sessionStorage.setItem(
2817
+ CONFIRMED_PLATFORM_STORAGE_KEY,
2818
+ JSON.stringify({ platform, ts: Date.now() })
2819
+ );
2820
+ } catch {
2821
+ }
2822
+ };
2624
2823
  const shellPlatform = readShellPlatform();
2625
2824
  if (shellPlatform) {
2825
+ persistConfirmedPlatform(shellPlatform);
2626
2826
  return shellPlatform;
2627
2827
  }
2628
2828
  const userAgent = navigator.userAgent.toLowerCase();
2629
2829
  const hasNativeBridge = typeof window.NativeBridge?.postMessage === "function";
2630
2830
  if (hasNativeBridge) {
2631
2831
  if (userAgent.includes("android")) {
2832
+ persistConfirmedPlatform("shell_android");
2632
2833
  return "shell_android";
2633
2834
  }
2835
+ persistConfirmedPlatform("shell_ios");
2634
2836
  return "shell_ios";
2635
2837
  }
2636
- if (window.Telegram?.WebApp || hasParam("tgWebAppPlatform", "tgWebAppVersion", "tgWebAppData", "tgWebAppLanguage") || userAgent.includes("telegram")) {
2838
+ const telegramGlobals = window;
2839
+ const hasTelegramGlobal = Boolean(window.Telegram?.WebApp) || typeof telegramGlobals.TelegramWebviewProxy !== "undefined" || typeof telegramGlobals.TelegramGameProxy !== "undefined";
2840
+ const hasTelegramParams = hasParam("tgWebAppPlatform", "tgWebAppVersion", "tgWebAppData", "tgWebAppLanguage");
2841
+ if (hasTelegramGlobal || hasTelegramParams || userAgent.includes("telegram")) {
2842
+ persistConfirmedPlatform("telegram");
2637
2843
  return "telegram";
2638
2844
  }
2639
2845
  if (window.WebApp) {
2846
+ persistConfirmedPlatform("max");
2640
2847
  return "max";
2641
2848
  }
2642
2849
  if (window.MaxMiniApp) {
2850
+ persistConfirmedPlatform("max");
2643
2851
  return "max";
2644
2852
  }
2645
- if (hasParam("vk_app_id", "vk_platform")) {
2853
+ const hasVkParams = hasParam("vk_app_id", "vk_platform", "vk_user_id", "vk_language", "sign");
2854
+ const hasVkUserAgentSignal = userAgent.includes("vkclient") || userAgent.includes("vk-android") || userAgent.includes("vkontakte");
2855
+ if (hasVkParams || hasVkUserAgentSignal) {
2856
+ persistConfirmedPlatform("vk");
2646
2857
  return "vk";
2647
2858
  }
2859
+ const confirmedPlatform = readConfirmedPlatform();
2860
+ if (confirmedPlatform && confirmedPlatform !== "web") {
2861
+ return confirmedPlatform;
2862
+ }
2648
2863
  return "web";
2649
2864
  }
2650
2865
  function createAdapter(input) {
@@ -2845,14 +3060,18 @@ var cachedPlatform = null;
2845
3060
  function getPlatform() {
2846
3061
  const adapter = getActiveAdapter();
2847
3062
  if (adapter) {
2848
- cachedPlatform = adapter.platform;
3063
+ if (adapter.platform !== "web") {
3064
+ cachedPlatform = adapter.platform;
3065
+ }
2849
3066
  return adapter.platform;
2850
3067
  }
2851
- if (cachedPlatform) {
3068
+ if (cachedPlatform && cachedPlatform !== "web") {
2852
3069
  return cachedPlatform;
2853
3070
  }
2854
3071
  const detectedPlatform = detectPlatform();
2855
- cachedPlatform = detectedPlatform;
3072
+ if (detectedPlatform !== "web") {
3073
+ cachedPlatform = detectedPlatform;
3074
+ }
2856
3075
  return detectedPlatform;
2857
3076
  }
2858
3077