@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 +268 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -4
- package/dist/index.d.ts +38 -4
- package/dist/index.js +268 -49
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -66,7 +66,7 @@ interface MiniAppInitOptions {
|
|
|
66
66
|
*/
|
|
67
67
|
debug?: boolean;
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
69
|
+
* Enables Eruda devtools when host app already exposed `window.eruda`.
|
|
70
70
|
*/
|
|
71
71
|
eruda?: boolean;
|
|
72
72
|
/**
|
|
@@ -75,13 +75,45 @@ interface MiniAppInitOptions {
|
|
|
75
75
|
mockForMacOS?: boolean;
|
|
76
76
|
}
|
|
77
77
|
interface MiniAppShareStoryOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Shared story caption. Used by Telegram directly.
|
|
80
|
+
* For VK this value is converted into a simple text sticker when `vk.stickers` is not provided.
|
|
81
|
+
*/
|
|
78
82
|
text?: string;
|
|
79
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Universal CTA link.
|
|
85
|
+
* Telegram maps it to `widgetLink`, VK maps it to `attachment` when custom VK attachment is not provided.
|
|
86
|
+
*/
|
|
87
|
+
link?: {
|
|
80
88
|
url: string;
|
|
81
89
|
name?: string;
|
|
82
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* Telegram-specific options.
|
|
93
|
+
*/
|
|
94
|
+
telegram?: {
|
|
95
|
+
text?: string;
|
|
96
|
+
widgetLink?: {
|
|
97
|
+
url: string;
|
|
98
|
+
name?: string;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* VK-specific options.
|
|
103
|
+
*/
|
|
104
|
+
vk?: {
|
|
105
|
+
backgroundType?: 'image' | 'video';
|
|
106
|
+
locked?: boolean;
|
|
107
|
+
attachment?: {
|
|
108
|
+
type: string;
|
|
109
|
+
text?: string;
|
|
110
|
+
url?: string;
|
|
111
|
+
[key: string]: unknown;
|
|
112
|
+
};
|
|
113
|
+
stickers?: Array<Record<string, unknown>>;
|
|
114
|
+
};
|
|
83
115
|
}
|
|
84
|
-
type MiniAppCapability = 'haptics' | 'popup' | 'qrScanner' | 'closeApp' | 'backButton' | 'backButtonVisibility' | 'bindCssVariables' | 'requestPhone' | 'notifications';
|
|
116
|
+
type MiniAppCapability = 'haptics' | 'popup' | 'qrScanner' | 'closeApp' | 'backButton' | 'backButtonVisibility' | 'bindCssVariables' | 'requestPhone' | 'notifications' | 'openInternalLink' | 'requestFullscreen' | 'verticalSwipes' | 'viewVisibility' | 'shareUrl' | 'shareStory' | 'copyTextToClipboard' | 'downloadFile' | 'addToHomeScreen' | 'checkHomeScreenStatus' | 'denyNotifications';
|
|
85
117
|
interface MiniAppPopupButton {
|
|
86
118
|
id: string;
|
|
87
119
|
text?: string;
|
|
@@ -307,7 +339,7 @@ declare abstract class BaseMiniAppAdapter implements MiniAppAdapter {
|
|
|
307
339
|
onPushToken(callback: (token: string) => void): () => void;
|
|
308
340
|
onDeepLink(callback: (path: string) => void): () => void;
|
|
309
341
|
openExternalLink(url: string): Promise<void>;
|
|
310
|
-
openInternalLink(
|
|
342
|
+
openInternalLink(url: string): Promise<void>;
|
|
311
343
|
closeApp(): Promise<void>;
|
|
312
344
|
setBackButtonVisibility(_visible: boolean): void;
|
|
313
345
|
onAppearanceChange(callback: (appearance: 'dark' | 'light' | undefined) => void): () => void;
|
|
@@ -361,6 +393,7 @@ declare class MaxMiniAppAdapter extends BaseMiniAppAdapter {
|
|
|
361
393
|
onBackButton(callback: () => void): () => void;
|
|
362
394
|
setBackButtonVisibility(visible: boolean): void;
|
|
363
395
|
openExternalLink(url: string): Promise<void>;
|
|
396
|
+
openInternalLink(url: string): Promise<void>;
|
|
364
397
|
closeApp(): Promise<void>;
|
|
365
398
|
vibrateImpact(style: ImpactHapticFeedbackStyle): void;
|
|
366
399
|
vibrateNotification(type: NotificationHapticFeedbackType): void;
|
|
@@ -505,6 +538,7 @@ declare class VKMiniAppAdapter extends BaseMiniAppAdapter {
|
|
|
505
538
|
declare class WebMiniAppAdapter extends BaseMiniAppAdapter {
|
|
506
539
|
private deferredPrompt;
|
|
507
540
|
constructor();
|
|
541
|
+
supports(capability: MiniAppCapability): boolean | Promise<boolean>;
|
|
508
542
|
downloadFile(url: string, filename: string): Promise<void>;
|
|
509
543
|
scanQRCode(_options?: MiniAppQrScanOptions): Promise<string | null>;
|
|
510
544
|
shareUrl(url: string, text: string): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -66,7 +66,7 @@ interface MiniAppInitOptions {
|
|
|
66
66
|
*/
|
|
67
67
|
debug?: boolean;
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
69
|
+
* Enables Eruda devtools when host app already exposed `window.eruda`.
|
|
70
70
|
*/
|
|
71
71
|
eruda?: boolean;
|
|
72
72
|
/**
|
|
@@ -75,13 +75,45 @@ interface MiniAppInitOptions {
|
|
|
75
75
|
mockForMacOS?: boolean;
|
|
76
76
|
}
|
|
77
77
|
interface MiniAppShareStoryOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Shared story caption. Used by Telegram directly.
|
|
80
|
+
* For VK this value is converted into a simple text sticker when `vk.stickers` is not provided.
|
|
81
|
+
*/
|
|
78
82
|
text?: string;
|
|
79
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Universal CTA link.
|
|
85
|
+
* Telegram maps it to `widgetLink`, VK maps it to `attachment` when custom VK attachment is not provided.
|
|
86
|
+
*/
|
|
87
|
+
link?: {
|
|
80
88
|
url: string;
|
|
81
89
|
name?: string;
|
|
82
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* Telegram-specific options.
|
|
93
|
+
*/
|
|
94
|
+
telegram?: {
|
|
95
|
+
text?: string;
|
|
96
|
+
widgetLink?: {
|
|
97
|
+
url: string;
|
|
98
|
+
name?: string;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* VK-specific options.
|
|
103
|
+
*/
|
|
104
|
+
vk?: {
|
|
105
|
+
backgroundType?: 'image' | 'video';
|
|
106
|
+
locked?: boolean;
|
|
107
|
+
attachment?: {
|
|
108
|
+
type: string;
|
|
109
|
+
text?: string;
|
|
110
|
+
url?: string;
|
|
111
|
+
[key: string]: unknown;
|
|
112
|
+
};
|
|
113
|
+
stickers?: Array<Record<string, unknown>>;
|
|
114
|
+
};
|
|
83
115
|
}
|
|
84
|
-
type MiniAppCapability = 'haptics' | 'popup' | 'qrScanner' | 'closeApp' | 'backButton' | 'backButtonVisibility' | 'bindCssVariables' | 'requestPhone' | 'notifications';
|
|
116
|
+
type MiniAppCapability = 'haptics' | 'popup' | 'qrScanner' | 'closeApp' | 'backButton' | 'backButtonVisibility' | 'bindCssVariables' | 'requestPhone' | 'notifications' | 'openInternalLink' | 'requestFullscreen' | 'verticalSwipes' | 'viewVisibility' | 'shareUrl' | 'shareStory' | 'copyTextToClipboard' | 'downloadFile' | 'addToHomeScreen' | 'checkHomeScreenStatus' | 'denyNotifications';
|
|
85
117
|
interface MiniAppPopupButton {
|
|
86
118
|
id: string;
|
|
87
119
|
text?: string;
|
|
@@ -307,7 +339,7 @@ declare abstract class BaseMiniAppAdapter implements MiniAppAdapter {
|
|
|
307
339
|
onPushToken(callback: (token: string) => void): () => void;
|
|
308
340
|
onDeepLink(callback: (path: string) => void): () => void;
|
|
309
341
|
openExternalLink(url: string): Promise<void>;
|
|
310
|
-
openInternalLink(
|
|
342
|
+
openInternalLink(url: string): Promise<void>;
|
|
311
343
|
closeApp(): Promise<void>;
|
|
312
344
|
setBackButtonVisibility(_visible: boolean): void;
|
|
313
345
|
onAppearanceChange(callback: (appearance: 'dark' | 'light' | undefined) => void): () => void;
|
|
@@ -361,6 +393,7 @@ declare class MaxMiniAppAdapter extends BaseMiniAppAdapter {
|
|
|
361
393
|
onBackButton(callback: () => void): () => void;
|
|
362
394
|
setBackButtonVisibility(visible: boolean): void;
|
|
363
395
|
openExternalLink(url: string): Promise<void>;
|
|
396
|
+
openInternalLink(url: string): Promise<void>;
|
|
364
397
|
closeApp(): Promise<void>;
|
|
365
398
|
vibrateImpact(style: ImpactHapticFeedbackStyle): void;
|
|
366
399
|
vibrateNotification(type: NotificationHapticFeedbackType): void;
|
|
@@ -505,6 +538,7 @@ declare class VKMiniAppAdapter extends BaseMiniAppAdapter {
|
|
|
505
538
|
declare class WebMiniAppAdapter extends BaseMiniAppAdapter {
|
|
506
539
|
private deferredPrompt;
|
|
507
540
|
constructor();
|
|
541
|
+
supports(capability: MiniAppCapability): boolean | Promise<boolean>;
|
|
508
542
|
downloadFile(url: string, filename: string): Promise<void>;
|
|
509
543
|
scanQRCode(_options?: MiniAppQrScanOptions): Promise<string | null>;
|
|
510
544
|
shareUrl(url: string, text: string): void;
|
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
|
|
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
|
|
461
|
+
const scanner = new Html5Qrcode(elementId);
|
|
462
462
|
const cleanup = async (result, error) => {
|
|
463
463
|
if (disposed) {
|
|
464
464
|
return;
|
|
@@ -580,7 +580,8 @@ var BaseMiniAppAdapter = class {
|
|
|
580
580
|
async openExternalLink(url) {
|
|
581
581
|
window.open(url, "_blank", "noopener,noreferrer");
|
|
582
582
|
}
|
|
583
|
-
async openInternalLink(
|
|
583
|
+
async openInternalLink(url) {
|
|
584
|
+
window.open(url, "_self", "noopener,noreferrer");
|
|
584
585
|
}
|
|
585
586
|
async closeApp() {
|
|
586
587
|
if (window.history.length > 1) {
|
|
@@ -806,6 +807,10 @@ var MaxMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
806
807
|
return Boolean(bridge2?.BackButton?.onClick);
|
|
807
808
|
case "backButtonVisibility":
|
|
808
809
|
return Boolean(bridge2?.BackButton?.show && bridge2.BackButton.hide);
|
|
810
|
+
case "openInternalLink":
|
|
811
|
+
return typeof bridge2?.openMaxLink === "function";
|
|
812
|
+
case "downloadFile":
|
|
813
|
+
return typeof bridge2?.downloadFile === "function";
|
|
809
814
|
case "requestPhone":
|
|
810
815
|
if (!bridge2) {
|
|
811
816
|
return false;
|
|
@@ -860,6 +865,14 @@ var MaxMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
860
865
|
}
|
|
861
866
|
await super.openExternalLink(url);
|
|
862
867
|
}
|
|
868
|
+
async openInternalLink(url) {
|
|
869
|
+
const bridge2 = getMaxBridge();
|
|
870
|
+
if (bridge2?.openMaxLink) {
|
|
871
|
+
bridge2.openMaxLink(url);
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
await super.openInternalLink(url);
|
|
875
|
+
}
|
|
863
876
|
async closeApp() {
|
|
864
877
|
const bridge2 = getMaxBridge();
|
|
865
878
|
if (bridge2?.close) {
|
|
@@ -1287,6 +1300,30 @@ var TelegramMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
1287
1300
|
return backButton.hide.isSupported();
|
|
1288
1301
|
case "bindCssVariables":
|
|
1289
1302
|
return true;
|
|
1303
|
+
case "openInternalLink":
|
|
1304
|
+
return true;
|
|
1305
|
+
case "requestFullscreen":
|
|
1306
|
+
return Boolean(
|
|
1307
|
+
typeof rawViewport.requestFullscreen === "function" || viewport.requestFullscreen?.isAvailable?.()
|
|
1308
|
+
);
|
|
1309
|
+
case "verticalSwipes":
|
|
1310
|
+
return Boolean(
|
|
1311
|
+
swipeBehavior.enableVertical.isAvailable() || swipeBehavior.disableVertical.isAvailable()
|
|
1312
|
+
);
|
|
1313
|
+
case "viewVisibility":
|
|
1314
|
+
return true;
|
|
1315
|
+
case "shareUrl":
|
|
1316
|
+
return typeof shareURLSdk === "function";
|
|
1317
|
+
case "shareStory":
|
|
1318
|
+
return typeof shareStorySdk === "function";
|
|
1319
|
+
case "copyTextToClipboard":
|
|
1320
|
+
return typeof copyTextToClipboardSdk === "function";
|
|
1321
|
+
case "downloadFile":
|
|
1322
|
+
return typeof downloadFileSdk === "function";
|
|
1323
|
+
case "addToHomeScreen":
|
|
1324
|
+
return typeof addToHomeScreenSdk?.isAvailable === "function" ? addToHomeScreenSdk.isAvailable() : typeof addToHomeScreenSdk === "function";
|
|
1325
|
+
case "checkHomeScreenStatus":
|
|
1326
|
+
return typeof checkHomeScreenStatusSdk === "function";
|
|
1290
1327
|
case "requestPhone": {
|
|
1291
1328
|
return Boolean(isFeatureAvailable(requestPhoneAccess) || isFeatureAvailable(requestContact));
|
|
1292
1329
|
}
|
|
@@ -1532,7 +1569,15 @@ var TelegramMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
1532
1569
|
await super.downloadFile(url, filename);
|
|
1533
1570
|
}
|
|
1534
1571
|
async shareStory(mediaUrl, options) {
|
|
1535
|
-
|
|
1572
|
+
const text = options?.telegram?.text ?? options?.text;
|
|
1573
|
+
const widgetLink = options?.telegram?.widgetLink ?? (options?.link ? {
|
|
1574
|
+
url: options.link.url,
|
|
1575
|
+
...options.link.name ? { name: options.link.name } : {}
|
|
1576
|
+
} : void 0);
|
|
1577
|
+
shareStorySdk(mediaUrl, {
|
|
1578
|
+
...text ? { text } : {},
|
|
1579
|
+
...widgetLink ? { widgetLink } : {}
|
|
1580
|
+
});
|
|
1536
1581
|
}
|
|
1537
1582
|
async addToHomeScreen() {
|
|
1538
1583
|
const isAvailable = typeof addToHomeScreenSdk?.isAvailable === "function" ? addToHomeScreenSdk.isAvailable() : true;
|
|
@@ -1889,17 +1934,41 @@ var VKMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
1889
1934
|
};
|
|
1890
1935
|
}
|
|
1891
1936
|
async supports(capability) {
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1937
|
+
switch (capability) {
|
|
1938
|
+
case "haptics": {
|
|
1939
|
+
const [impact, notification, selection] = await Promise.all([
|
|
1940
|
+
this.supportsBridgeMethod("VKWebAppTapticImpactOccurred"),
|
|
1941
|
+
this.supportsBridgeMethod("VKWebAppTapticNotificationOccurred"),
|
|
1942
|
+
this.supportsBridgeMethod("VKWebAppTapticSelectionChanged")
|
|
1943
|
+
]);
|
|
1944
|
+
return impact || notification || selection;
|
|
1945
|
+
}
|
|
1946
|
+
case "qrScanner":
|
|
1947
|
+
return this.supportsBridgeMethod("VKWebAppOpenCodeReader");
|
|
1948
|
+
case "requestPhone": {
|
|
1949
|
+
const [supportsPhoneNumber, supportsPersonalCard] = await Promise.all([
|
|
1950
|
+
this.supportsBridgeMethod("VKWebAppGetPhoneNumber"),
|
|
1951
|
+
this.supportsBridgeMethod("VKWebAppGetPersonalCard")
|
|
1952
|
+
]);
|
|
1953
|
+
return supportsPhoneNumber || supportsPersonalCard;
|
|
1954
|
+
}
|
|
1955
|
+
case "notifications":
|
|
1956
|
+
return this.supportsBridgeMethod("VKWebAppAllowNotifications");
|
|
1957
|
+
case "shareUrl":
|
|
1958
|
+
return this.supportsBridgeMethod("VKWebAppShare");
|
|
1959
|
+
case "shareStory":
|
|
1960
|
+
return this.supportsBridgeMethod("VKWebAppShowStoryBox");
|
|
1961
|
+
case "downloadFile":
|
|
1962
|
+
return this.supportsBridgeMethod("VKWebAppDownloadFile");
|
|
1963
|
+
case "addToHomeScreen":
|
|
1964
|
+
return this.supportsBridgeMethod("VKWebAppAddToHomeScreen");
|
|
1965
|
+
case "denyNotifications":
|
|
1966
|
+
return this.supportsBridgeMethod("VKWebAppDenyNotifications");
|
|
1967
|
+
case "viewVisibility":
|
|
1968
|
+
return true;
|
|
1969
|
+
default:
|
|
1970
|
+
return await super.supports(capability);
|
|
1901
1971
|
}
|
|
1902
|
-
return await super.supports(capability);
|
|
1903
1972
|
}
|
|
1904
1973
|
async requestPhone() {
|
|
1905
1974
|
const [supportsPhoneNumber, supportsPersonalCard] = await Promise.all([
|
|
@@ -2001,9 +2070,34 @@ var VKMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
2001
2070
|
};
|
|
2002
2071
|
}
|
|
2003
2072
|
async shareStory(mediaUrl, _options) {
|
|
2073
|
+
const options = _options;
|
|
2074
|
+
const vkOptions = options?.vk;
|
|
2075
|
+
const fallbackAttachment = options?.link ? {
|
|
2076
|
+
type: "url",
|
|
2077
|
+
text: "open",
|
|
2078
|
+
url: options.link.url
|
|
2079
|
+
} : void 0;
|
|
2080
|
+
const fallbackStickers = options?.text ? [{
|
|
2081
|
+
sticker_type: "native",
|
|
2082
|
+
sticker: {
|
|
2083
|
+
action_type: "text",
|
|
2084
|
+
action: {
|
|
2085
|
+
text: options.text,
|
|
2086
|
+
style: "classic",
|
|
2087
|
+
background_style: "none"
|
|
2088
|
+
},
|
|
2089
|
+
transform: {
|
|
2090
|
+
gravity: "center_bottom",
|
|
2091
|
+
translation_y: -0.2
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
}] : void 0;
|
|
2004
2095
|
const bridgeOptions = {
|
|
2005
|
-
background_type: "image",
|
|
2006
|
-
url: mediaUrl
|
|
2096
|
+
background_type: vkOptions?.backgroundType ?? "image",
|
|
2097
|
+
url: mediaUrl,
|
|
2098
|
+
locked: vkOptions?.locked ?? true,
|
|
2099
|
+
...vkOptions?.attachment ?? fallbackAttachment ? { attachment: vkOptions?.attachment ?? fallbackAttachment } : {},
|
|
2100
|
+
...vkOptions?.stickers ?? fallbackStickers ? { stickers: vkOptions?.stickers ?? fallbackStickers } : {}
|
|
2007
2101
|
};
|
|
2008
2102
|
await bridge.send("VKWebAppShowStoryBox", bridgeOptions);
|
|
2009
2103
|
}
|
|
@@ -2376,7 +2470,7 @@ var VKMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
2376
2470
|
};
|
|
2377
2471
|
|
|
2378
2472
|
// src/adapters/webAdapter.ts
|
|
2379
|
-
import
|
|
2473
|
+
import jsQR from "jsqr";
|
|
2380
2474
|
var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
2381
2475
|
constructor() {
|
|
2382
2476
|
super("web", {
|
|
@@ -2392,6 +2486,22 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
2392
2486
|
});
|
|
2393
2487
|
}
|
|
2394
2488
|
}
|
|
2489
|
+
supports(capability) {
|
|
2490
|
+
switch (capability) {
|
|
2491
|
+
case "copyTextToClipboard":
|
|
2492
|
+
return typeof navigator !== "undefined" && Boolean(navigator.clipboard?.writeText);
|
|
2493
|
+
case "downloadFile":
|
|
2494
|
+
return typeof document !== "undefined";
|
|
2495
|
+
case "shareUrl":
|
|
2496
|
+
return typeof navigator !== "undefined" && (Boolean(navigator.share) || Boolean(navigator.clipboard?.writeText));
|
|
2497
|
+
case "addToHomeScreen":
|
|
2498
|
+
return /android/i.test(navigator.userAgent) && Boolean(this.deferredPrompt);
|
|
2499
|
+
case "checkHomeScreenStatus":
|
|
2500
|
+
return true;
|
|
2501
|
+
default:
|
|
2502
|
+
return super.supports(capability);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2395
2505
|
async downloadFile(url, filename) {
|
|
2396
2506
|
try {
|
|
2397
2507
|
await triggerFileDownload(url, filename, { preferBlob: true });
|
|
@@ -2446,14 +2556,31 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
2446
2556
|
closeBtn.style.cursor = "pointer";
|
|
2447
2557
|
closeBtn.style.zIndex = "9999999999";
|
|
2448
2558
|
overlay.appendChild(closeBtn);
|
|
2559
|
+
const scanSize = Math.min(Math.floor(Math.min(window.innerWidth, window.innerHeight) * 0.72), 320);
|
|
2560
|
+
const scanBox = document.createElement("div");
|
|
2561
|
+
scanBox.style.width = `${scanSize}px`;
|
|
2562
|
+
scanBox.style.height = `${scanSize}px`;
|
|
2563
|
+
scanBox.style.position = "relative";
|
|
2564
|
+
scanBox.style.flex = "0 0 auto";
|
|
2565
|
+
scanBox.style.borderRadius = "18px";
|
|
2566
|
+
scanBox.style.overflow = "hidden";
|
|
2567
|
+
overlay.appendChild(scanBox);
|
|
2449
2568
|
const scanArea = document.createElement("div");
|
|
2450
|
-
scanArea.
|
|
2451
|
-
scanArea.style.
|
|
2452
|
-
scanArea.style.
|
|
2453
|
-
scanArea.style.
|
|
2454
|
-
scanArea
|
|
2455
|
-
|
|
2456
|
-
|
|
2569
|
+
scanArea.style.position = "absolute";
|
|
2570
|
+
scanArea.style.inset = "0";
|
|
2571
|
+
scanArea.style.zIndex = "1";
|
|
2572
|
+
scanArea.style.background = "#000";
|
|
2573
|
+
scanBox.appendChild(scanArea);
|
|
2574
|
+
const video = document.createElement("video");
|
|
2575
|
+
video.setAttribute("playsinline", "true");
|
|
2576
|
+
video.autoplay = true;
|
|
2577
|
+
video.muted = true;
|
|
2578
|
+
video.style.width = "100%";
|
|
2579
|
+
video.style.height = "100%";
|
|
2580
|
+
video.style.objectFit = "cover";
|
|
2581
|
+
video.style.position = "absolute";
|
|
2582
|
+
video.style.inset = "0";
|
|
2583
|
+
scanArea.appendChild(video);
|
|
2457
2584
|
const frame = document.createElement("div");
|
|
2458
2585
|
frame.style.position = "absolute";
|
|
2459
2586
|
frame.style.top = "0";
|
|
@@ -2463,8 +2590,8 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
2463
2590
|
frame.style.border = "3px solid rgba(255,255,255,0.9)";
|
|
2464
2591
|
frame.style.borderRadius = "18px";
|
|
2465
2592
|
frame.style.pointerEvents = "none";
|
|
2466
|
-
frame.style.zIndex = "
|
|
2467
|
-
|
|
2593
|
+
frame.style.zIndex = "3";
|
|
2594
|
+
scanBox.appendChild(frame);
|
|
2468
2595
|
const line = document.createElement("div");
|
|
2469
2596
|
line.style.position = "absolute";
|
|
2470
2597
|
line.style.left = "0";
|
|
@@ -2473,8 +2600,8 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
2473
2600
|
line.style.background = "rgba(255,255,255,0.85)";
|
|
2474
2601
|
line.style.borderRadius = "2px";
|
|
2475
2602
|
line.style.animation = "qr-line 2s infinite";
|
|
2476
|
-
line.style.zIndex = "
|
|
2477
|
-
|
|
2603
|
+
line.style.zIndex = "4";
|
|
2604
|
+
scanBox.appendChild(line);
|
|
2478
2605
|
const styleTag = document.createElement("style");
|
|
2479
2606
|
styleTag.innerHTML = `
|
|
2480
2607
|
@keyframes qr-line {
|
|
@@ -2491,17 +2618,26 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
2491
2618
|
hint.style.fontSize = "17px";
|
|
2492
2619
|
hint.style.opacity = "0.9";
|
|
2493
2620
|
overlay.appendChild(hint);
|
|
2494
|
-
const
|
|
2621
|
+
const canvas = document.createElement("canvas");
|
|
2622
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
2623
|
+
let stream = null;
|
|
2624
|
+
let rafId = null;
|
|
2625
|
+
let lastScanAt = 0;
|
|
2495
2626
|
let closed = false;
|
|
2496
2627
|
const finalize = async (result) => {
|
|
2497
2628
|
if (closed) {
|
|
2498
2629
|
return;
|
|
2499
2630
|
}
|
|
2500
2631
|
closed = true;
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2632
|
+
if (rafId !== null) {
|
|
2633
|
+
cancelAnimationFrame(rafId);
|
|
2634
|
+
rafId = null;
|
|
2635
|
+
}
|
|
2636
|
+
if (stream) {
|
|
2637
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
2638
|
+
stream = null;
|
|
2504
2639
|
}
|
|
2640
|
+
video.srcObject = null;
|
|
2505
2641
|
overlay.remove();
|
|
2506
2642
|
styleTag.remove();
|
|
2507
2643
|
document.body.style.overflow = prevOverflow;
|
|
@@ -2520,19 +2656,50 @@ var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
|
2520
2656
|
};
|
|
2521
2657
|
closeBtn.onclick = () => closeScanner(null);
|
|
2522
2658
|
try {
|
|
2523
|
-
|
|
2524
|
-
{ facingMode: "environment" },
|
|
2525
|
-
{
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
()
|
|
2659
|
+
const constraints = [
|
|
2660
|
+
{ video: { facingMode: { ideal: "environment" } }, audio: false },
|
|
2661
|
+
{ video: { facingMode: "environment" }, audio: false },
|
|
2662
|
+
{ video: true, audio: false }
|
|
2663
|
+
];
|
|
2664
|
+
let lastError = null;
|
|
2665
|
+
for (const constraint of constraints) {
|
|
2666
|
+
try {
|
|
2667
|
+
stream = await navigator.mediaDevices.getUserMedia(constraint);
|
|
2668
|
+
break;
|
|
2669
|
+
} catch (error) {
|
|
2670
|
+
lastError = error;
|
|
2534
2671
|
}
|
|
2535
|
-
|
|
2672
|
+
}
|
|
2673
|
+
if (!stream) {
|
|
2674
|
+
throw lastError instanceof Error ? lastError : new Error("Unable to access camera");
|
|
2675
|
+
}
|
|
2676
|
+
video.srcObject = stream;
|
|
2677
|
+
await video.play();
|
|
2678
|
+
const scanFrame = (now) => {
|
|
2679
|
+
if (closed) {
|
|
2680
|
+
return;
|
|
2681
|
+
}
|
|
2682
|
+
if (now - lastScanAt >= 100 && context && video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
2683
|
+
const width = video.videoWidth;
|
|
2684
|
+
const height = video.videoHeight;
|
|
2685
|
+
if (width > 0 && height > 0) {
|
|
2686
|
+
canvas.width = width;
|
|
2687
|
+
canvas.height = height;
|
|
2688
|
+
context.drawImage(video, 0, 0, width, height);
|
|
2689
|
+
const imageData = context.getImageData(0, 0, width, height);
|
|
2690
|
+
const result = jsQR(imageData.data, width, height, {
|
|
2691
|
+
inversionAttempts: "attemptBoth"
|
|
2692
|
+
});
|
|
2693
|
+
if (result?.data) {
|
|
2694
|
+
closeScanner(result.data);
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
lastScanAt = now;
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
rafId = requestAnimationFrame(scanFrame);
|
|
2701
|
+
};
|
|
2702
|
+
rafId = requestAnimationFrame(scanFrame);
|
|
2536
2703
|
} catch (error) {
|
|
2537
2704
|
console.error("QR Start error", error);
|
|
2538
2705
|
closeScanner(null);
|
|
@@ -2585,6 +2752,8 @@ ${url}` : url;
|
|
|
2585
2752
|
};
|
|
2586
2753
|
|
|
2587
2754
|
// src/adapters/index.ts
|
|
2755
|
+
var CONFIRMED_PLATFORM_STORAGE_KEY = "mini-app-adapter:confirmed-platform";
|
|
2756
|
+
var CONFIRMED_PLATFORM_TTL_MS = 30 * 60 * 1e3;
|
|
2588
2757
|
function detectPlatform() {
|
|
2589
2758
|
if (typeof window === "undefined") {
|
|
2590
2759
|
return "web";
|
|
@@ -2596,30 +2765,76 @@ function detectPlatform() {
|
|
|
2596
2765
|
})();
|
|
2597
2766
|
const getParam = (name) => searchParams.get(name) ?? hashParams.get(name);
|
|
2598
2767
|
const hasParam = (...names) => names.some((name) => getParam(name));
|
|
2768
|
+
const readConfirmedPlatform = () => {
|
|
2769
|
+
try {
|
|
2770
|
+
const raw = window.sessionStorage.getItem(CONFIRMED_PLATFORM_STORAGE_KEY);
|
|
2771
|
+
if (!raw) {
|
|
2772
|
+
return null;
|
|
2773
|
+
}
|
|
2774
|
+
const parsed = JSON.parse(raw);
|
|
2775
|
+
if (!parsed?.platform || typeof parsed.ts !== "number") {
|
|
2776
|
+
return null;
|
|
2777
|
+
}
|
|
2778
|
+
if (Date.now() - parsed.ts > CONFIRMED_PLATFORM_TTL_MS) {
|
|
2779
|
+
return null;
|
|
2780
|
+
}
|
|
2781
|
+
return parsed.platform;
|
|
2782
|
+
} catch {
|
|
2783
|
+
return null;
|
|
2784
|
+
}
|
|
2785
|
+
};
|
|
2786
|
+
const persistConfirmedPlatform = (platform) => {
|
|
2787
|
+
if (platform === "web") {
|
|
2788
|
+
return;
|
|
2789
|
+
}
|
|
2790
|
+
try {
|
|
2791
|
+
window.sessionStorage.setItem(
|
|
2792
|
+
CONFIRMED_PLATFORM_STORAGE_KEY,
|
|
2793
|
+
JSON.stringify({ platform, ts: Date.now() })
|
|
2794
|
+
);
|
|
2795
|
+
} catch {
|
|
2796
|
+
}
|
|
2797
|
+
};
|
|
2599
2798
|
const shellPlatform = readShellPlatform();
|
|
2600
2799
|
if (shellPlatform) {
|
|
2800
|
+
persistConfirmedPlatform(shellPlatform);
|
|
2601
2801
|
return shellPlatform;
|
|
2602
2802
|
}
|
|
2603
2803
|
const userAgent = navigator.userAgent.toLowerCase();
|
|
2604
2804
|
const hasNativeBridge = typeof window.NativeBridge?.postMessage === "function";
|
|
2605
2805
|
if (hasNativeBridge) {
|
|
2606
2806
|
if (userAgent.includes("android")) {
|
|
2807
|
+
persistConfirmedPlatform("shell_android");
|
|
2607
2808
|
return "shell_android";
|
|
2608
2809
|
}
|
|
2810
|
+
persistConfirmedPlatform("shell_ios");
|
|
2609
2811
|
return "shell_ios";
|
|
2610
2812
|
}
|
|
2611
|
-
|
|
2813
|
+
const telegramGlobals = window;
|
|
2814
|
+
const hasTelegramGlobal = Boolean(window.Telegram?.WebApp) || typeof telegramGlobals.TelegramWebviewProxy !== "undefined" || typeof telegramGlobals.TelegramGameProxy !== "undefined";
|
|
2815
|
+
const hasTelegramParams = hasParam("tgWebAppPlatform", "tgWebAppVersion", "tgWebAppData", "tgWebAppLanguage");
|
|
2816
|
+
if (hasTelegramGlobal || hasTelegramParams || userAgent.includes("telegram")) {
|
|
2817
|
+
persistConfirmedPlatform("telegram");
|
|
2612
2818
|
return "telegram";
|
|
2613
2819
|
}
|
|
2614
2820
|
if (window.WebApp) {
|
|
2821
|
+
persistConfirmedPlatform("max");
|
|
2615
2822
|
return "max";
|
|
2616
2823
|
}
|
|
2617
2824
|
if (window.MaxMiniApp) {
|
|
2825
|
+
persistConfirmedPlatform("max");
|
|
2618
2826
|
return "max";
|
|
2619
2827
|
}
|
|
2620
|
-
|
|
2828
|
+
const hasVkParams = hasParam("vk_app_id", "vk_platform", "vk_user_id", "vk_language", "sign");
|
|
2829
|
+
const hasVkUserAgentSignal = userAgent.includes("vkclient") || userAgent.includes("vk-android") || userAgent.includes("vkontakte");
|
|
2830
|
+
if (hasVkParams || hasVkUserAgentSignal) {
|
|
2831
|
+
persistConfirmedPlatform("vk");
|
|
2621
2832
|
return "vk";
|
|
2622
2833
|
}
|
|
2834
|
+
const confirmedPlatform = readConfirmedPlatform();
|
|
2835
|
+
if (confirmedPlatform && confirmedPlatform !== "web") {
|
|
2836
|
+
return confirmedPlatform;
|
|
2837
|
+
}
|
|
2623
2838
|
return "web";
|
|
2624
2839
|
}
|
|
2625
2840
|
function createAdapter(input) {
|
|
@@ -2820,14 +3035,18 @@ var cachedPlatform = null;
|
|
|
2820
3035
|
function getPlatform() {
|
|
2821
3036
|
const adapter = getActiveAdapter();
|
|
2822
3037
|
if (adapter) {
|
|
2823
|
-
|
|
3038
|
+
if (adapter.platform !== "web") {
|
|
3039
|
+
cachedPlatform = adapter.platform;
|
|
3040
|
+
}
|
|
2824
3041
|
return adapter.platform;
|
|
2825
3042
|
}
|
|
2826
|
-
if (cachedPlatform) {
|
|
3043
|
+
if (cachedPlatform && cachedPlatform !== "web") {
|
|
2827
3044
|
return cachedPlatform;
|
|
2828
3045
|
}
|
|
2829
3046
|
const detectedPlatform = detectPlatform();
|
|
2830
|
-
|
|
3047
|
+
if (detectedPlatform !== "web") {
|
|
3048
|
+
cachedPlatform = detectedPlatform;
|
|
3049
|
+
}
|
|
2831
3050
|
return detectedPlatform;
|
|
2832
3051
|
}
|
|
2833
3052
|
|