@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.cjs +242 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +242 -35
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
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
|
|
519
|
+
const scanner = new Html5Qrcode(elementId);
|
|
520
520
|
const cleanup = async (result, error) => {
|
|
521
521
|
if (disposed) {
|
|
522
522
|
return;
|
|
@@ -665,6 +665,27 @@ var BaseMiniAppAdapter = class {
|
|
|
665
665
|
}
|
|
666
666
|
requestFullscreen() {
|
|
667
667
|
}
|
|
668
|
+
onViewportChange(callback) {
|
|
669
|
+
if (typeof window === "undefined") {
|
|
670
|
+
return () => {
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
const fallbackHeight = () => window.visualViewport?.height ?? window.innerHeight;
|
|
674
|
+
const notify = () => {
|
|
675
|
+
const height = fallbackHeight();
|
|
676
|
+
callback({ height, stableHeight: height });
|
|
677
|
+
};
|
|
678
|
+
notify();
|
|
679
|
+
const onResize = () => notify();
|
|
680
|
+
window.visualViewport?.addEventListener("resize", onResize);
|
|
681
|
+
window.visualViewport?.addEventListener("scroll", onResize);
|
|
682
|
+
window.addEventListener("resize", onResize);
|
|
683
|
+
return () => {
|
|
684
|
+
window.visualViewport?.removeEventListener("resize", onResize);
|
|
685
|
+
window.visualViewport?.removeEventListener("scroll", onResize);
|
|
686
|
+
window.removeEventListener("resize", onResize);
|
|
687
|
+
};
|
|
688
|
+
}
|
|
668
689
|
getViewportInsets() {
|
|
669
690
|
return void 0;
|
|
670
691
|
}
|
|
@@ -1406,6 +1427,63 @@ var TelegramMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
1406
1427
|
return void 0;
|
|
1407
1428
|
}
|
|
1408
1429
|
}
|
|
1430
|
+
onViewportChange(callback) {
|
|
1431
|
+
const disposers = [];
|
|
1432
|
+
const fallbackHeight = () => typeof window !== "undefined" ? window.visualViewport?.height ?? window.innerHeight : 0;
|
|
1433
|
+
const notify = (state) => {
|
|
1434
|
+
const heightCandidate = state?.height ?? this.safeHeightFromSdk();
|
|
1435
|
+
const stableCandidate = state?.stableHeight ?? this.stableHeightFromSdk();
|
|
1436
|
+
const height = Number.isFinite(heightCandidate) ? heightCandidate : fallbackHeight();
|
|
1437
|
+
const stableHeight = Number.isFinite(stableCandidate) && stableCandidate > 0 ? stableCandidate : height;
|
|
1438
|
+
callback({ height, stableHeight });
|
|
1439
|
+
};
|
|
1440
|
+
const ensureMounted = async () => {
|
|
1441
|
+
try {
|
|
1442
|
+
await ensureViewportMounted(this.getViewportMountOptions());
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
console.warn("[tvm-app-adapter] ensureViewportMounted failed:", error);
|
|
1445
|
+
}
|
|
1446
|
+
};
|
|
1447
|
+
void ensureMounted().finally(() => notify());
|
|
1448
|
+
const { sdkViewport } = this.getViewportMountOptions();
|
|
1449
|
+
if (typeof sdkViewport.on === "function") {
|
|
1450
|
+
try {
|
|
1451
|
+
const off2 = sdkViewport.on("change", (next) => notify(next));
|
|
1452
|
+
if (typeof off2 === "function") {
|
|
1453
|
+
disposers.push(off2);
|
|
1454
|
+
}
|
|
1455
|
+
} catch (error) {
|
|
1456
|
+
console.warn("[tvm-app-adapter] viewport.on(change) subscription failed:", error);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
try {
|
|
1460
|
+
if (typeof sdkViewport.height?.sub === "function") {
|
|
1461
|
+
disposers.push(sdkViewport.height.sub(() => notify()));
|
|
1462
|
+
}
|
|
1463
|
+
if (typeof sdkViewport.stableHeight?.sub === "function") {
|
|
1464
|
+
disposers.push(sdkViewport.stableHeight.sub(() => notify()));
|
|
1465
|
+
}
|
|
1466
|
+
} catch (error) {
|
|
1467
|
+
console.warn("[tvm-app-adapter] viewport signal subscriptions failed:", error);
|
|
1468
|
+
}
|
|
1469
|
+
if (typeof window !== "undefined") {
|
|
1470
|
+
const onResize = () => notify();
|
|
1471
|
+
window.visualViewport?.addEventListener("resize", onResize);
|
|
1472
|
+
window.addEventListener("resize", onResize);
|
|
1473
|
+
disposers.push(() => {
|
|
1474
|
+
window.visualViewport?.removeEventListener("resize", onResize);
|
|
1475
|
+
window.removeEventListener("resize", onResize);
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
return () => {
|
|
1479
|
+
disposers.forEach((dispose) => {
|
|
1480
|
+
try {
|
|
1481
|
+
dispose();
|
|
1482
|
+
} catch {
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1409
1487
|
onAppearanceChange(callback) {
|
|
1410
1488
|
this.appearanceListeners.add(callback);
|
|
1411
1489
|
callback(this.environment.appearance);
|
|
@@ -1636,6 +1714,26 @@ var TelegramMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
1636
1714
|
}
|
|
1637
1715
|
};
|
|
1638
1716
|
}
|
|
1717
|
+
safeHeightFromSdk() {
|
|
1718
|
+
try {
|
|
1719
|
+
if (typeof import_sdk.viewport.height === "function") {
|
|
1720
|
+
return import_sdk.viewport.height();
|
|
1721
|
+
}
|
|
1722
|
+
} catch {
|
|
1723
|
+
return void 0;
|
|
1724
|
+
}
|
|
1725
|
+
return void 0;
|
|
1726
|
+
}
|
|
1727
|
+
stableHeightFromSdk() {
|
|
1728
|
+
try {
|
|
1729
|
+
if (typeof import_sdk.viewport.stableHeight === "function") {
|
|
1730
|
+
return import_sdk.viewport.stableHeight();
|
|
1731
|
+
}
|
|
1732
|
+
} catch {
|
|
1733
|
+
return void 0;
|
|
1734
|
+
}
|
|
1735
|
+
return void 0;
|
|
1736
|
+
}
|
|
1639
1737
|
notifyViewHide() {
|
|
1640
1738
|
for (const listener of this.viewHideListeners) {
|
|
1641
1739
|
try {
|
|
@@ -2303,7 +2401,7 @@ var VKMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
2303
2401
|
};
|
|
2304
2402
|
|
|
2305
2403
|
// src/adapters/webAdapter.ts
|
|
2306
|
-
var
|
|
2404
|
+
var import_jsqr = __toESM(require("jsqr"), 1);
|
|
2307
2405
|
var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
2308
2406
|
constructor() {
|
|
2309
2407
|
super("web", {
|
|
@@ -2373,14 +2471,31 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
2373
2471
|
closeBtn.style.cursor = "pointer";
|
|
2374
2472
|
closeBtn.style.zIndex = "9999999999";
|
|
2375
2473
|
overlay.appendChild(closeBtn);
|
|
2474
|
+
const scanSize = Math.min(Math.floor(Math.min(window.innerWidth, window.innerHeight) * 0.72), 320);
|
|
2475
|
+
const scanBox = document.createElement("div");
|
|
2476
|
+
scanBox.style.width = `${scanSize}px`;
|
|
2477
|
+
scanBox.style.height = `${scanSize}px`;
|
|
2478
|
+
scanBox.style.position = "relative";
|
|
2479
|
+
scanBox.style.flex = "0 0 auto";
|
|
2480
|
+
scanBox.style.borderRadius = "18px";
|
|
2481
|
+
scanBox.style.overflow = "hidden";
|
|
2482
|
+
overlay.appendChild(scanBox);
|
|
2376
2483
|
const scanArea = document.createElement("div");
|
|
2377
|
-
scanArea.
|
|
2378
|
-
scanArea.style.
|
|
2379
|
-
scanArea.style.
|
|
2380
|
-
scanArea.style.
|
|
2381
|
-
scanArea
|
|
2382
|
-
|
|
2383
|
-
|
|
2484
|
+
scanArea.style.position = "absolute";
|
|
2485
|
+
scanArea.style.inset = "0";
|
|
2486
|
+
scanArea.style.zIndex = "1";
|
|
2487
|
+
scanArea.style.background = "#000";
|
|
2488
|
+
scanBox.appendChild(scanArea);
|
|
2489
|
+
const video = document.createElement("video");
|
|
2490
|
+
video.setAttribute("playsinline", "true");
|
|
2491
|
+
video.autoplay = true;
|
|
2492
|
+
video.muted = true;
|
|
2493
|
+
video.style.width = "100%";
|
|
2494
|
+
video.style.height = "100%";
|
|
2495
|
+
video.style.objectFit = "cover";
|
|
2496
|
+
video.style.position = "absolute";
|
|
2497
|
+
video.style.inset = "0";
|
|
2498
|
+
scanArea.appendChild(video);
|
|
2384
2499
|
const frame = document.createElement("div");
|
|
2385
2500
|
frame.style.position = "absolute";
|
|
2386
2501
|
frame.style.top = "0";
|
|
@@ -2390,8 +2505,8 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
2390
2505
|
frame.style.border = "3px solid rgba(255,255,255,0.9)";
|
|
2391
2506
|
frame.style.borderRadius = "18px";
|
|
2392
2507
|
frame.style.pointerEvents = "none";
|
|
2393
|
-
frame.style.zIndex = "
|
|
2394
|
-
|
|
2508
|
+
frame.style.zIndex = "3";
|
|
2509
|
+
scanBox.appendChild(frame);
|
|
2395
2510
|
const line = document.createElement("div");
|
|
2396
2511
|
line.style.position = "absolute";
|
|
2397
2512
|
line.style.left = "0";
|
|
@@ -2400,8 +2515,8 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
2400
2515
|
line.style.background = "rgba(255,255,255,0.85)";
|
|
2401
2516
|
line.style.borderRadius = "2px";
|
|
2402
2517
|
line.style.animation = "qr-line 2s infinite";
|
|
2403
|
-
line.style.zIndex = "
|
|
2404
|
-
|
|
2518
|
+
line.style.zIndex = "4";
|
|
2519
|
+
scanBox.appendChild(line);
|
|
2405
2520
|
const styleTag = document.createElement("style");
|
|
2406
2521
|
styleTag.innerHTML = `
|
|
2407
2522
|
@keyframes qr-line {
|
|
@@ -2418,17 +2533,26 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
2418
2533
|
hint.style.fontSize = "17px";
|
|
2419
2534
|
hint.style.opacity = "0.9";
|
|
2420
2535
|
overlay.appendChild(hint);
|
|
2421
|
-
const
|
|
2536
|
+
const canvas = document.createElement("canvas");
|
|
2537
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
2538
|
+
let stream = null;
|
|
2539
|
+
let rafId = null;
|
|
2540
|
+
let lastScanAt = 0;
|
|
2422
2541
|
let closed = false;
|
|
2423
2542
|
const finalize = async (result) => {
|
|
2424
2543
|
if (closed) {
|
|
2425
2544
|
return;
|
|
2426
2545
|
}
|
|
2427
2546
|
closed = true;
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2547
|
+
if (rafId !== null) {
|
|
2548
|
+
cancelAnimationFrame(rafId);
|
|
2549
|
+
rafId = null;
|
|
2431
2550
|
}
|
|
2551
|
+
if (stream) {
|
|
2552
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
2553
|
+
stream = null;
|
|
2554
|
+
}
|
|
2555
|
+
video.srcObject = null;
|
|
2432
2556
|
overlay.remove();
|
|
2433
2557
|
styleTag.remove();
|
|
2434
2558
|
document.body.style.overflow = prevOverflow;
|
|
@@ -2447,19 +2571,50 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
2447
2571
|
};
|
|
2448
2572
|
closeBtn.onclick = () => closeScanner(null);
|
|
2449
2573
|
try {
|
|
2450
|
-
|
|
2451
|
-
{ facingMode: "environment" },
|
|
2452
|
-
{
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
()
|
|
2574
|
+
const constraints = [
|
|
2575
|
+
{ video: { facingMode: { ideal: "environment" } }, audio: false },
|
|
2576
|
+
{ video: { facingMode: "environment" }, audio: false },
|
|
2577
|
+
{ video: true, audio: false }
|
|
2578
|
+
];
|
|
2579
|
+
let lastError = null;
|
|
2580
|
+
for (const constraint of constraints) {
|
|
2581
|
+
try {
|
|
2582
|
+
stream = await navigator.mediaDevices.getUserMedia(constraint);
|
|
2583
|
+
break;
|
|
2584
|
+
} catch (error) {
|
|
2585
|
+
lastError = error;
|
|
2461
2586
|
}
|
|
2462
|
-
|
|
2587
|
+
}
|
|
2588
|
+
if (!stream) {
|
|
2589
|
+
throw lastError instanceof Error ? lastError : new Error("Unable to access camera");
|
|
2590
|
+
}
|
|
2591
|
+
video.srcObject = stream;
|
|
2592
|
+
await video.play();
|
|
2593
|
+
const scanFrame = (now) => {
|
|
2594
|
+
if (closed) {
|
|
2595
|
+
return;
|
|
2596
|
+
}
|
|
2597
|
+
if (now - lastScanAt >= 100 && context && video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
2598
|
+
const width = video.videoWidth;
|
|
2599
|
+
const height = video.videoHeight;
|
|
2600
|
+
if (width > 0 && height > 0) {
|
|
2601
|
+
canvas.width = width;
|
|
2602
|
+
canvas.height = height;
|
|
2603
|
+
context.drawImage(video, 0, 0, width, height);
|
|
2604
|
+
const imageData = context.getImageData(0, 0, width, height);
|
|
2605
|
+
const result = (0, import_jsqr.default)(imageData.data, width, height, {
|
|
2606
|
+
inversionAttempts: "attemptBoth"
|
|
2607
|
+
});
|
|
2608
|
+
if (result?.data) {
|
|
2609
|
+
closeScanner(result.data);
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2612
|
+
lastScanAt = now;
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
rafId = requestAnimationFrame(scanFrame);
|
|
2616
|
+
};
|
|
2617
|
+
rafId = requestAnimationFrame(scanFrame);
|
|
2463
2618
|
} catch (error) {
|
|
2464
2619
|
console.error("QR Start error", error);
|
|
2465
2620
|
closeScanner(null);
|
|
@@ -2512,6 +2667,8 @@ ${url}` : url;
|
|
|
2512
2667
|
};
|
|
2513
2668
|
|
|
2514
2669
|
// src/adapters/index.ts
|
|
2670
|
+
var CONFIRMED_PLATFORM_STORAGE_KEY = "mini-app-adapter:confirmed-platform";
|
|
2671
|
+
var CONFIRMED_PLATFORM_TTL_MS = 30 * 60 * 1e3;
|
|
2515
2672
|
function detectPlatform() {
|
|
2516
2673
|
if (typeof window === "undefined") {
|
|
2517
2674
|
return "web";
|
|
@@ -2523,30 +2680,76 @@ function detectPlatform() {
|
|
|
2523
2680
|
})();
|
|
2524
2681
|
const getParam = (name) => searchParams.get(name) ?? hashParams.get(name);
|
|
2525
2682
|
const hasParam = (...names) => names.some((name) => getParam(name));
|
|
2683
|
+
const readConfirmedPlatform = () => {
|
|
2684
|
+
try {
|
|
2685
|
+
const raw = window.sessionStorage.getItem(CONFIRMED_PLATFORM_STORAGE_KEY);
|
|
2686
|
+
if (!raw) {
|
|
2687
|
+
return null;
|
|
2688
|
+
}
|
|
2689
|
+
const parsed = JSON.parse(raw);
|
|
2690
|
+
if (!parsed?.platform || typeof parsed.ts !== "number") {
|
|
2691
|
+
return null;
|
|
2692
|
+
}
|
|
2693
|
+
if (Date.now() - parsed.ts > CONFIRMED_PLATFORM_TTL_MS) {
|
|
2694
|
+
return null;
|
|
2695
|
+
}
|
|
2696
|
+
return parsed.platform;
|
|
2697
|
+
} catch {
|
|
2698
|
+
return null;
|
|
2699
|
+
}
|
|
2700
|
+
};
|
|
2701
|
+
const persistConfirmedPlatform = (platform) => {
|
|
2702
|
+
if (platform === "web") {
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
try {
|
|
2706
|
+
window.sessionStorage.setItem(
|
|
2707
|
+
CONFIRMED_PLATFORM_STORAGE_KEY,
|
|
2708
|
+
JSON.stringify({ platform, ts: Date.now() })
|
|
2709
|
+
);
|
|
2710
|
+
} catch {
|
|
2711
|
+
}
|
|
2712
|
+
};
|
|
2526
2713
|
const shellPlatform = readShellPlatform();
|
|
2527
2714
|
if (shellPlatform) {
|
|
2715
|
+
persistConfirmedPlatform(shellPlatform);
|
|
2528
2716
|
return shellPlatform;
|
|
2529
2717
|
}
|
|
2530
2718
|
const userAgent = navigator.userAgent.toLowerCase();
|
|
2531
2719
|
const hasNativeBridge = typeof window.NativeBridge?.postMessage === "function";
|
|
2532
2720
|
if (hasNativeBridge) {
|
|
2533
2721
|
if (userAgent.includes("android")) {
|
|
2722
|
+
persistConfirmedPlatform("shell_android");
|
|
2534
2723
|
return "shell_android";
|
|
2535
2724
|
}
|
|
2725
|
+
persistConfirmedPlatform("shell_ios");
|
|
2536
2726
|
return "shell_ios";
|
|
2537
2727
|
}
|
|
2538
|
-
|
|
2728
|
+
const telegramGlobals = window;
|
|
2729
|
+
const hasTelegramGlobal = Boolean(window.Telegram?.WebApp) || typeof telegramGlobals.TelegramWebviewProxy !== "undefined" || typeof telegramGlobals.TelegramGameProxy !== "undefined";
|
|
2730
|
+
const hasTelegramParams = hasParam("tgWebAppPlatform", "tgWebAppVersion", "tgWebAppData", "tgWebAppLanguage");
|
|
2731
|
+
if (hasTelegramGlobal || hasTelegramParams || userAgent.includes("telegram")) {
|
|
2732
|
+
persistConfirmedPlatform("telegram");
|
|
2539
2733
|
return "telegram";
|
|
2540
2734
|
}
|
|
2541
2735
|
if (window.WebApp) {
|
|
2736
|
+
persistConfirmedPlatform("max");
|
|
2542
2737
|
return "max";
|
|
2543
2738
|
}
|
|
2544
2739
|
if (window.MaxMiniApp) {
|
|
2740
|
+
persistConfirmedPlatform("max");
|
|
2545
2741
|
return "max";
|
|
2546
2742
|
}
|
|
2547
|
-
|
|
2743
|
+
const hasVkParams = hasParam("vk_app_id", "vk_platform", "vk_user_id", "vk_language", "sign");
|
|
2744
|
+
const hasVkUserAgentSignal = userAgent.includes("vkclient") || userAgent.includes("vk-android") || userAgent.includes("vkontakte");
|
|
2745
|
+
if (hasVkParams || hasVkUserAgentSignal) {
|
|
2746
|
+
persistConfirmedPlatform("vk");
|
|
2548
2747
|
return "vk";
|
|
2549
2748
|
}
|
|
2749
|
+
const confirmedPlatform = readConfirmedPlatform();
|
|
2750
|
+
if (confirmedPlatform && confirmedPlatform !== "web") {
|
|
2751
|
+
return confirmedPlatform;
|
|
2752
|
+
}
|
|
2550
2753
|
return "web";
|
|
2551
2754
|
}
|
|
2552
2755
|
function createAdapter(input) {
|
|
@@ -2747,14 +2950,18 @@ var cachedPlatform = null;
|
|
|
2747
2950
|
function getPlatform() {
|
|
2748
2951
|
const adapter = getActiveAdapter();
|
|
2749
2952
|
if (adapter) {
|
|
2750
|
-
|
|
2953
|
+
if (adapter.platform !== "web") {
|
|
2954
|
+
cachedPlatform = adapter.platform;
|
|
2955
|
+
}
|
|
2751
2956
|
return adapter.platform;
|
|
2752
2957
|
}
|
|
2753
|
-
if (cachedPlatform) {
|
|
2958
|
+
if (cachedPlatform && cachedPlatform !== "web") {
|
|
2754
2959
|
return cachedPlatform;
|
|
2755
2960
|
}
|
|
2756
2961
|
const detectedPlatform = detectPlatform();
|
|
2757
|
-
|
|
2962
|
+
if (detectedPlatform !== "web") {
|
|
2963
|
+
cachedPlatform = detectedPlatform;
|
|
2964
|
+
}
|
|
2758
2965
|
return detectedPlatform;
|
|
2759
2966
|
}
|
|
2760
2967
|
|