@wowlabtech/mini-app-adapter 0.2.83 → 0.2.85
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/README.md +1 -1
- package/dist/index.cjs +4 -3236
- package/dist/index.js +4 -3210
- package/package.json +1 -2
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,3216 +1,10 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1
|
+
var vt=Object.defineProperty;var yt=(o,r,e)=>r in o?vt(o,r,{enumerable:!0,configurable:!0,writable:!0,value:e}):o[r]=e;var c=(o,r,e)=>yt(o,typeof r!="symbol"?r+"":r,e);var X=class{constructor(){c(this,"disposers",new Set)}add(r){if(!r)return()=>{};let e=typeof r=="function"?r:typeof r.dispose=="function"?r.dispose.bind(r):void 0;if(!e)return()=>{};let t=!1,n=()=>{if(!t){t=!0;try{e()}catch(i){console.warn("[tvm-app-adapter] disposable failed:",i)}finally{this.disposers.delete(n)}}};return this.disposers.add(n),n}disposeAll(){for(let r of Array.from(this.disposers))try{r()}catch(e){console.warn("[tvm-app-adapter] disposeAll failed:",e)}this.disposers.clear()}};var Fe=typeof window<"u"&&typeof document<"u";function gt(o,r){if(r&&r.trim().length>0)return r;try{let t=new URL(o).pathname.split("/").filter(Boolean).pop();if(t)return t}catch{}return"download"}function Re(o,r,{targetBlank:e}){if(!Fe)return;let t=document.createElement("a");t.href=o,t.download=r,t.rel="noopener noreferrer",e&&(t.target="_blank"),document.body.appendChild(t),t.click(),document.body.removeChild(t)}async function J(o,r,e){if(!o)return;let t=gt(o,r),n=e?.preferBlob??!1;if(!Fe){typeof window<"u"&&window.open(o,"_blank","noopener,noreferrer");return}if(n&&typeof fetch=="function")try{let i=await fetch(o,{credentials:"include"});if(!i.ok)throw new Error(`Failed to download file: ${i.status}`);let s=await i.blob(),p=URL.createObjectURL(s);Re(p,t,{targetBlank:!1}),setTimeout(()=>URL.revokeObjectURL(p),3e4);return}catch(i){console.warn("[tvm-app-adapter] blob download failed, falling back to direct link:",i)}Re(o,t,{targetBlank:!0})}var ce=["top","right","bottom","left"],He={top:0,right:0,bottom:0,left:0};function le(o){return{...o}}function N(o,r){if(r)for(let e of ce){let t=r[e];typeof t=="number"&&Number.isFinite(t)&&(o[e]+=t)}}function Ie(o,r){if(r)for(let e of ce){let t=r[e];typeof t=="number"&&Number.isFinite(t)&&(o[e]=Math.max(o[e],t))}}function wt(o,r){return!o||!r?!1:ce.every(e=>o[e]===r[e])}function D(o={}){let r=le(He);return N(r,o.environment),o.viewport&&(N(r,o.viewport.safeArea),N(r,o.viewport.contentSafeArea)),N(r,o.additions),Ie(r,o.css),Ie(r,o.minimum),r}function Z(){if(typeof window>"u"||typeof document>"u")return;let o=getComputedStyle(document.documentElement),r=s=>{let p=parseFloat(o.getPropertyValue(s));return Number.isFinite(p)?p:0},e=r("--safe-area-inset-top"),t=r("--safe-area-inset-right"),n=r("--safe-area-inset-bottom"),i=r("--safe-area-inset-left");if(e||t||n||i)return{top:e,right:t,bottom:n,left:i}}function We(o){if(!o)return;let r={...He};return N(r,o),r}function Oe(o){let r=o.windowObj??(typeof window<"u"?window:void 0);if(!r)return;let e=We(o.getSafeArea());e&&(o.onChange(e),e=le(e));let t=()=>{let i=We(o.getSafeArea());i&&(!e||!wt(e,i))&&(o.onChange(i),e=le(i))};t();let n=o.events??["resize","orientationchange"];for(let i of n)r.addEventListener(i,t);return()=>{for(let i of n)r.removeEventListener(i,t)}}var bt=["shell_ios","shell_android"];var fe=new Set,me=new Set,he=new Set,ve=new Set,ye=null,P=null,At={platformFlag:"nativePlatform",pushTokenCallback:"nativePushToken",qrResultCallback:"nativeQRResult",deepLinkCallback:"nativeDeepLink",appActiveCallback:"nativeAppActive",appBackgroundCallback:"nativeAppBackground"},Ne={...At},ue={};function kt(o){ye=o;for(let r of fe)try{r(o)}catch(e){console.warn("[tvm-app-adapter] push token listener failed:",e)}}function De(o){return bt.includes(o)}function Pt(o){return o==="shell_ios"}function St(o){return o==="shell_android"}function ee(){let o=we();if(!o)return;let r=o[Ne.platformFlag];if(r==="shell_ios"||r==="shell_android")return r}function te(o){xt();let r=typeof o=="function"?o:()=>o;return{async openNativeQR(){let e=r();return De(e)?Bt():Et()},onPushToken(e){return fe.add(e),ye&&queueMicrotask(()=>{try{e(ye)}catch(t){console.warn("[tvm-app-adapter] push token listener failed:",t)}}),()=>fe.delete(e)},onDeepLink(e){return me.add(e),()=>me.delete(e)},onAppActive(e){return he.add(e),()=>he.delete(e)},onAppBackground(e){return ve.add(e),()=>ve.delete(e)}}}var Mt=te(()=>ee()??"web");function Ct(o){return be({type:"storeToken",payload:o})}function ge(){return be({type:"requestPushPermission"})}function we(){return typeof window>"u"?null:window}function Vt(){let o=we();if(!o)return null;let r=o.NativeBridge;return!r||typeof r.postMessage!="function"?null:r}function be(o){let r=Vt();if(!r)return!1;try{return r.postMessage(o),!0}catch(e){return console.warn("[tvm-app-adapter] NativeBridge.postMessage failed:",e),!1}}function xt(){let o=we();if(!o){ue={};return}let r=o,e=(t,n)=>{let i=Ne[t],s=ue[t];s&&s!==i&&delete r[s],ue[t]=i,r[i]=n};e("pushTokenCallback",t=>{typeof t=="string"&&(console.log("[tvm-app-adapter] nativePushToken",t),kt(t))}),e("deepLinkCallback",t=>{if(typeof t=="string")for(let n of me)try{n(t)}catch(i){console.warn("[tvm-app-adapter] deep link listener failed:",i)}}),e("appActiveCallback",()=>{for(let t of he)try{t()}catch(n){console.warn("[tvm-app-adapter] app active listener failed:",n)}}),e("appBackgroundCallback",()=>{for(let t of ve)try{t()}catch(n){console.warn("[tvm-app-adapter] app background listener failed:",n)}}),e("qrResultCallback",t=>{typeof t=="string"&&P&&(clearTimeout(P.timeoutId),P.resolve(t),P=null)})}function Bt(){return new Promise((o,r)=>{if(P&&(P.reject(new Error("QR request superseded by a new call.")),clearTimeout(P.timeoutId),P=null),!be({type:"openNativeQR"})){r(new Error("Native bridge is unavailable."));return}let e=setTimeout(()=>{P&&(P.reject(new Error("Native QR request timed out.")),P=null)},6e4);P={resolve:o,reject:r,timeoutId:e}})}async function Et(){if(typeof document>"u")throw new Error("QR scanning requires a browser environment.");let[{Html5Qrcode:o}]=await Promise.all([import("html5-qrcode")]);return new Promise((r,e)=>{let t=`native-shell-qr-${Date.now()}`,n=document.createElement("div");n.style.position="fixed",n.style.inset="0",n.style.background="rgba(0, 0, 0, 0.9)",n.style.display="flex",n.style.flexDirection="column",n.style.alignItems="center",n.style.justifyContent="center",n.style.zIndex="2147483647",n.style.backdropFilter="blur(2px)";let i=document.createElement("div");i.id=t,i.style.width="280px",i.style.height="280px",i.style.borderRadius="16px",i.style.overflow="hidden",i.style.position="relative";let s=document.createElement("button");s.type="button",s.textContent="\u2715",s.style.position="absolute",s.style.top="16px",s.style.right="16px",s.style.background="transparent",s.style.border="none",s.style.color="#fff",s.style.fontSize="28px",s.style.cursor="pointer",n.appendChild(s),n.appendChild(i),document.body.appendChild(n);let p=document.body.style.overflow;document.body.style.overflow="hidden";let d=!1,a=new o(t),l=async(h,u)=>{if(!d){d=!0;try{await a.stop();let b=a.clear;typeof b=="function"&&await Promise.resolve(b.call(a))}catch{}n.remove(),document.body.style.overflow=p,typeof h=="string"?r(h):e(u||new Error("QR scanning was cancelled."))}};s.addEventListener("click",()=>{l(void 0,new Error("QR scanning was cancelled by the user."))}),a.start({facingMode:"environment"},{fps:10,qrbox:{width:240,height:240}},h=>{l(h)},()=>{}).catch(h=>{let u=h instanceof Error?h:new Error("Unable to start HTML5 QR scanner.");l(void 0,u)})})}var A=class{constructor(r,e){c(this,"ready",!1);c(this,"environment");c(this,"listeners",new Set);c(this,"disposables",new X);c(this,"shell");if(this.shell=te(r),this.environment={platform:r,...e},typeof window<"u"){let t=()=>this.notifyEnvironmentChanged();if(window.addEventListener("resize",t),this.registerDisposable(()=>window.removeEventListener("resize",t)),r!=="web"){let n=this.applyScrollGuards();n&&this.registerDisposable(n)}}}supports(r){switch(r){case"openExternalLink":return!0;default:return!1}}get platform(){return this.environment.platform}async init(r){this.ready=!0}isReady(){return this.ready}getEnvironment(){return{...this.environment}}destroy(){try{this.onDestroy()}finally{this.disposables.disposeAll(),this.listeners.clear(),this.ready=!1}}subscribe(r){return this.listeners.add(r),()=>{this.listeners.delete(r)}}async setColors(r){if(r.background&&(document.body.style.backgroundColor=r.background),r.header){let e=document.querySelector('meta[name="theme-color"]');e&&e.setAttribute("content",r.header)}}onBackButton(r){let e=()=>r();return window.addEventListener("popstate",e),()=>window.removeEventListener("popstate",e)}onPushToken(r){return this.shell.onPushToken(r)}onDeepLink(r){return this.shell.onDeepLink(r)}async openExternalLink(r){window.open(r,"_blank","noopener,noreferrer")}async openInternalLink(r){window.open(r,"_self","noopener,noreferrer")}async closeApp(){window.history.length>1?window.history.back():window.close()}setBackButtonVisibility(r){}onAppearanceChange(r){return r(this.environment.appearance),()=>{}}getInitData(){}getLaunchParams(){return{customLaunchParams:this.readCustomUrlParams()}}decodeStartParam(r){}requestFullscreen(){}onViewportChange(r){if(typeof window>"u")return()=>{};let e=()=>window.visualViewport?.height??window.innerHeight,t=()=>{let i=e();r({height:i,stableHeight:i})};t();let n=()=>t();return window.visualViewport?.addEventListener("resize",n),window.visualViewport?.addEventListener("scroll",n),window.addEventListener("resize",n),()=>{window.visualViewport?.removeEventListener("resize",n),window.visualViewport?.removeEventListener("scroll",n),window.removeEventListener("resize",n)}}getViewportInsets(){}shareMessage(r){return Promise.resolve()}shareUrl(r,e){}async downloadFile(r,e){await J(r,e)}shareStory(r,e){return Promise.resolve()}trackConversionEvent(r,e){}trackPixelEvent(r,e){}copyTextToClipboard(r){return navigator.clipboard.writeText(r).catch(()=>{let e=document.createElement("textarea");e.value=r,e.style.position="fixed",document.body.appendChild(e),e.focus(),e.select();try{document.execCommand("copy")}catch{}document.body.removeChild(e)})}computeSafeArea(){let r=this.getViewportInsets?.(),e=Z();return D({environment:this.environment.safeArea,viewport:r,css:e})}bindCssVariables(r){}vibrateImpact(r){navigator.vibrate?.(10)}vibrateNotification(r){navigator.vibrate?.([10,30,10])}vibrateSelection(){navigator.vibrate?.(5)}onViewHide(r){return()=>{}}onViewRestore(r){return()=>{}}async showPopup(r){let e=[r.title,r.message].filter(Boolean).join(`
|
|
4
2
|
|
|
5
|
-
// src/lib/disposables.ts
|
|
6
|
-
var DisposableBag = class {
|
|
7
|
-
constructor() {
|
|
8
|
-
__publicField(this, "disposers", /* @__PURE__ */ new Set());
|
|
9
|
-
}
|
|
10
|
-
add(disposable) {
|
|
11
|
-
if (!disposable) {
|
|
12
|
-
return () => {
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
const dispose = typeof disposable === "function" ? disposable : typeof disposable.dispose === "function" ? disposable.dispose.bind(disposable) : void 0;
|
|
16
|
-
if (!dispose) {
|
|
17
|
-
return () => {
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
let called = false;
|
|
21
|
-
const wrapped = () => {
|
|
22
|
-
if (called) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
called = true;
|
|
26
|
-
try {
|
|
27
|
-
dispose();
|
|
28
|
-
} catch (error) {
|
|
29
|
-
console.warn("[tvm-app-adapter] disposable failed:", error);
|
|
30
|
-
} finally {
|
|
31
|
-
this.disposers.delete(wrapped);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
this.disposers.add(wrapped);
|
|
35
|
-
return wrapped;
|
|
36
|
-
}
|
|
37
|
-
disposeAll() {
|
|
38
|
-
for (const disposer of Array.from(this.disposers)) {
|
|
39
|
-
try {
|
|
40
|
-
disposer();
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.warn("[tvm-app-adapter] disposeAll failed:", error);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
this.disposers.clear();
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
// src/lib/download.ts
|
|
50
|
-
var isClient = typeof window !== "undefined" && typeof document !== "undefined";
|
|
51
|
-
function getFallbackFileName(url, provided) {
|
|
52
|
-
if (provided && provided.trim().length > 0) {
|
|
53
|
-
return provided;
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
const parsed = new URL(url);
|
|
57
|
-
const path = parsed.pathname.split("/").filter(Boolean).pop();
|
|
58
|
-
if (path) {
|
|
59
|
-
return path;
|
|
60
|
-
}
|
|
61
|
-
} catch {
|
|
62
|
-
}
|
|
63
|
-
return "download";
|
|
64
|
-
}
|
|
65
|
-
function triggerAnchorDownload(href, fileName, { targetBlank }) {
|
|
66
|
-
if (!isClient) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
const anchor = document.createElement("a");
|
|
70
|
-
anchor.href = href;
|
|
71
|
-
anchor.download = fileName;
|
|
72
|
-
anchor.rel = "noopener noreferrer";
|
|
73
|
-
if (targetBlank) {
|
|
74
|
-
anchor.target = "_blank";
|
|
75
|
-
}
|
|
76
|
-
document.body.appendChild(anchor);
|
|
77
|
-
anchor.click();
|
|
78
|
-
document.body.removeChild(anchor);
|
|
79
|
-
}
|
|
80
|
-
async function triggerFileDownload(url, fileName, options) {
|
|
81
|
-
if (!url) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const safeFileName = getFallbackFileName(url, fileName);
|
|
85
|
-
const preferBlob = options?.preferBlob ?? false;
|
|
86
|
-
if (!isClient) {
|
|
87
|
-
if (typeof window !== "undefined") {
|
|
88
|
-
window.open(url, "_blank", "noopener,noreferrer");
|
|
89
|
-
}
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (preferBlob && typeof fetch === "function") {
|
|
93
|
-
try {
|
|
94
|
-
const response = await fetch(url, { credentials: "include" });
|
|
95
|
-
if (!response.ok) {
|
|
96
|
-
throw new Error(`Failed to download file: ${response.status}`);
|
|
97
|
-
}
|
|
98
|
-
const blob = await response.blob();
|
|
99
|
-
const objectUrl = URL.createObjectURL(blob);
|
|
100
|
-
triggerAnchorDownload(objectUrl, safeFileName, { targetBlank: false });
|
|
101
|
-
setTimeout(() => URL.revokeObjectURL(objectUrl), 3e4);
|
|
102
|
-
return;
|
|
103
|
-
} catch (error) {
|
|
104
|
-
console.warn("[tvm-app-adapter] blob download failed, falling back to direct link:", error);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
triggerAnchorDownload(url, safeFileName, { targetBlank: true });
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// src/lib/safeArea.ts
|
|
111
|
-
var SAFE_AREA_EDGES = ["top", "right", "bottom", "left"];
|
|
112
|
-
var DEFAULT_SAFE_AREA = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
113
|
-
function cloneSafeArea(source) {
|
|
114
|
-
return { ...source };
|
|
115
|
-
}
|
|
116
|
-
function addSafeArea(target, source) {
|
|
117
|
-
if (!source) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
for (const edge of SAFE_AREA_EDGES) {
|
|
121
|
-
const value = source[edge];
|
|
122
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
123
|
-
target[edge] += value;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
function applyMinimum(target, source) {
|
|
128
|
-
if (!source) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
for (const edge of SAFE_AREA_EDGES) {
|
|
132
|
-
const value = source[edge];
|
|
133
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
134
|
-
target[edge] = Math.max(target[edge], value);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
function areSafeAreasEqual(a, b) {
|
|
139
|
-
if (!a || !b) {
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
return SAFE_AREA_EDGES.every((edge) => a[edge] === b[edge]);
|
|
143
|
-
}
|
|
144
|
-
function computeCombinedSafeArea(options = {}) {
|
|
145
|
-
const safeArea = cloneSafeArea(DEFAULT_SAFE_AREA);
|
|
146
|
-
addSafeArea(safeArea, options.environment);
|
|
147
|
-
if (options.viewport) {
|
|
148
|
-
addSafeArea(safeArea, options.viewport.safeArea);
|
|
149
|
-
addSafeArea(safeArea, options.viewport.contentSafeArea);
|
|
150
|
-
}
|
|
151
|
-
addSafeArea(safeArea, options.additions);
|
|
152
|
-
applyMinimum(safeArea, options.css);
|
|
153
|
-
applyMinimum(safeArea, options.minimum);
|
|
154
|
-
return safeArea;
|
|
155
|
-
}
|
|
156
|
-
function readCssSafeArea() {
|
|
157
|
-
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
158
|
-
return void 0;
|
|
159
|
-
}
|
|
160
|
-
const styles = getComputedStyle(document.documentElement);
|
|
161
|
-
const readEdge = (prop) => {
|
|
162
|
-
const value = parseFloat(styles.getPropertyValue(prop));
|
|
163
|
-
return Number.isFinite(value) ? value : 0;
|
|
164
|
-
};
|
|
165
|
-
const top = readEdge("--safe-area-inset-top");
|
|
166
|
-
const right = readEdge("--safe-area-inset-right");
|
|
167
|
-
const bottom = readEdge("--safe-area-inset-bottom");
|
|
168
|
-
const left = readEdge("--safe-area-inset-left");
|
|
169
|
-
if (top || right || bottom || left) {
|
|
170
|
-
return { top, right, bottom, left };
|
|
171
|
-
}
|
|
172
|
-
return void 0;
|
|
173
|
-
}
|
|
174
|
-
function normalizeSafeArea(value) {
|
|
175
|
-
if (!value) {
|
|
176
|
-
return void 0;
|
|
177
|
-
}
|
|
178
|
-
const safeArea = { ...DEFAULT_SAFE_AREA };
|
|
179
|
-
addSafeArea(safeArea, value);
|
|
180
|
-
return safeArea;
|
|
181
|
-
}
|
|
182
|
-
function createSafeAreaWatcher(options) {
|
|
183
|
-
const targetWindow = options.windowObj ?? (typeof window !== "undefined" ? window : void 0);
|
|
184
|
-
if (!targetWindow) {
|
|
185
|
-
return void 0;
|
|
186
|
-
}
|
|
187
|
-
let previous = normalizeSafeArea(options.getSafeArea());
|
|
188
|
-
if (previous) {
|
|
189
|
-
options.onChange(previous);
|
|
190
|
-
previous = cloneSafeArea(previous);
|
|
191
|
-
}
|
|
192
|
-
const handler = () => {
|
|
193
|
-
const next = normalizeSafeArea(options.getSafeArea());
|
|
194
|
-
if (!next) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
if (!previous || !areSafeAreasEqual(previous, next)) {
|
|
198
|
-
options.onChange(next);
|
|
199
|
-
previous = cloneSafeArea(next);
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
handler();
|
|
203
|
-
const events = options.events ?? ["resize", "orientationchange"];
|
|
204
|
-
for (const eventName of events) {
|
|
205
|
-
targetWindow.addEventListener(eventName, handler);
|
|
206
|
-
}
|
|
207
|
-
return () => {
|
|
208
|
-
for (const eventName of events) {
|
|
209
|
-
targetWindow.removeEventListener(eventName, handler);
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// src/lib/shell.ts
|
|
215
|
-
var SHELL_PLATFORMS = ["shell_ios", "shell_android"];
|
|
216
|
-
var SHELL_QR_TIMEOUT_MS = 6e4;
|
|
217
|
-
var pushListeners = /* @__PURE__ */ new Set();
|
|
218
|
-
var deepLinkListeners = /* @__PURE__ */ new Set();
|
|
219
|
-
var activeListeners = /* @__PURE__ */ new Set();
|
|
220
|
-
var backgroundListeners = /* @__PURE__ */ new Set();
|
|
221
|
-
var lastPushToken = null;
|
|
222
|
-
var pendingQrRequest = null;
|
|
223
|
-
var DEFAULT_BRIDGE_CONFIG = {
|
|
224
|
-
platformFlag: "nativePlatform",
|
|
225
|
-
pushTokenCallback: "nativePushToken",
|
|
226
|
-
qrResultCallback: "nativeQRResult",
|
|
227
|
-
deepLinkCallback: "nativeDeepLink",
|
|
228
|
-
appActiveCallback: "nativeAppActive",
|
|
229
|
-
appBackgroundCallback: "nativeAppBackground"
|
|
230
|
-
};
|
|
231
|
-
var bridgeConfig = { ...DEFAULT_BRIDGE_CONFIG };
|
|
232
|
-
var installedCallbackNames = {};
|
|
233
|
-
function notifyPushListeners(token) {
|
|
234
|
-
lastPushToken = token;
|
|
235
|
-
for (const listener of pushListeners) {
|
|
236
|
-
try {
|
|
237
|
-
listener(token);
|
|
238
|
-
} catch (error) {
|
|
239
|
-
console.warn("[tvm-app-adapter] push token listener failed:", error);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
function isShell(platform) {
|
|
244
|
-
return SHELL_PLATFORMS.includes(platform);
|
|
245
|
-
}
|
|
246
|
-
function isShellIOS(platform) {
|
|
247
|
-
return platform === "shell_ios";
|
|
248
|
-
}
|
|
249
|
-
function isShellAndroid(platform) {
|
|
250
|
-
return platform === "shell_android";
|
|
251
|
-
}
|
|
252
|
-
function readShellPlatform() {
|
|
253
|
-
const shellWindow = getShellWindow();
|
|
254
|
-
if (!shellWindow) {
|
|
255
|
-
return void 0;
|
|
256
|
-
}
|
|
257
|
-
const platform = shellWindow[bridgeConfig.platformFlag];
|
|
258
|
-
if (platform === "shell_ios" || platform === "shell_android") {
|
|
259
|
-
return platform;
|
|
260
|
-
}
|
|
261
|
-
return void 0;
|
|
262
|
-
}
|
|
263
|
-
function createShellAPI(platform) {
|
|
264
|
-
installGlobalCallbacks();
|
|
265
|
-
const resolvePlatform = typeof platform === "function" ? platform : () => platform;
|
|
266
|
-
return {
|
|
267
|
-
async openNativeQR() {
|
|
268
|
-
const currentPlatform = resolvePlatform();
|
|
269
|
-
if (isShell(currentPlatform)) {
|
|
270
|
-
return openNativeQrViaBridge();
|
|
271
|
-
}
|
|
272
|
-
return startHtml5Qrcode();
|
|
273
|
-
},
|
|
274
|
-
onPushToken(callback) {
|
|
275
|
-
pushListeners.add(callback);
|
|
276
|
-
if (lastPushToken) {
|
|
277
|
-
queueMicrotask(() => {
|
|
278
|
-
try {
|
|
279
|
-
callback(lastPushToken);
|
|
280
|
-
} catch (error) {
|
|
281
|
-
console.warn("[tvm-app-adapter] push token listener failed:", error);
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
return () => pushListeners.delete(callback);
|
|
286
|
-
},
|
|
287
|
-
onDeepLink(callback) {
|
|
288
|
-
deepLinkListeners.add(callback);
|
|
289
|
-
return () => deepLinkListeners.delete(callback);
|
|
290
|
-
},
|
|
291
|
-
onAppActive(callback) {
|
|
292
|
-
activeListeners.add(callback);
|
|
293
|
-
return () => activeListeners.delete(callback);
|
|
294
|
-
},
|
|
295
|
-
onAppBackground(callback) {
|
|
296
|
-
backgroundListeners.add(callback);
|
|
297
|
-
return () => backgroundListeners.delete(callback);
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
var shell = createShellAPI(() => readShellPlatform() ?? "web");
|
|
302
|
-
function storeShellToken(payload) {
|
|
303
|
-
return sendBridgeCommand({ type: "storeToken", payload });
|
|
304
|
-
}
|
|
305
|
-
function requestShellPushPermission() {
|
|
306
|
-
return sendBridgeCommand({ type: "requestPushPermission" });
|
|
307
|
-
}
|
|
308
|
-
function getShellWindow() {
|
|
309
|
-
return typeof window === "undefined" ? null : window;
|
|
310
|
-
}
|
|
311
|
-
function getNativeBridge() {
|
|
312
|
-
const shellWindow = getShellWindow();
|
|
313
|
-
if (!shellWindow) {
|
|
314
|
-
return null;
|
|
315
|
-
}
|
|
316
|
-
const bridge2 = shellWindow.NativeBridge;
|
|
317
|
-
if (!bridge2 || typeof bridge2.postMessage !== "function") {
|
|
318
|
-
return null;
|
|
319
|
-
}
|
|
320
|
-
return bridge2;
|
|
321
|
-
}
|
|
322
|
-
function sendBridgeCommand(command) {
|
|
323
|
-
const bridge2 = getNativeBridge();
|
|
324
|
-
if (!bridge2) {
|
|
325
|
-
return false;
|
|
326
|
-
}
|
|
327
|
-
try {
|
|
328
|
-
bridge2.postMessage(command);
|
|
329
|
-
return true;
|
|
330
|
-
} catch (error) {
|
|
331
|
-
console.warn("[tvm-app-adapter] NativeBridge.postMessage failed:", error);
|
|
332
|
-
return false;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
function installGlobalCallbacks() {
|
|
336
|
-
const shellWindow = getShellWindow();
|
|
337
|
-
if (!shellWindow) {
|
|
338
|
-
installedCallbackNames = {};
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
const target = shellWindow;
|
|
342
|
-
const assignCallback = (key, handler) => {
|
|
343
|
-
const nextName = bridgeConfig[key];
|
|
344
|
-
const previousName = installedCallbackNames[key];
|
|
345
|
-
if (previousName && previousName !== nextName) {
|
|
346
|
-
delete target[previousName];
|
|
347
|
-
}
|
|
348
|
-
installedCallbackNames[key] = nextName;
|
|
349
|
-
target[nextName] = handler;
|
|
350
|
-
};
|
|
351
|
-
assignCallback("pushTokenCallback", (token) => {
|
|
352
|
-
if (typeof token !== "string") {
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
console.log("[tvm-app-adapter] nativePushToken", token);
|
|
356
|
-
notifyPushListeners(token);
|
|
357
|
-
});
|
|
358
|
-
assignCallback("deepLinkCallback", (path) => {
|
|
359
|
-
if (typeof path !== "string") {
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
for (const listener of deepLinkListeners) {
|
|
363
|
-
try {
|
|
364
|
-
listener(path);
|
|
365
|
-
} catch (error) {
|
|
366
|
-
console.warn("[tvm-app-adapter] deep link listener failed:", error);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
assignCallback("appActiveCallback", () => {
|
|
371
|
-
for (const listener of activeListeners) {
|
|
372
|
-
try {
|
|
373
|
-
listener();
|
|
374
|
-
} catch (error) {
|
|
375
|
-
console.warn("[tvm-app-adapter] app active listener failed:", error);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
assignCallback("appBackgroundCallback", () => {
|
|
380
|
-
for (const listener of backgroundListeners) {
|
|
381
|
-
try {
|
|
382
|
-
listener();
|
|
383
|
-
} catch (error) {
|
|
384
|
-
console.warn("[tvm-app-adapter] app background listener failed:", error);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
assignCallback("qrResultCallback", (value) => {
|
|
389
|
-
if (typeof value !== "string") {
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
if (!pendingQrRequest) {
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
clearTimeout(pendingQrRequest.timeoutId);
|
|
396
|
-
pendingQrRequest.resolve(value);
|
|
397
|
-
pendingQrRequest = null;
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
function openNativeQrViaBridge() {
|
|
401
|
-
return new Promise((resolve, reject) => {
|
|
402
|
-
if (pendingQrRequest) {
|
|
403
|
-
pendingQrRequest.reject(new Error("QR request superseded by a new call."));
|
|
404
|
-
clearTimeout(pendingQrRequest.timeoutId);
|
|
405
|
-
pendingQrRequest = null;
|
|
406
|
-
}
|
|
407
|
-
if (!sendBridgeCommand({ type: "openNativeQR" })) {
|
|
408
|
-
reject(new Error("Native bridge is unavailable."));
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
const timeoutId = setTimeout(() => {
|
|
412
|
-
if (pendingQrRequest) {
|
|
413
|
-
pendingQrRequest.reject(new Error("Native QR request timed out."));
|
|
414
|
-
pendingQrRequest = null;
|
|
415
|
-
}
|
|
416
|
-
}, SHELL_QR_TIMEOUT_MS);
|
|
417
|
-
pendingQrRequest = { resolve, reject, timeoutId };
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
async function startHtml5Qrcode() {
|
|
421
|
-
if (typeof document === "undefined") {
|
|
422
|
-
throw new Error("QR scanning requires a browser environment.");
|
|
423
|
-
}
|
|
424
|
-
const [{ Html5Qrcode }] = await Promise.all([import("html5-qrcode")]);
|
|
425
|
-
return new Promise((resolve, reject) => {
|
|
426
|
-
const elementId = `native-shell-qr-${Date.now()}`;
|
|
427
|
-
const overlay = document.createElement("div");
|
|
428
|
-
overlay.style.position = "fixed";
|
|
429
|
-
overlay.style.inset = "0";
|
|
430
|
-
overlay.style.background = "rgba(0, 0, 0, 0.9)";
|
|
431
|
-
overlay.style.display = "flex";
|
|
432
|
-
overlay.style.flexDirection = "column";
|
|
433
|
-
overlay.style.alignItems = "center";
|
|
434
|
-
overlay.style.justifyContent = "center";
|
|
435
|
-
overlay.style.zIndex = "2147483647";
|
|
436
|
-
overlay.style.backdropFilter = "blur(2px)";
|
|
437
|
-
const reader = document.createElement("div");
|
|
438
|
-
reader.id = elementId;
|
|
439
|
-
reader.style.width = "280px";
|
|
440
|
-
reader.style.height = "280px";
|
|
441
|
-
reader.style.borderRadius = "16px";
|
|
442
|
-
reader.style.overflow = "hidden";
|
|
443
|
-
reader.style.position = "relative";
|
|
444
|
-
const closeButton = document.createElement("button");
|
|
445
|
-
closeButton.type = "button";
|
|
446
|
-
closeButton.textContent = "\u2715";
|
|
447
|
-
closeButton.style.position = "absolute";
|
|
448
|
-
closeButton.style.top = "16px";
|
|
449
|
-
closeButton.style.right = "16px";
|
|
450
|
-
closeButton.style.background = "transparent";
|
|
451
|
-
closeButton.style.border = "none";
|
|
452
|
-
closeButton.style.color = "#fff";
|
|
453
|
-
closeButton.style.fontSize = "28px";
|
|
454
|
-
closeButton.style.cursor = "pointer";
|
|
455
|
-
overlay.appendChild(closeButton);
|
|
456
|
-
overlay.appendChild(reader);
|
|
457
|
-
document.body.appendChild(overlay);
|
|
458
|
-
const previousOverflow = document.body.style.overflow;
|
|
459
|
-
document.body.style.overflow = "hidden";
|
|
460
|
-
let disposed = false;
|
|
461
|
-
const scanner = new Html5Qrcode(elementId);
|
|
462
|
-
const cleanup = async (result, error) => {
|
|
463
|
-
if (disposed) {
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
disposed = true;
|
|
467
|
-
try {
|
|
468
|
-
await scanner.stop();
|
|
469
|
-
const maybeClear = scanner.clear;
|
|
470
|
-
if (typeof maybeClear === "function") {
|
|
471
|
-
await Promise.resolve(maybeClear.call(scanner));
|
|
472
|
-
}
|
|
473
|
-
} catch {
|
|
474
|
-
}
|
|
475
|
-
overlay.remove();
|
|
476
|
-
document.body.style.overflow = previousOverflow;
|
|
477
|
-
if (typeof result === "string") {
|
|
478
|
-
resolve(result);
|
|
479
|
-
} else if (error) {
|
|
480
|
-
reject(error);
|
|
481
|
-
} else {
|
|
482
|
-
reject(new Error("QR scanning was cancelled."));
|
|
483
|
-
}
|
|
484
|
-
};
|
|
485
|
-
closeButton.addEventListener("click", () => {
|
|
486
|
-
void cleanup(void 0, new Error("QR scanning was cancelled by the user."));
|
|
487
|
-
});
|
|
488
|
-
scanner.start(
|
|
489
|
-
{ facingMode: "environment" },
|
|
490
|
-
{ fps: 10, qrbox: { width: 240, height: 240 } },
|
|
491
|
-
(decodedText) => {
|
|
492
|
-
void cleanup(decodedText);
|
|
493
|
-
},
|
|
494
|
-
() => {
|
|
495
|
-
}
|
|
496
|
-
).catch((error) => {
|
|
497
|
-
const cause = error instanceof Error ? error : new Error("Unable to start HTML5 QR scanner.");
|
|
498
|
-
void cleanup(void 0, cause);
|
|
499
|
-
});
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// src/adapters/baseAdapter.ts
|
|
504
|
-
var BaseMiniAppAdapter = class {
|
|
505
|
-
constructor(platform, environment) {
|
|
506
|
-
__publicField(this, "ready", false);
|
|
507
|
-
__publicField(this, "environment");
|
|
508
|
-
__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
509
|
-
__publicField(this, "disposables", new DisposableBag());
|
|
510
|
-
__publicField(this, "shell");
|
|
511
|
-
this.shell = createShellAPI(platform);
|
|
512
|
-
this.environment = {
|
|
513
|
-
platform,
|
|
514
|
-
...environment
|
|
515
|
-
};
|
|
516
|
-
if (typeof window !== "undefined") {
|
|
517
|
-
const resizeHandler = () => this.notifyEnvironmentChanged();
|
|
518
|
-
window.addEventListener("resize", resizeHandler);
|
|
519
|
-
this.registerDisposable(() => window.removeEventListener("resize", resizeHandler));
|
|
520
|
-
if (platform !== "web") {
|
|
521
|
-
const cleanup = this.applyScrollGuards();
|
|
522
|
-
if (cleanup) {
|
|
523
|
-
this.registerDisposable(cleanup);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
supports(capability) {
|
|
529
|
-
switch (capability) {
|
|
530
|
-
case "openExternalLink":
|
|
531
|
-
return true;
|
|
532
|
-
default:
|
|
533
|
-
return false;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
get platform() {
|
|
537
|
-
return this.environment.platform;
|
|
538
|
-
}
|
|
539
|
-
async init(_options) {
|
|
540
|
-
this.ready = true;
|
|
541
|
-
}
|
|
542
|
-
isReady() {
|
|
543
|
-
return this.ready;
|
|
544
|
-
}
|
|
545
|
-
getEnvironment() {
|
|
546
|
-
return { ...this.environment };
|
|
547
|
-
}
|
|
548
|
-
destroy() {
|
|
549
|
-
try {
|
|
550
|
-
this.onDestroy();
|
|
551
|
-
} finally {
|
|
552
|
-
this.disposables.disposeAll();
|
|
553
|
-
this.listeners.clear();
|
|
554
|
-
this.ready = false;
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
subscribe(listener) {
|
|
558
|
-
this.listeners.add(listener);
|
|
559
|
-
return () => {
|
|
560
|
-
this.listeners.delete(listener);
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
async setColors(colors) {
|
|
564
|
-
if (colors.background) {
|
|
565
|
-
document.body.style.backgroundColor = colors.background;
|
|
566
|
-
}
|
|
567
|
-
if (colors.header) {
|
|
568
|
-
const meta = document.querySelector('meta[name="theme-color"]');
|
|
569
|
-
if (meta) {
|
|
570
|
-
meta.setAttribute("content", colors.header);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
onBackButton(callback) {
|
|
575
|
-
const handler = () => callback();
|
|
576
|
-
window.addEventListener("popstate", handler);
|
|
577
|
-
return () => window.removeEventListener("popstate", handler);
|
|
578
|
-
}
|
|
579
|
-
onPushToken(callback) {
|
|
580
|
-
return this.shell.onPushToken(callback);
|
|
581
|
-
}
|
|
582
|
-
onDeepLink(callback) {
|
|
583
|
-
return this.shell.onDeepLink(callback);
|
|
584
|
-
}
|
|
585
|
-
async openExternalLink(url) {
|
|
586
|
-
window.open(url, "_blank", "noopener,noreferrer");
|
|
587
|
-
}
|
|
588
|
-
async openInternalLink(url) {
|
|
589
|
-
window.open(url, "_self", "noopener,noreferrer");
|
|
590
|
-
}
|
|
591
|
-
async closeApp() {
|
|
592
|
-
if (window.history.length > 1) {
|
|
593
|
-
window.history.back();
|
|
594
|
-
} else {
|
|
595
|
-
window.close();
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
setBackButtonVisibility(_visible) {
|
|
599
|
-
}
|
|
600
|
-
onAppearanceChange(callback) {
|
|
601
|
-
callback(this.environment.appearance);
|
|
602
|
-
return () => {
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
getInitData() {
|
|
606
|
-
return void 0;
|
|
607
|
-
}
|
|
608
|
-
getLaunchParams() {
|
|
609
|
-
return {
|
|
610
|
-
customLaunchParams: this.readCustomUrlParams()
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
decodeStartParam(_param) {
|
|
614
|
-
return void 0;
|
|
615
|
-
}
|
|
616
|
-
requestFullscreen() {
|
|
617
|
-
}
|
|
618
|
-
onViewportChange(callback) {
|
|
619
|
-
if (typeof window === "undefined") {
|
|
620
|
-
return () => {
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
const fallbackHeight = () => window.visualViewport?.height ?? window.innerHeight;
|
|
624
|
-
const notify = () => {
|
|
625
|
-
const height = fallbackHeight();
|
|
626
|
-
callback({ height, stableHeight: height });
|
|
627
|
-
};
|
|
628
|
-
notify();
|
|
629
|
-
const onResize = () => notify();
|
|
630
|
-
window.visualViewport?.addEventListener("resize", onResize);
|
|
631
|
-
window.visualViewport?.addEventListener("scroll", onResize);
|
|
632
|
-
window.addEventListener("resize", onResize);
|
|
633
|
-
return () => {
|
|
634
|
-
window.visualViewport?.removeEventListener("resize", onResize);
|
|
635
|
-
window.visualViewport?.removeEventListener("scroll", onResize);
|
|
636
|
-
window.removeEventListener("resize", onResize);
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
getViewportInsets() {
|
|
640
|
-
return void 0;
|
|
641
|
-
}
|
|
642
|
-
shareMessage(_message) {
|
|
643
|
-
return Promise.resolve();
|
|
644
|
-
}
|
|
645
|
-
shareUrl(_url, _text) {
|
|
646
|
-
}
|
|
647
|
-
async downloadFile(url, filename) {
|
|
648
|
-
await triggerFileDownload(url, filename);
|
|
649
|
-
}
|
|
650
|
-
shareStory(_mediaUrl, _options) {
|
|
651
|
-
return Promise.resolve();
|
|
652
|
-
}
|
|
653
|
-
trackConversionEvent(_event, _payload) {
|
|
654
|
-
}
|
|
655
|
-
trackPixelEvent(_event, _payload) {
|
|
656
|
-
}
|
|
657
|
-
copyTextToClipboard(text) {
|
|
658
|
-
return navigator.clipboard.writeText(text).catch(() => {
|
|
659
|
-
const textarea = document.createElement("textarea");
|
|
660
|
-
textarea.value = text;
|
|
661
|
-
textarea.style.position = "fixed";
|
|
662
|
-
document.body.appendChild(textarea);
|
|
663
|
-
textarea.focus();
|
|
664
|
-
textarea.select();
|
|
665
|
-
try {
|
|
666
|
-
document.execCommand("copy");
|
|
667
|
-
} catch {
|
|
668
|
-
}
|
|
669
|
-
document.body.removeChild(textarea);
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
computeSafeArea() {
|
|
673
|
-
const viewportInsets = this.getViewportInsets?.();
|
|
674
|
-
const cssSafeArea = readCssSafeArea();
|
|
675
|
-
return computeCombinedSafeArea({
|
|
676
|
-
environment: this.environment.safeArea,
|
|
677
|
-
viewport: viewportInsets,
|
|
678
|
-
css: cssSafeArea
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
bindCssVariables(_mapper) {
|
|
682
|
-
}
|
|
683
|
-
vibrateImpact(_style) {
|
|
684
|
-
navigator.vibrate?.(10);
|
|
685
|
-
}
|
|
686
|
-
vibrateNotification(_type) {
|
|
687
|
-
navigator.vibrate?.([10, 30, 10]);
|
|
688
|
-
}
|
|
689
|
-
vibrateSelection() {
|
|
690
|
-
navigator.vibrate?.(5);
|
|
691
|
-
}
|
|
692
|
-
onViewHide(_callback) {
|
|
693
|
-
return () => {
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
onViewRestore(_callback) {
|
|
697
|
-
return () => {
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
async showPopup(options) {
|
|
701
|
-
const message = [options.title, options.message].filter(Boolean).join("\n\n");
|
|
702
|
-
window.alert(message);
|
|
703
|
-
const firstButton = options.buttons?.[0];
|
|
704
|
-
return firstButton?.id ?? "ok";
|
|
705
|
-
}
|
|
706
|
-
async scanQRCode(_options) {
|
|
707
|
-
return null;
|
|
708
|
-
}
|
|
709
|
-
async requestPhone() {
|
|
710
|
-
return null;
|
|
711
|
-
}
|
|
712
|
-
async requestNotificationsPermission() {
|
|
713
|
-
if (typeof Notification === "undefined" || typeof Notification.requestPermission !== "function") {
|
|
714
|
-
return false;
|
|
715
|
-
}
|
|
716
|
-
try {
|
|
717
|
-
const permission = await Notification.requestPermission();
|
|
718
|
-
return permission === "granted";
|
|
719
|
-
} catch (error) {
|
|
720
|
-
console.warn("[tvm-app-adapter] requestNotificationsPermission fallback failed:", error);
|
|
721
|
-
return false;
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
async addToHomeScreen() {
|
|
725
|
-
return false;
|
|
726
|
-
}
|
|
727
|
-
async checkHomeScreenStatus() {
|
|
728
|
-
return "unknown";
|
|
729
|
-
}
|
|
730
|
-
async denyNotifications() {
|
|
731
|
-
console.warn("[tvm-app-adapter] denyNotifications fallback is not supported in this environment.");
|
|
732
|
-
return false;
|
|
733
|
-
}
|
|
734
|
-
enableVerticalSwipes() {
|
|
735
|
-
}
|
|
736
|
-
disableVerticalSwipes() {
|
|
737
|
-
}
|
|
738
|
-
notifyEnvironmentChanged() {
|
|
739
|
-
for (const listener of this.listeners) {
|
|
740
|
-
try {
|
|
741
|
-
listener();
|
|
742
|
-
} catch (error) {
|
|
743
|
-
console.warn("[tvm-app-adapter] environment listener failed:", error);
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
onDestroy() {
|
|
748
|
-
}
|
|
749
|
-
registerDisposable(disposable) {
|
|
750
|
-
return this.disposables.add(disposable);
|
|
751
|
-
}
|
|
752
|
-
readCustomUrlParams(isServiceParam) {
|
|
753
|
-
if (typeof window === "undefined") {
|
|
754
|
-
return {};
|
|
755
|
-
}
|
|
756
|
-
const result = {};
|
|
757
|
-
const append = (source) => {
|
|
758
|
-
const keys = /* @__PURE__ */ new Set();
|
|
759
|
-
for (const [key] of source.entries()) {
|
|
760
|
-
keys.add(key);
|
|
761
|
-
}
|
|
762
|
-
for (const key of keys) {
|
|
763
|
-
if (isServiceParam?.(key)) {
|
|
764
|
-
continue;
|
|
765
|
-
}
|
|
766
|
-
const values = source.getAll(key);
|
|
767
|
-
if (!values.length) {
|
|
768
|
-
continue;
|
|
769
|
-
}
|
|
770
|
-
result[key] = values.length === 1 ? values[0] : values;
|
|
771
|
-
}
|
|
772
|
-
};
|
|
773
|
-
append(new URLSearchParams(window.location.search));
|
|
774
|
-
const hash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
|
|
775
|
-
if (hash && hash.includes("=")) {
|
|
776
|
-
append(new URLSearchParams(hash));
|
|
777
|
-
}
|
|
778
|
-
return result;
|
|
779
|
-
}
|
|
780
|
-
applyScrollGuards() {
|
|
781
|
-
if (typeof document === "undefined") {
|
|
782
|
-
return void 0;
|
|
783
|
-
}
|
|
784
|
-
const html = document.documentElement;
|
|
785
|
-
const body = document.body;
|
|
786
|
-
if (!html || !body) {
|
|
787
|
-
return void 0;
|
|
788
|
-
}
|
|
789
|
-
const prevHtmlOverscroll = html.style.overscrollBehaviorY;
|
|
790
|
-
const prevBodyOverscroll = body.style.overscrollBehaviorY;
|
|
791
|
-
const prevBodyTouchAction = body.style.touchAction;
|
|
792
|
-
html.style.overscrollBehaviorY = "none";
|
|
793
|
-
body.style.overscrollBehaviorY = "none";
|
|
794
|
-
body.style.touchAction = "manipulation";
|
|
795
|
-
return () => {
|
|
796
|
-
html.style.overscrollBehaviorY = prevHtmlOverscroll;
|
|
797
|
-
body.style.overscrollBehaviorY = prevBodyOverscroll;
|
|
798
|
-
body.style.touchAction = prevBodyTouchAction;
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
};
|
|
802
|
-
|
|
803
|
-
// src/adapters/maxAdapter.ts
|
|
804
|
-
function getMaxBridge() {
|
|
805
|
-
return window.WebApp;
|
|
806
|
-
}
|
|
807
|
-
var MaxMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
808
|
-
constructor() {
|
|
809
|
-
super("max");
|
|
810
|
-
__publicField(this, "backHandlers", /* @__PURE__ */ new Map());
|
|
811
|
-
__publicField(this, "initData");
|
|
812
|
-
__publicField(this, "initDataUnsafe");
|
|
813
|
-
}
|
|
814
|
-
async init(_options) {
|
|
815
|
-
if (this.ready) {
|
|
816
|
-
return;
|
|
817
|
-
}
|
|
818
|
-
const bridge2 = getMaxBridge();
|
|
819
|
-
bridge2?.ready?.();
|
|
820
|
-
this.initData = bridge2?.initData;
|
|
821
|
-
this.initDataUnsafe = bridge2?.initDataUnsafe;
|
|
822
|
-
const environment = {
|
|
823
|
-
platform: "max",
|
|
824
|
-
sdkVersion: bridge2?.version,
|
|
825
|
-
appVersion: bridge2?.version,
|
|
826
|
-
languageCode: bridge2?.initDataUnsafe?.user?.language_code,
|
|
827
|
-
isWebView: true
|
|
828
|
-
};
|
|
829
|
-
this.environment = environment;
|
|
830
|
-
this.ready = true;
|
|
831
|
-
}
|
|
832
|
-
supports(capability) {
|
|
833
|
-
const bridge2 = getMaxBridge();
|
|
834
|
-
switch (capability) {
|
|
835
|
-
case "haptics":
|
|
836
|
-
return Boolean(bridge2?.HapticFeedback?.impactOccurred);
|
|
837
|
-
case "qrScanner":
|
|
838
|
-
return typeof bridge2?.openCodeReader === "function";
|
|
839
|
-
case "closeApp":
|
|
840
|
-
return typeof bridge2?.close === "function";
|
|
841
|
-
case "backButton":
|
|
842
|
-
return Boolean(bridge2?.BackButton?.onClick);
|
|
843
|
-
case "backButtonVisibility":
|
|
844
|
-
return Boolean(bridge2?.BackButton?.show && bridge2.BackButton.hide);
|
|
845
|
-
case "openInternalLink":
|
|
846
|
-
return typeof bridge2?.openMaxLink === "function";
|
|
847
|
-
case "downloadFile":
|
|
848
|
-
return typeof bridge2?.downloadFile === "function";
|
|
849
|
-
case "requestPhone":
|
|
850
|
-
if (!bridge2) {
|
|
851
|
-
return false;
|
|
852
|
-
}
|
|
853
|
-
return typeof bridge2.requestPhoneNumber === "function" || typeof window !== "undefined";
|
|
854
|
-
case "popup":
|
|
855
|
-
return false;
|
|
856
|
-
default:
|
|
857
|
-
return false;
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
getInitData() {
|
|
861
|
-
return this.initData;
|
|
862
|
-
}
|
|
863
|
-
getLaunchParams() {
|
|
864
|
-
return {
|
|
865
|
-
launchParams: this.initDataUnsafe,
|
|
866
|
-
customLaunchParams: this.readCustomUrlParams()
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
onBackButton(callback) {
|
|
870
|
-
const bridge2 = getMaxBridge();
|
|
871
|
-
if (!bridge2?.BackButton?.onClick) {
|
|
872
|
-
return super.onBackButton(callback);
|
|
873
|
-
}
|
|
874
|
-
const wrapped = () => callback();
|
|
875
|
-
const disposer = bridge2.BackButton.onClick(wrapped);
|
|
876
|
-
bridge2.BackButton.show?.();
|
|
877
|
-
const removeFromBag = this.registerDisposable(() => {
|
|
878
|
-
if (typeof disposer === "function") {
|
|
879
|
-
disposer();
|
|
880
|
-
} else {
|
|
881
|
-
bridge2.BackButton?.offClick?.(wrapped);
|
|
882
|
-
}
|
|
883
|
-
this.backHandlers.delete(callback);
|
|
884
|
-
if (!this.backHandlers.size) {
|
|
885
|
-
bridge2.BackButton?.hide?.();
|
|
886
|
-
}
|
|
887
|
-
});
|
|
888
|
-
this.backHandlers.set(callback, removeFromBag);
|
|
889
|
-
return removeFromBag;
|
|
890
|
-
}
|
|
891
|
-
setBackButtonVisibility(visible) {
|
|
892
|
-
const bridge2 = getMaxBridge();
|
|
893
|
-
if (!bridge2?.BackButton) {
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
visible ? bridge2.BackButton.show?.() : bridge2.BackButton.hide?.();
|
|
897
|
-
}
|
|
898
|
-
async openExternalLink(url) {
|
|
899
|
-
const bridge2 = getMaxBridge();
|
|
900
|
-
if (bridge2?.openExternalLink) {
|
|
901
|
-
bridge2.openExternalLink(url);
|
|
902
|
-
return;
|
|
903
|
-
}
|
|
904
|
-
await super.openExternalLink(url);
|
|
905
|
-
}
|
|
906
|
-
async openInternalLink(url) {
|
|
907
|
-
const bridge2 = getMaxBridge();
|
|
908
|
-
if (bridge2?.openMaxLink) {
|
|
909
|
-
bridge2.openMaxLink(url);
|
|
910
|
-
return;
|
|
911
|
-
}
|
|
912
|
-
await super.openInternalLink(url);
|
|
913
|
-
}
|
|
914
|
-
async closeApp() {
|
|
915
|
-
const bridge2 = getMaxBridge();
|
|
916
|
-
if (bridge2?.close) {
|
|
917
|
-
bridge2.close();
|
|
918
|
-
return;
|
|
919
|
-
}
|
|
920
|
-
await super.closeApp();
|
|
921
|
-
}
|
|
922
|
-
vibrateImpact(style) {
|
|
923
|
-
const bridge2 = getMaxBridge();
|
|
924
|
-
if (!bridge2?.HapticFeedback?.impactOccurred) {
|
|
925
|
-
super.vibrateImpact(style);
|
|
926
|
-
return;
|
|
927
|
-
}
|
|
928
|
-
void bridge2.HapticFeedback.impactOccurred(style).catch((error) => {
|
|
929
|
-
console.warn("[mini-app-template] MAX impact haptic failed:", error);
|
|
930
|
-
});
|
|
931
|
-
}
|
|
932
|
-
vibrateNotification(type) {
|
|
933
|
-
const bridge2 = getMaxBridge();
|
|
934
|
-
if (!bridge2?.HapticFeedback?.notificationOccurred) {
|
|
935
|
-
super.vibrateNotification(type);
|
|
936
|
-
return;
|
|
937
|
-
}
|
|
938
|
-
void bridge2.HapticFeedback.notificationOccurred(type).catch((error) => {
|
|
939
|
-
console.warn("[mini-app-template] MAX notification haptic failed:", error);
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
vibrateSelection() {
|
|
943
|
-
const bridge2 = getMaxBridge();
|
|
944
|
-
if (!bridge2?.HapticFeedback?.selectionChanged) {
|
|
945
|
-
super.vibrateSelection();
|
|
946
|
-
return;
|
|
947
|
-
}
|
|
948
|
-
void bridge2.HapticFeedback.selectionChanged().catch((error) => {
|
|
949
|
-
console.warn("[mini-app-template] MAX selection haptic failed:", error);
|
|
950
|
-
});
|
|
951
|
-
}
|
|
952
|
-
async scanQRCode(options) {
|
|
953
|
-
const bridge2 = getMaxBridge();
|
|
954
|
-
if (!bridge2?.openCodeReader) {
|
|
955
|
-
return super.scanQRCode(options);
|
|
956
|
-
}
|
|
957
|
-
try {
|
|
958
|
-
const result = await bridge2.openCodeReader(options?.closeOnCapture !== false);
|
|
959
|
-
return result?.value ?? null;
|
|
960
|
-
} catch (error) {
|
|
961
|
-
console.warn("[mini-app-template] MAX QR scanner failed:", error);
|
|
962
|
-
return null;
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
async requestPhone() {
|
|
966
|
-
const bridge2 = getMaxBridge();
|
|
967
|
-
if (bridge2?.requestPhoneNumber) {
|
|
968
|
-
try {
|
|
969
|
-
const response = await bridge2.requestPhoneNumber();
|
|
970
|
-
return this.extractPhone(response);
|
|
971
|
-
} catch (error) {
|
|
972
|
-
console.warn("[mini-app-template] MAX requestPhone failed:", error);
|
|
973
|
-
return null;
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
return this.requestPhoneViaEvent();
|
|
977
|
-
}
|
|
978
|
-
async showPopup(options) {
|
|
979
|
-
return super.showPopup(options);
|
|
980
|
-
}
|
|
981
|
-
async downloadFile(url, filename) {
|
|
982
|
-
const bridge2 = getMaxBridge();
|
|
983
|
-
if (bridge2?.downloadFile) {
|
|
984
|
-
try {
|
|
985
|
-
await bridge2.downloadFile(url, filename);
|
|
986
|
-
return;
|
|
987
|
-
} catch (error) {
|
|
988
|
-
console.warn("[mini-app-template] MAX downloadFile failed:", error);
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
await super.downloadFile(url, filename);
|
|
992
|
-
}
|
|
993
|
-
async requestPhoneViaEvent() {
|
|
994
|
-
if (typeof window === "undefined") {
|
|
995
|
-
return null;
|
|
996
|
-
}
|
|
997
|
-
let providedPromise;
|
|
998
|
-
const detail = {
|
|
999
|
-
providePromise: (promise) => {
|
|
1000
|
-
providedPromise = promise;
|
|
1001
|
-
}
|
|
1002
|
-
};
|
|
1003
|
-
window.dispatchEvent(new CustomEvent("WebAppRequestPhone", { detail }));
|
|
1004
|
-
if (!providedPromise) {
|
|
1005
|
-
console.warn("[mini-app-template] MAX requestPhone not handled: native promise missing");
|
|
1006
|
-
return null;
|
|
1007
|
-
}
|
|
1008
|
-
try {
|
|
1009
|
-
const result = await providedPromise;
|
|
1010
|
-
return this.extractPhone(result);
|
|
1011
|
-
} catch (error) {
|
|
1012
|
-
console.warn("[mini-app-template] MAX requestPhone promise rejected:", error);
|
|
1013
|
-
return null;
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
extractPhone(data) {
|
|
1017
|
-
if (typeof data === "string") {
|
|
1018
|
-
return data || null;
|
|
1019
|
-
}
|
|
1020
|
-
if (!data || typeof data !== "object") {
|
|
1021
|
-
return null;
|
|
1022
|
-
}
|
|
1023
|
-
const directPhone = data.phone ?? data.phone_number ?? data.phoneNumber;
|
|
1024
|
-
if (typeof directPhone === "string" && directPhone) {
|
|
1025
|
-
return directPhone;
|
|
1026
|
-
}
|
|
1027
|
-
const contact = data.contact;
|
|
1028
|
-
if (contact && typeof contact === "object") {
|
|
1029
|
-
const nested = contact.phone ?? contact.phone_number ?? contact.phoneNumber;
|
|
1030
|
-
if (typeof nested === "string" && nested) {
|
|
1031
|
-
return nested;
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
return null;
|
|
1035
|
-
}
|
|
1036
|
-
onDestroy() {
|
|
1037
|
-
this.backHandlers.clear();
|
|
1038
|
-
super.onDestroy();
|
|
1039
|
-
}
|
|
1040
|
-
};
|
|
1041
|
-
|
|
1042
|
-
// src/adapters/shellAdapter.ts
|
|
1043
|
-
var ShellMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
1044
|
-
constructor(platform) {
|
|
1045
|
-
super(platform, {
|
|
1046
|
-
isWebView: true,
|
|
1047
|
-
hasNativeQR: true,
|
|
1048
|
-
hasPush: true,
|
|
1049
|
-
hasWidgets: true
|
|
1050
|
-
});
|
|
1051
|
-
}
|
|
1052
|
-
supports(capability) {
|
|
1053
|
-
switch (capability) {
|
|
1054
|
-
case "qrScanner":
|
|
1055
|
-
return true;
|
|
1056
|
-
case "notifications":
|
|
1057
|
-
return true;
|
|
1058
|
-
default:
|
|
1059
|
-
return super.supports(capability);
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
async scanQRCode() {
|
|
1063
|
-
try {
|
|
1064
|
-
const value = await this.shell.openNativeQR();
|
|
1065
|
-
return value ?? null;
|
|
1066
|
-
} catch (error) {
|
|
1067
|
-
console.warn("[tvm-app-adapter] shell.openNativeQR failed:", error);
|
|
1068
|
-
return null;
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
async requestNotificationsPermission() {
|
|
1072
|
-
return requestShellPushPermission();
|
|
1073
|
-
}
|
|
1074
|
-
};
|
|
1075
|
-
|
|
1076
|
-
// src/adapters/telegramAdapter.ts
|
|
1077
|
-
import {
|
|
1078
|
-
backButton,
|
|
1079
|
-
emitEvent,
|
|
1080
|
-
hapticFeedback,
|
|
1081
|
-
init as initSDK,
|
|
1082
|
-
initData,
|
|
1083
|
-
mockTelegramEnv,
|
|
1084
|
-
miniApp,
|
|
1085
|
-
openLink,
|
|
1086
|
-
popup,
|
|
1087
|
-
qrScanner,
|
|
1088
|
-
postEvent,
|
|
1089
|
-
retrieveLaunchParams,
|
|
1090
|
-
setDebug,
|
|
1091
|
-
themeParams,
|
|
1092
|
-
viewport
|
|
1093
|
-
} from "@tma.js/sdk-react";
|
|
1094
|
-
import {
|
|
1095
|
-
decodeStartParam,
|
|
1096
|
-
closingBehavior,
|
|
1097
|
-
requestContact,
|
|
1098
|
-
requestPhoneAccess,
|
|
1099
|
-
swipeBehavior,
|
|
1100
|
-
viewport as rawViewport,
|
|
1101
|
-
shareURL as shareURLSdk,
|
|
1102
|
-
copyTextToClipboard as copyTextToClipboardSdk,
|
|
1103
|
-
downloadFile as downloadFileSdk,
|
|
1104
|
-
shareStory as shareStorySdk,
|
|
1105
|
-
addToHomeScreen as addToHomeScreenSdk,
|
|
1106
|
-
checkHomeScreenStatus as checkHomeScreenStatusSdk,
|
|
1107
|
-
on,
|
|
1108
|
-
off
|
|
1109
|
-
} from "@tma.js/sdk";
|
|
1110
|
-
|
|
1111
|
-
// src/lib/features.ts
|
|
1112
|
-
function isFeatureAvailable(feature) {
|
|
1113
|
-
if (typeof feature !== "function") {
|
|
1114
|
-
return false;
|
|
1115
|
-
}
|
|
1116
|
-
const candidate = feature;
|
|
1117
|
-
if (typeof candidate.isAvailable === "function") {
|
|
1118
|
-
try {
|
|
1119
|
-
return candidate.isAvailable();
|
|
1120
|
-
} catch (error) {
|
|
1121
|
-
console.warn("[tvm-app-adapter] feature availability check failed:", error);
|
|
1122
|
-
return false;
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
return true;
|
|
1126
|
-
}
|
|
1127
|
-
function ensureFeature(feature, ...args) {
|
|
1128
|
-
if (!isFeatureAvailable(feature)) {
|
|
1129
|
-
return { ok: false };
|
|
1130
|
-
}
|
|
1131
|
-
try {
|
|
1132
|
-
const value = feature(...args);
|
|
1133
|
-
return { ok: true, value };
|
|
1134
|
-
} catch (error) {
|
|
1135
|
-
console.warn("[tvm-app-adapter] feature call failed:", error);
|
|
1136
|
-
return { ok: false };
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
// src/lib/viewport.ts
|
|
1141
|
-
async function ensureViewportMounted(options) {
|
|
1142
|
-
const { sdkViewport, fallbackMount } = options;
|
|
1143
|
-
if (sdkViewport?.isSupported?.()) {
|
|
1144
|
-
if (typeof sdkViewport.isMounted === "function" && sdkViewport.isMounted()) {
|
|
1145
|
-
return;
|
|
1146
|
-
}
|
|
1147
|
-
if (typeof sdkViewport.mount === "function") {
|
|
1148
|
-
await sdkViewport.mount();
|
|
1149
|
-
}
|
|
1150
|
-
return;
|
|
1151
|
-
}
|
|
1152
|
-
if (typeof fallbackMount === "function") {
|
|
1153
|
-
await fallbackMount();
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
async function bindViewportCssVars(options) {
|
|
1157
|
-
await ensureViewportMounted(options);
|
|
1158
|
-
if (typeof options.bindCssVars !== "function") {
|
|
1159
|
-
return;
|
|
1160
|
-
}
|
|
1161
|
-
try {
|
|
1162
|
-
options.bindCssVars(options.mapper);
|
|
1163
|
-
} catch (error) {
|
|
1164
|
-
if (error instanceof Error && /css variables are already bound/i.test(error.message)) {
|
|
1165
|
-
return;
|
|
1166
|
-
}
|
|
1167
|
-
throw error;
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
// src/adapters/telegramAdapter.ts
|
|
1172
|
-
var TelegramMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
1173
|
-
constructor() {
|
|
1174
|
-
super("telegram");
|
|
1175
|
-
__publicField(this, "backHandlers", /* @__PURE__ */ new Map());
|
|
1176
|
-
__publicField(this, "cssVariablesBound", false);
|
|
1177
|
-
__publicField(this, "appearanceListeners", /* @__PURE__ */ new Set());
|
|
1178
|
-
__publicField(this, "appearanceWatcherDispose");
|
|
1179
|
-
__publicField(this, "viewHideListeners", /* @__PURE__ */ new Set());
|
|
1180
|
-
__publicField(this, "viewRestoreListeners", /* @__PURE__ */ new Set());
|
|
1181
|
-
__publicField(this, "activeWatcherDispose");
|
|
1182
|
-
}
|
|
1183
|
-
async init(options) {
|
|
1184
|
-
if (this.ready) {
|
|
1185
|
-
return;
|
|
1186
|
-
}
|
|
1187
|
-
const debug = Boolean(options?.debug);
|
|
1188
|
-
const eruda = Boolean(options?.eruda);
|
|
1189
|
-
const mockForMacOS = Boolean(options?.mockForMacOS);
|
|
1190
|
-
setDebug(debug);
|
|
1191
|
-
initSDK();
|
|
1192
|
-
if (!miniApp.isSupported()) {
|
|
1193
|
-
console.warn("[tvm-app-adapter] miniApp feature is not supported; falling back to limited mode.");
|
|
1194
|
-
}
|
|
1195
|
-
if (eruda) {
|
|
1196
|
-
void import("eruda").then(({ default: erudaInstance }) => {
|
|
1197
|
-
erudaInstance.init();
|
|
1198
|
-
erudaInstance.position({ x: window.innerWidth - 150, y: window.innerHeight - 150 });
|
|
1199
|
-
});
|
|
1200
|
-
}
|
|
1201
|
-
if (mockForMacOS) {
|
|
1202
|
-
let firstThemeSent = false;
|
|
1203
|
-
mockTelegramEnv({
|
|
1204
|
-
onEvent(event, next) {
|
|
1205
|
-
if (event.name === "web_app_request_theme") {
|
|
1206
|
-
let tp = {};
|
|
1207
|
-
if (firstThemeSent) {
|
|
1208
|
-
tp = themeParams.state();
|
|
1209
|
-
} else {
|
|
1210
|
-
firstThemeSent = true;
|
|
1211
|
-
tp || (tp = retrieveLaunchParams().tgWebAppThemeParams);
|
|
1212
|
-
}
|
|
1213
|
-
return emitEvent("theme_changed", { theme_params: tp });
|
|
1214
|
-
}
|
|
1215
|
-
if (event.name === "web_app_request_safe_area") {
|
|
1216
|
-
return emitEvent("safe_area_changed", { left: 0, top: 0, right: 0, bottom: 0 });
|
|
1217
|
-
}
|
|
1218
|
-
next();
|
|
1219
|
-
}
|
|
1220
|
-
});
|
|
1221
|
-
}
|
|
1222
|
-
initData.restore();
|
|
1223
|
-
miniApp.ready();
|
|
1224
|
-
const launchParams = retrieveLaunchParams();
|
|
1225
|
-
let appearance;
|
|
1226
|
-
try {
|
|
1227
|
-
appearance = miniApp.isDark() ? "dark" : "light";
|
|
1228
|
-
} catch {
|
|
1229
|
-
appearance = void 0;
|
|
1230
|
-
}
|
|
1231
|
-
const environment = {
|
|
1232
|
-
platform: "telegram",
|
|
1233
|
-
sdkVersion: launchParams.tgWebAppVersion,
|
|
1234
|
-
languageCode: initData.user()?.language_code,
|
|
1235
|
-
appearance,
|
|
1236
|
-
isWebView: true
|
|
1237
|
-
};
|
|
1238
|
-
this.environment = environment;
|
|
1239
|
-
this.notifyAppearance(environment.appearance);
|
|
1240
|
-
backButton.mount.ifAvailable();
|
|
1241
|
-
if (miniApp.mount.isAvailable()) {
|
|
1242
|
-
themeParams.mount();
|
|
1243
|
-
miniApp.mount();
|
|
1244
|
-
this.bindCssVariables();
|
|
1245
|
-
}
|
|
1246
|
-
await this.prepareViewport();
|
|
1247
|
-
this.setupAppearanceWatcher();
|
|
1248
|
-
this.setupActiveWatcher();
|
|
1249
|
-
this.ready = true;
|
|
1250
|
-
}
|
|
1251
|
-
async setColors(colors) {
|
|
1252
|
-
const fallback = {};
|
|
1253
|
-
if (colors.header) {
|
|
1254
|
-
if (miniApp.setHeaderColor.isAvailable()) {
|
|
1255
|
-
const headerColor = miniApp.setHeaderColor.supports?.("rgb") ? colors.header : "bg_color";
|
|
1256
|
-
const { ok } = ensureFeature(miniApp.setHeaderColor, headerColor);
|
|
1257
|
-
if (!ok) {
|
|
1258
|
-
fallback.header = colors.header;
|
|
1259
|
-
}
|
|
1260
|
-
} else {
|
|
1261
|
-
fallback.header = colors.header;
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
if (colors.background) {
|
|
1265
|
-
if (miniApp.setBgColor.isAvailable()) {
|
|
1266
|
-
const { ok } = ensureFeature(miniApp.setBgColor, colors.background);
|
|
1267
|
-
if (!ok) {
|
|
1268
|
-
fallback.background = colors.background;
|
|
1269
|
-
}
|
|
1270
|
-
} else {
|
|
1271
|
-
fallback.background = colors.background;
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
if (colors.footer) {
|
|
1275
|
-
if (miniApp.setBgColor.isAvailable()) {
|
|
1276
|
-
const { ok } = ensureFeature(miniApp.setBottomBarColorFp, colors.footer);
|
|
1277
|
-
if (!ok) {
|
|
1278
|
-
fallback.footer = colors.footer;
|
|
1279
|
-
}
|
|
1280
|
-
} else {
|
|
1281
|
-
fallback.footer = colors.footer;
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
if (fallback.header || fallback.background) {
|
|
1285
|
-
await super.setColors(fallback);
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
copyTextToClipboard(text) {
|
|
1289
|
-
return copyTextToClipboardSdk(text);
|
|
1290
|
-
}
|
|
1291
|
-
onBackButton(callback) {
|
|
1292
|
-
if (!backButton.isSupported()) {
|
|
1293
|
-
return super.onBackButton(callback);
|
|
1294
|
-
}
|
|
1295
|
-
const dispose = backButton.onClick(() => callback());
|
|
1296
|
-
const removeFromBag = this.registerDisposable(() => {
|
|
1297
|
-
if (typeof dispose === "function") {
|
|
1298
|
-
dispose();
|
|
1299
|
-
}
|
|
1300
|
-
this.backHandlers.delete(callback);
|
|
1301
|
-
if (!this.backHandlers.size) {
|
|
1302
|
-
backButton.hide();
|
|
1303
|
-
}
|
|
1304
|
-
});
|
|
1305
|
-
this.backHandlers.set(callback, removeFromBag);
|
|
1306
|
-
return removeFromBag;
|
|
1307
|
-
}
|
|
1308
|
-
async openExternalLink(url) {
|
|
1309
|
-
try {
|
|
1310
|
-
openLink(url, { tryInstantView: true });
|
|
1311
|
-
return;
|
|
1312
|
-
} catch {
|
|
1313
|
-
}
|
|
1314
|
-
await super.openExternalLink(url);
|
|
1315
|
-
}
|
|
1316
|
-
async openInternalLink(url) {
|
|
1317
|
-
postEvent("web_app_open_tg_link", { path_full: url });
|
|
1318
|
-
}
|
|
1319
|
-
enableDebug(state) {
|
|
1320
|
-
try {
|
|
1321
|
-
state ? closingBehavior.enableConfirmation() : closingBehavior.disableConfirmation();
|
|
1322
|
-
} catch {
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
supports(capability) {
|
|
1326
|
-
switch (capability) {
|
|
1327
|
-
case "haptics":
|
|
1328
|
-
return isFeatureAvailable(hapticFeedback.selectionChanged);
|
|
1329
|
-
case "popup":
|
|
1330
|
-
return isFeatureAvailable(popup.show);
|
|
1331
|
-
case "qrScanner":
|
|
1332
|
-
return isFeatureAvailable(qrScanner.open);
|
|
1333
|
-
case "closeApp":
|
|
1334
|
-
return isFeatureAvailable(miniApp.close);
|
|
1335
|
-
case "backButton":
|
|
1336
|
-
return backButton.isSupported();
|
|
1337
|
-
case "backButtonVisibility":
|
|
1338
|
-
return backButton.hide.isSupported();
|
|
1339
|
-
case "bindCssVariables":
|
|
1340
|
-
return true;
|
|
1341
|
-
case "openExternalLink":
|
|
1342
|
-
return isFeatureAvailable(openLink);
|
|
1343
|
-
case "openInternalLink":
|
|
1344
|
-
return true;
|
|
1345
|
-
case "requestFullscreen":
|
|
1346
|
-
return Boolean(
|
|
1347
|
-
typeof rawViewport.requestFullscreen === "function" || viewport.requestFullscreen?.isAvailable?.()
|
|
1348
|
-
);
|
|
1349
|
-
case "verticalSwipes":
|
|
1350
|
-
return Boolean(
|
|
1351
|
-
swipeBehavior.enableVertical.isAvailable() || swipeBehavior.disableVertical.isAvailable()
|
|
1352
|
-
);
|
|
1353
|
-
case "viewVisibility":
|
|
1354
|
-
return true;
|
|
1355
|
-
case "shareUrl":
|
|
1356
|
-
return typeof shareURLSdk === "function";
|
|
1357
|
-
case "shareStory":
|
|
1358
|
-
return typeof shareStorySdk === "function";
|
|
1359
|
-
case "copyTextToClipboard":
|
|
1360
|
-
return typeof copyTextToClipboardSdk === "function";
|
|
1361
|
-
case "downloadFile":
|
|
1362
|
-
return typeof downloadFileSdk === "function";
|
|
1363
|
-
case "addToHomeScreen":
|
|
1364
|
-
return typeof addToHomeScreenSdk?.isAvailable === "function" ? addToHomeScreenSdk.isAvailable() : typeof addToHomeScreenSdk === "function";
|
|
1365
|
-
case "checkHomeScreenStatus":
|
|
1366
|
-
return typeof checkHomeScreenStatusSdk === "function";
|
|
1367
|
-
case "requestPhone": {
|
|
1368
|
-
return Boolean(isFeatureAvailable(requestPhoneAccess) || isFeatureAvailable(requestContact));
|
|
1369
|
-
}
|
|
1370
|
-
default:
|
|
1371
|
-
return false;
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
bindCssVariables(mapper) {
|
|
1375
|
-
if (this.cssVariablesBound) {
|
|
1376
|
-
return;
|
|
1377
|
-
}
|
|
1378
|
-
try {
|
|
1379
|
-
themeParams.bindCssVars(mapper);
|
|
1380
|
-
this.cssVariablesBound = true;
|
|
1381
|
-
} catch (error) {
|
|
1382
|
-
if (error instanceof Error && /css variables are already bound/i.test(error.message)) {
|
|
1383
|
-
this.cssVariablesBound = true;
|
|
1384
|
-
return;
|
|
1385
|
-
}
|
|
1386
|
-
throw error;
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
vibrateImpact(style) {
|
|
1390
|
-
if (this.supports("haptics")) {
|
|
1391
|
-
hapticFeedback.impactOccurred(style);
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
vibrateNotification(type) {
|
|
1395
|
-
if (this.supports("haptics")) {
|
|
1396
|
-
hapticFeedback.notificationOccurred(type);
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
vibrateSelection() {
|
|
1400
|
-
if (this.supports("haptics")) {
|
|
1401
|
-
hapticFeedback.selectionChanged();
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
async showPopup(options) {
|
|
1405
|
-
const popupResult = ensureFeature(popup.show, {
|
|
1406
|
-
title: options.title,
|
|
1407
|
-
message: options.message,
|
|
1408
|
-
buttons: options.buttons?.map((button) => ({
|
|
1409
|
-
id: button.id,
|
|
1410
|
-
text: button.text ?? button.id,
|
|
1411
|
-
type: button.type ?? "default"
|
|
1412
|
-
}))
|
|
1413
|
-
});
|
|
1414
|
-
if (!popupResult.ok) {
|
|
1415
|
-
return super.showPopup(options);
|
|
1416
|
-
}
|
|
1417
|
-
const response = await popupResult.value;
|
|
1418
|
-
return response ?? null;
|
|
1419
|
-
}
|
|
1420
|
-
async scanQRCode(options) {
|
|
1421
|
-
let result = null;
|
|
1422
|
-
const closeOnCapture = options?.closeOnCapture ?? true;
|
|
1423
|
-
const qrScannerResult = ensureFeature(qrScanner.open, {
|
|
1424
|
-
onCaptured: (qr) => {
|
|
1425
|
-
result = qr;
|
|
1426
|
-
if (closeOnCapture) {
|
|
1427
|
-
void ensureFeature(qrScanner.close);
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
});
|
|
1431
|
-
if (!qrScannerResult.ok) {
|
|
1432
|
-
return super.scanQRCode(options);
|
|
1433
|
-
}
|
|
1434
|
-
await qrScannerResult.value;
|
|
1435
|
-
return result;
|
|
1436
|
-
}
|
|
1437
|
-
async closeApp() {
|
|
1438
|
-
const closeResult = ensureFeature(miniApp.close);
|
|
1439
|
-
if (closeResult.ok) {
|
|
1440
|
-
return;
|
|
1441
|
-
}
|
|
1442
|
-
await super.closeApp();
|
|
1443
|
-
}
|
|
1444
|
-
getInitData() {
|
|
1445
|
-
try {
|
|
1446
|
-
return initData.raw();
|
|
1447
|
-
} catch {
|
|
1448
|
-
return void 0;
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
getLaunchParams() {
|
|
1452
|
-
const customFromUrl = this.readCustomUrlParams((key) => key.toLowerCase().startsWith("tgwebapp"));
|
|
1453
|
-
let customFromStartParam = {};
|
|
1454
|
-
try {
|
|
1455
|
-
const launchParams = retrieveLaunchParams();
|
|
1456
|
-
const startParam = launchParams.tgWebAppStartParam;
|
|
1457
|
-
if (typeof startParam === "string" && startParam) {
|
|
1458
|
-
customFromStartParam = this.normalizeDecodedStartParam(startParam);
|
|
1459
|
-
}
|
|
1460
|
-
return {
|
|
1461
|
-
launchParams,
|
|
1462
|
-
customLaunchParams: {
|
|
1463
|
-
...customFromUrl,
|
|
1464
|
-
...customFromStartParam
|
|
1465
|
-
}
|
|
1466
|
-
};
|
|
1467
|
-
} catch {
|
|
1468
|
-
return {
|
|
1469
|
-
customLaunchParams: {
|
|
1470
|
-
...customFromUrl,
|
|
1471
|
-
...customFromStartParam
|
|
1472
|
-
}
|
|
1473
|
-
};
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
decodeStartParam(param) {
|
|
1477
|
-
try {
|
|
1478
|
-
return decodeStartParam(param);
|
|
1479
|
-
} catch {
|
|
1480
|
-
return void 0;
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
requestFullscreen() {
|
|
1484
|
-
void this.requestFullscreenInternal();
|
|
1485
|
-
}
|
|
1486
|
-
getViewportInsets() {
|
|
1487
|
-
try {
|
|
1488
|
-
const safeArea = viewport.safeAreaInsets();
|
|
1489
|
-
const contentSafeArea = viewport.contentSafeAreaInsets();
|
|
1490
|
-
return {
|
|
1491
|
-
safeArea,
|
|
1492
|
-
contentSafeArea
|
|
1493
|
-
};
|
|
1494
|
-
} catch {
|
|
1495
|
-
return void 0;
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
onViewportChange(callback) {
|
|
1499
|
-
const disposers = [];
|
|
1500
|
-
const fallbackHeight = () => typeof window !== "undefined" ? window.visualViewport?.height ?? window.innerHeight : 0;
|
|
1501
|
-
const notify = (state) => {
|
|
1502
|
-
const heightCandidate = state?.height ?? this.safeHeightFromSdk();
|
|
1503
|
-
const stableCandidate = state?.stableHeight ?? this.stableHeightFromSdk();
|
|
1504
|
-
const height = Number.isFinite(heightCandidate) ? heightCandidate : fallbackHeight();
|
|
1505
|
-
const stableHeight = Number.isFinite(stableCandidate) && stableCandidate > 0 ? stableCandidate : height;
|
|
1506
|
-
callback({ height, stableHeight });
|
|
1507
|
-
};
|
|
1508
|
-
const ensureMounted = async () => {
|
|
1509
|
-
try {
|
|
1510
|
-
await ensureViewportMounted(this.getViewportMountOptions());
|
|
1511
|
-
} catch (error) {
|
|
1512
|
-
console.warn("[tvm-app-adapter] ensureViewportMounted failed:", error);
|
|
1513
|
-
}
|
|
1514
|
-
};
|
|
1515
|
-
void ensureMounted().finally(() => notify());
|
|
1516
|
-
const { sdkViewport } = this.getViewportMountOptions();
|
|
1517
|
-
if (typeof sdkViewport.on === "function") {
|
|
1518
|
-
try {
|
|
1519
|
-
const off2 = sdkViewport.on("change", (next) => notify(next));
|
|
1520
|
-
if (typeof off2 === "function") {
|
|
1521
|
-
disposers.push(off2);
|
|
1522
|
-
}
|
|
1523
|
-
} catch (error) {
|
|
1524
|
-
console.warn("[tvm-app-adapter] viewport.on(change) subscription failed:", error);
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
try {
|
|
1528
|
-
if (typeof sdkViewport.height?.sub === "function") {
|
|
1529
|
-
disposers.push(sdkViewport.height.sub(() => notify()));
|
|
1530
|
-
}
|
|
1531
|
-
if (typeof sdkViewport.stableHeight?.sub === "function") {
|
|
1532
|
-
disposers.push(sdkViewport.stableHeight.sub(() => notify()));
|
|
1533
|
-
}
|
|
1534
|
-
} catch (error) {
|
|
1535
|
-
console.warn("[tvm-app-adapter] viewport signal subscriptions failed:", error);
|
|
1536
|
-
}
|
|
1537
|
-
if (typeof window !== "undefined") {
|
|
1538
|
-
const onResize = () => notify();
|
|
1539
|
-
window.visualViewport?.addEventListener("resize", onResize);
|
|
1540
|
-
window.addEventListener("resize", onResize);
|
|
1541
|
-
disposers.push(() => {
|
|
1542
|
-
window.visualViewport?.removeEventListener("resize", onResize);
|
|
1543
|
-
window.removeEventListener("resize", onResize);
|
|
1544
|
-
});
|
|
1545
|
-
}
|
|
1546
|
-
return () => {
|
|
1547
|
-
disposers.forEach((dispose) => {
|
|
1548
|
-
try {
|
|
1549
|
-
dispose();
|
|
1550
|
-
} catch {
|
|
1551
|
-
}
|
|
1552
|
-
});
|
|
1553
|
-
};
|
|
1554
|
-
}
|
|
1555
|
-
onAppearanceChange(callback) {
|
|
1556
|
-
this.appearanceListeners.add(callback);
|
|
1557
|
-
callback(this.environment.appearance);
|
|
1558
|
-
return () => {
|
|
1559
|
-
this.appearanceListeners.delete(callback);
|
|
1560
|
-
};
|
|
1561
|
-
}
|
|
1562
|
-
setBackButtonVisibility(visible) {
|
|
1563
|
-
if (!backButton.isSupported()) {
|
|
1564
|
-
return;
|
|
1565
|
-
}
|
|
1566
|
-
if (visible) {
|
|
1567
|
-
backButton.show();
|
|
1568
|
-
} else {
|
|
1569
|
-
backButton.hide();
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
enableVerticalSwipes() {
|
|
1573
|
-
try {
|
|
1574
|
-
const sdkSwipe = swipeBehavior;
|
|
1575
|
-
if (typeof sdkSwipe.isSupported === "function" && sdkSwipe.isSupported()) {
|
|
1576
|
-
if (typeof sdkSwipe.isMounted === "function" && !sdkSwipe.isMounted()) {
|
|
1577
|
-
sdkSwipe.mount?.();
|
|
1578
|
-
}
|
|
1579
|
-
sdkSwipe.enableVertical?.();
|
|
1580
|
-
} else if (swipeBehavior.enableVertical.isAvailable()) {
|
|
1581
|
-
swipeBehavior.enableVertical();
|
|
1582
|
-
}
|
|
1583
|
-
} catch (error) {
|
|
1584
|
-
console.warn("[tvm-app-adapter] enableVerticalSwipes failed:", error);
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
disableVerticalSwipes() {
|
|
1588
|
-
try {
|
|
1589
|
-
const sdkSwipe = swipeBehavior;
|
|
1590
|
-
if (typeof sdkSwipe.isSupported === "function" && sdkSwipe.isSupported()) {
|
|
1591
|
-
if (typeof sdkSwipe.isMounted === "function" && !sdkSwipe.isMounted()) {
|
|
1592
|
-
sdkSwipe.mount?.();
|
|
1593
|
-
}
|
|
1594
|
-
sdkSwipe.disableVertical?.();
|
|
1595
|
-
} else if (swipeBehavior.disableVertical.isAvailable()) {
|
|
1596
|
-
swipeBehavior.disableVertical();
|
|
1597
|
-
}
|
|
1598
|
-
} catch (error) {
|
|
1599
|
-
console.warn("[tvm-app-adapter] disableVerticalSwipes failed:", error);
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
onViewHide(callback) {
|
|
1603
|
-
this.viewHideListeners.add(callback);
|
|
1604
|
-
return () => {
|
|
1605
|
-
this.viewHideListeners.delete(callback);
|
|
1606
|
-
};
|
|
1607
|
-
}
|
|
1608
|
-
onViewRestore(callback) {
|
|
1609
|
-
this.viewRestoreListeners.add(callback);
|
|
1610
|
-
return () => {
|
|
1611
|
-
this.viewRestoreListeners.delete(callback);
|
|
1612
|
-
};
|
|
1613
|
-
}
|
|
1614
|
-
shareUrl(url, text) {
|
|
1615
|
-
return shareURLSdk(url, text);
|
|
1616
|
-
}
|
|
1617
|
-
async downloadFile(url, filename) {
|
|
1618
|
-
const result = ensureFeature(downloadFileSdk, url, filename);
|
|
1619
|
-
if (result.ok) {
|
|
1620
|
-
try {
|
|
1621
|
-
await result.value;
|
|
1622
|
-
return;
|
|
1623
|
-
} catch (error) {
|
|
1624
|
-
console.warn("[tvm-app-adapter] Telegram downloadFile failed:", error);
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
await super.downloadFile(url, filename);
|
|
1628
|
-
}
|
|
1629
|
-
async shareStory(mediaUrl, options) {
|
|
1630
|
-
const text = options?.telegram?.text ?? options?.text;
|
|
1631
|
-
const widgetLink = options?.telegram?.widgetLink ?? (options?.link ? {
|
|
1632
|
-
url: options.link.url,
|
|
1633
|
-
...options.link.name ? { name: options.link.name } : {}
|
|
1634
|
-
} : void 0);
|
|
1635
|
-
shareStorySdk(mediaUrl, {
|
|
1636
|
-
...text ? { text } : {},
|
|
1637
|
-
...widgetLink ? { widgetLink } : {}
|
|
1638
|
-
});
|
|
1639
|
-
}
|
|
1640
|
-
async addToHomeScreen() {
|
|
1641
|
-
const isAvailable = typeof addToHomeScreenSdk?.isAvailable === "function" ? addToHomeScreenSdk.isAvailable() : true;
|
|
1642
|
-
if (!isAvailable) {
|
|
1643
|
-
return super.addToHomeScreen();
|
|
1644
|
-
}
|
|
1645
|
-
return new Promise((resolve) => {
|
|
1646
|
-
const cleanup = () => {
|
|
1647
|
-
off("home_screen_added", handleSuccess);
|
|
1648
|
-
off("home_screen_failed", handleFail);
|
|
1649
|
-
};
|
|
1650
|
-
const handleSuccess = () => {
|
|
1651
|
-
cleanup();
|
|
1652
|
-
resolve(true);
|
|
1653
|
-
};
|
|
1654
|
-
const handleFail = () => {
|
|
1655
|
-
cleanup();
|
|
1656
|
-
resolve(false);
|
|
1657
|
-
};
|
|
1658
|
-
on("home_screen_added", handleSuccess);
|
|
1659
|
-
on("home_screen_failed", handleFail);
|
|
1660
|
-
try {
|
|
1661
|
-
addToHomeScreenSdk();
|
|
1662
|
-
} catch (error) {
|
|
1663
|
-
cleanup();
|
|
1664
|
-
console.warn("[tvm-app-adapter] Telegram addToHomeScreen failed:", error);
|
|
1665
|
-
resolve(false);
|
|
1666
|
-
}
|
|
1667
|
-
});
|
|
1668
|
-
}
|
|
1669
|
-
async checkHomeScreenStatus() {
|
|
1670
|
-
try {
|
|
1671
|
-
const status = await checkHomeScreenStatusSdk();
|
|
1672
|
-
if (typeof status === "string") {
|
|
1673
|
-
return status;
|
|
1674
|
-
}
|
|
1675
|
-
if (typeof status === "boolean") {
|
|
1676
|
-
return status ? "added" : "not_added";
|
|
1677
|
-
}
|
|
1678
|
-
return "unknown";
|
|
1679
|
-
} catch (error) {
|
|
1680
|
-
console.warn("[tvm-app-adapter] Telegram checkHomeScreenStatus failed:", error);
|
|
1681
|
-
return "unknown";
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
async requestPhone() {
|
|
1685
|
-
const contactFeature = ensureFeature(requestContact);
|
|
1686
|
-
if (!contactFeature.ok) {
|
|
1687
|
-
return super.requestPhone();
|
|
1688
|
-
}
|
|
1689
|
-
if (requestPhoneAccess) {
|
|
1690
|
-
const accessFeature = ensureFeature(requestPhoneAccess);
|
|
1691
|
-
if (accessFeature.ok) {
|
|
1692
|
-
try {
|
|
1693
|
-
await accessFeature.value;
|
|
1694
|
-
} catch (error) {
|
|
1695
|
-
console.warn("[tvm-app-adapter] Telegram requestPhone access failed:", error);
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
try {
|
|
1700
|
-
const result = await contactFeature.value;
|
|
1701
|
-
if (!result || typeof result !== "object") {
|
|
1702
|
-
return null;
|
|
1703
|
-
}
|
|
1704
|
-
const contact = result.contact;
|
|
1705
|
-
const phone = contact?.phoneNumber ?? contact?.phone_number ?? contact?.phone ?? result.phoneNumber ?? result.phone_number ?? result.phone;
|
|
1706
|
-
return typeof phone === "string" && phone ? phone : null;
|
|
1707
|
-
} catch (error) {
|
|
1708
|
-
console.warn("[tvm-app-adapter] Telegram requestPhone failed:", error);
|
|
1709
|
-
return null;
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
setupAppearanceWatcher() {
|
|
1713
|
-
this.appearanceWatcherDispose?.();
|
|
1714
|
-
if (typeof themeParams.isDark?.sub === "function") {
|
|
1715
|
-
const disposer = themeParams.isDark.sub(() => {
|
|
1716
|
-
const appearance = themeParams.isDark() ? "dark" : "light";
|
|
1717
|
-
this.environment.appearance = appearance;
|
|
1718
|
-
this.notifyAppearance(appearance);
|
|
1719
|
-
});
|
|
1720
|
-
this.appearanceWatcherDispose = this.registerDisposable(disposer);
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
notifyAppearance(appearance) {
|
|
1724
|
-
for (const listener of this.appearanceListeners) {
|
|
1725
|
-
listener(appearance);
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
setupActiveWatcher() {
|
|
1729
|
-
this.activeWatcherDispose?.();
|
|
1730
|
-
const activeSignal = miniApp.isActive;
|
|
1731
|
-
const invoke = () => {
|
|
1732
|
-
try {
|
|
1733
|
-
const isActive = miniApp.isActive();
|
|
1734
|
-
if (isActive) {
|
|
1735
|
-
this.notifyViewRestore();
|
|
1736
|
-
} else {
|
|
1737
|
-
this.notifyViewHide();
|
|
1738
|
-
}
|
|
1739
|
-
} catch (error) {
|
|
1740
|
-
console.warn("[tvm-app-adapter] miniApp.isActive() failed:", error);
|
|
1741
|
-
}
|
|
1742
|
-
};
|
|
1743
|
-
if (typeof activeSignal?.sub === "function") {
|
|
1744
|
-
const disposer = activeSignal.sub(() => invoke());
|
|
1745
|
-
this.activeWatcherDispose = this.registerDisposable(disposer);
|
|
1746
|
-
invoke();
|
|
1747
|
-
return;
|
|
1748
|
-
}
|
|
1749
|
-
try {
|
|
1750
|
-
invoke();
|
|
1751
|
-
} catch {
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
async prepareViewport() {
|
|
1755
|
-
try {
|
|
1756
|
-
await bindViewportCssVars({
|
|
1757
|
-
...this.getViewportMountOptions(),
|
|
1758
|
-
bindCssVars: typeof viewport.bindCssVars === "function" ? viewport.bindCssVars : void 0
|
|
1759
|
-
});
|
|
1760
|
-
} catch (error) {
|
|
1761
|
-
console.warn("[tvm-app-adapter] prepareViewport failed:", error);
|
|
1762
|
-
}
|
|
1763
|
-
}
|
|
1764
|
-
async requestFullscreenInternal() {
|
|
1765
|
-
try {
|
|
1766
|
-
const viewportOptions = this.getViewportMountOptions();
|
|
1767
|
-
await ensureViewportMounted(viewportOptions);
|
|
1768
|
-
const { sdkViewport } = viewportOptions;
|
|
1769
|
-
const canUseRaw = typeof sdkViewport.isSupported === "function" ? sdkViewport.isSupported() : false;
|
|
1770
|
-
if (canUseRaw && typeof sdkViewport.requestFullscreen === "function") {
|
|
1771
|
-
await sdkViewport.requestFullscreen();
|
|
1772
|
-
} else if (viewport.requestFullscreen && viewport.requestFullscreen.isAvailable?.()) {
|
|
1773
|
-
await viewport.requestFullscreen();
|
|
1774
|
-
} else {
|
|
1775
|
-
postEvent("web_app_request_fullscreen");
|
|
1776
|
-
}
|
|
1777
|
-
this.disableVerticalSwipes();
|
|
1778
|
-
} catch (error) {
|
|
1779
|
-
console.warn("[tvm-app-adapter] Telegram requestFullscreen failed:", error);
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
getViewportMountOptions() {
|
|
1783
|
-
const sdkViewport = rawViewport;
|
|
1784
|
-
return {
|
|
1785
|
-
sdkViewport,
|
|
1786
|
-
fallbackMount: async () => {
|
|
1787
|
-
if (viewport.mount?.isAvailable?.()) {
|
|
1788
|
-
await viewport.mount();
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
};
|
|
1792
|
-
}
|
|
1793
|
-
safeHeightFromSdk() {
|
|
1794
|
-
try {
|
|
1795
|
-
if (typeof rawViewport.height === "function") {
|
|
1796
|
-
return rawViewport.height();
|
|
1797
|
-
}
|
|
1798
|
-
} catch {
|
|
1799
|
-
return void 0;
|
|
1800
|
-
}
|
|
1801
|
-
return void 0;
|
|
1802
|
-
}
|
|
1803
|
-
stableHeightFromSdk() {
|
|
1804
|
-
try {
|
|
1805
|
-
if (typeof rawViewport.stableHeight === "function") {
|
|
1806
|
-
return rawViewport.stableHeight();
|
|
1807
|
-
}
|
|
1808
|
-
} catch {
|
|
1809
|
-
return void 0;
|
|
1810
|
-
}
|
|
1811
|
-
return void 0;
|
|
1812
|
-
}
|
|
1813
|
-
notifyViewHide() {
|
|
1814
|
-
for (const listener of this.viewHideListeners) {
|
|
1815
|
-
try {
|
|
1816
|
-
listener();
|
|
1817
|
-
} catch (error) {
|
|
1818
|
-
console.warn("[tvm-app-adapter] onViewHide listener failed:", error);
|
|
1819
|
-
}
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
normalizeDecodedStartParam(startParam) {
|
|
1823
|
-
let decoded;
|
|
1824
|
-
try {
|
|
1825
|
-
decoded = decodeStartParam(startParam);
|
|
1826
|
-
} catch {
|
|
1827
|
-
decoded = startParam;
|
|
1828
|
-
}
|
|
1829
|
-
if (decoded && typeof decoded === "object" && !Array.isArray(decoded)) {
|
|
1830
|
-
return { ...decoded };
|
|
1831
|
-
}
|
|
1832
|
-
if (typeof decoded === "string" && decoded) {
|
|
1833
|
-
const parsed = this.parseQueryString(decoded);
|
|
1834
|
-
if (Object.keys(parsed).length) {
|
|
1835
|
-
return parsed;
|
|
1836
|
-
}
|
|
1837
|
-
return { startParam: decoded };
|
|
1838
|
-
}
|
|
1839
|
-
return {};
|
|
1840
|
-
}
|
|
1841
|
-
parseQueryString(value) {
|
|
1842
|
-
const normalized = value.startsWith("?") ? value.slice(1) : value;
|
|
1843
|
-
const params = new URLSearchParams(normalized);
|
|
1844
|
-
const result = {};
|
|
1845
|
-
const keys = /* @__PURE__ */ new Set();
|
|
1846
|
-
for (const [key] of params.entries()) {
|
|
1847
|
-
keys.add(key);
|
|
1848
|
-
}
|
|
1849
|
-
for (const key of keys) {
|
|
1850
|
-
const values = params.getAll(key);
|
|
1851
|
-
if (!values.length) {
|
|
1852
|
-
continue;
|
|
1853
|
-
}
|
|
1854
|
-
result[key] = values.length === 1 ? values[0] : values;
|
|
1855
|
-
}
|
|
1856
|
-
return result;
|
|
1857
|
-
}
|
|
1858
|
-
notifyViewRestore() {
|
|
1859
|
-
for (const listener of this.viewRestoreListeners) {
|
|
1860
|
-
try {
|
|
1861
|
-
listener();
|
|
1862
|
-
} catch (error) {
|
|
1863
|
-
console.warn("[tvm-app-adapter] onViewRestore listener failed:", error);
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
onDestroy() {
|
|
1868
|
-
this.appearanceWatcherDispose?.();
|
|
1869
|
-
this.appearanceWatcherDispose = void 0;
|
|
1870
|
-
this.activeWatcherDispose?.();
|
|
1871
|
-
this.activeWatcherDispose = void 0;
|
|
1872
|
-
this.appearanceListeners.clear();
|
|
1873
|
-
this.viewHideListeners.clear();
|
|
1874
|
-
this.viewRestoreListeners.clear();
|
|
1875
|
-
this.backHandlers.clear();
|
|
1876
|
-
super.onDestroy();
|
|
1877
|
-
}
|
|
1878
|
-
};
|
|
1879
|
-
|
|
1880
|
-
// src/adapters/vkAdapter.ts
|
|
1881
|
-
import bridge, {
|
|
1882
|
-
parseURLSearchParamsForGetLaunchParams
|
|
1883
|
-
} from "@vkontakte/vk-bridge";
|
|
1884
|
-
|
|
1885
|
-
// src/config/vkAnalytics.ts
|
|
1886
|
-
var pixelCode = null;
|
|
1887
|
-
function setVkPixelCode(next) {
|
|
1888
|
-
if (typeof next !== "string") {
|
|
1889
|
-
pixelCode = null;
|
|
1890
|
-
return;
|
|
1891
|
-
}
|
|
1892
|
-
const normalized = next.trim();
|
|
1893
|
-
pixelCode = normalized || null;
|
|
1894
|
-
}
|
|
1895
|
-
function getVkPixelCode() {
|
|
1896
|
-
return pixelCode;
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
// src/lib/bridge.ts
|
|
1900
|
-
async function isBridgeMethodSupported(method, supportsAsync) {
|
|
1901
|
-
if (typeof supportsAsync !== "function") {
|
|
1902
|
-
return false;
|
|
1903
|
-
}
|
|
1904
|
-
try {
|
|
1905
|
-
return await supportsAsync(method);
|
|
1906
|
-
} catch (error) {
|
|
1907
|
-
console.warn("[tvm-app-adapter] bridge.supportsAsync failed:", error);
|
|
1908
|
-
return false;
|
|
1909
|
-
}
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
// src/adapters/vkAdapter.ts
|
|
1913
|
-
var ANALYTICS_EVENT_NAME_PATTERN = /^[a-z0-9][a-z0-9_.:-]{0,63}$/i;
|
|
1914
|
-
var ANALYTICS_FALLBACK_EVENT = "VK_ANALYTICS_EVENT";
|
|
1915
|
-
var VKMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
1916
|
-
constructor() {
|
|
1917
|
-
super("vk");
|
|
1918
|
-
__publicField(this, "configSafeArea");
|
|
1919
|
-
__publicField(this, "stopViewportTracking");
|
|
1920
|
-
__publicField(this, "supportsAsync", typeof bridge.supportsAsync === "function" ? bridge.supportsAsync.bind(bridge) : void 0);
|
|
1921
|
-
__publicField(this, "pixelCodeWarningShown", false);
|
|
1922
|
-
__publicField(this, "unsubscribe");
|
|
1923
|
-
__publicField(this, "launchParams");
|
|
1924
|
-
__publicField(this, "queryParams");
|
|
1925
|
-
__publicField(this, "viewHideListeners", /* @__PURE__ */ new Set());
|
|
1926
|
-
__publicField(this, "viewRestoreListeners", /* @__PURE__ */ new Set());
|
|
1927
|
-
}
|
|
1928
|
-
computeSafeArea() {
|
|
1929
|
-
const baseSafeArea = this.computeBaseSafeArea();
|
|
1930
|
-
const overlayInsets = this.resolveOverlayInsets();
|
|
1931
|
-
if (overlayInsets) {
|
|
1932
|
-
return computeCombinedSafeArea({
|
|
1933
|
-
environment: baseSafeArea,
|
|
1934
|
-
minimum: overlayInsets
|
|
1935
|
-
});
|
|
1936
|
-
}
|
|
1937
|
-
return baseSafeArea;
|
|
1938
|
-
}
|
|
1939
|
-
async init(_options) {
|
|
1940
|
-
if (this.ready) {
|
|
1941
|
-
return;
|
|
1942
|
-
}
|
|
1943
|
-
const handler = (event) => this.handleBridgeEvent(event);
|
|
1944
|
-
bridge.subscribe(handler);
|
|
1945
|
-
this.unsubscribe = this.registerDisposable(() => bridge.unsubscribe(handler));
|
|
1946
|
-
let initialConfig;
|
|
1947
|
-
try {
|
|
1948
|
-
initialConfig = await bridge.send("VKWebAppGetConfig");
|
|
1949
|
-
} catch (error) {
|
|
1950
|
-
console.warn("[tvm-app-adapter] VKWebAppGetConfig failed:", error);
|
|
1951
|
-
}
|
|
1952
|
-
try {
|
|
1953
|
-
const initResult = await bridge.send("VKWebAppInit");
|
|
1954
|
-
if (initResult && "result" in initResult && initResult.result === false) {
|
|
1955
|
-
console.warn("[tvm-app-adapter] VKWebAppInit returned result=false.");
|
|
1956
|
-
}
|
|
1957
|
-
} catch (error) {
|
|
1958
|
-
console.error("[tvm-app-adapter] VKWebAppInit failed:", error);
|
|
1959
|
-
this.unsubscribe?.();
|
|
1960
|
-
this.unsubscribe = void 0;
|
|
1961
|
-
throw error;
|
|
1962
|
-
}
|
|
1963
|
-
let launchParams;
|
|
1964
|
-
try {
|
|
1965
|
-
launchParams = await bridge.send("VKWebAppGetLaunchParams");
|
|
1966
|
-
} catch (error) {
|
|
1967
|
-
console.error("[tvm-app-adapter] VKWebAppGetLaunchParams failed:", error);
|
|
1968
|
-
this.unsubscribe?.();
|
|
1969
|
-
this.unsubscribe = void 0;
|
|
1970
|
-
throw error;
|
|
1971
|
-
}
|
|
1972
|
-
const search = typeof window !== "undefined" ? window.location.search : "";
|
|
1973
|
-
const queryParams = parseURLSearchParamsForGetLaunchParams(search);
|
|
1974
|
-
this.launchParams = launchParams;
|
|
1975
|
-
this.queryParams = queryParams;
|
|
1976
|
-
this.environment = this.composeEnvironment(launchParams, queryParams, initialConfig);
|
|
1977
|
-
this.configSafeArea = this.environment.safeArea;
|
|
1978
|
-
const combinedSafeArea = this.computeSafeArea();
|
|
1979
|
-
this.environment.safeArea = combinedSafeArea;
|
|
1980
|
-
this.applyAppearance(this.environment.appearance, initialConfig?.scheme);
|
|
1981
|
-
this.notifyEnvironmentChanged();
|
|
1982
|
-
this.ready = true;
|
|
1983
|
-
this.startViewportTracking();
|
|
1984
|
-
}
|
|
1985
|
-
async vibrateImpact(style) {
|
|
1986
|
-
if (await this.supportsBridgeMethod("VKWebAppTapticImpactOccurred")) {
|
|
1987
|
-
bridge.send("VKWebAppTapticImpactOccurred", { style });
|
|
1988
|
-
}
|
|
1989
|
-
}
|
|
1990
|
-
async vibrateNotification(type) {
|
|
1991
|
-
if (await this.supportsBridgeMethod("VKWebAppTapticNotificationOccurred")) {
|
|
1992
|
-
bridge.send("VKWebAppTapticNotificationOccurred", { type });
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
async vibrateSelection() {
|
|
1996
|
-
if (await this.supportsBridgeMethod("VKWebAppTapticSelectionChanged")) {
|
|
1997
|
-
bridge.send("VKWebAppTapticSelectionChanged");
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
async setColors(colors) {
|
|
2001
|
-
const { header, background } = colors;
|
|
2002
|
-
if (header || background) {
|
|
2003
|
-
const canApplyViewSettings = await this.supportsBridgeMethod("VKWebAppSetViewSettings");
|
|
2004
|
-
if (canApplyViewSettings) {
|
|
2005
|
-
const statusBarStyle = header ? this.resolveStatusBarStyle(header) : this.environment.appearance?.includes("dark") ? "light" : "dark";
|
|
2006
|
-
await bridge.send("VKWebAppSetViewSettings", {
|
|
2007
|
-
status_bar_style: statusBarStyle,
|
|
2008
|
-
...header ? { action_bar_color: header } : {},
|
|
2009
|
-
...background ? { navigation_bar_color: background } : {}
|
|
2010
|
-
});
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
await super.setColors(colors);
|
|
2014
|
-
}
|
|
2015
|
-
getEnvironment() {
|
|
2016
|
-
return {
|
|
2017
|
-
...this.environment,
|
|
2018
|
-
isWebView: bridge.isWebView()
|
|
2019
|
-
};
|
|
2020
|
-
}
|
|
2021
|
-
getLaunchParams() {
|
|
2022
|
-
if (!this.launchParams) {
|
|
2023
|
-
return {
|
|
2024
|
-
customLaunchParams: this.readCustomUrlParams((key) => {
|
|
2025
|
-
const normalized = key.toLowerCase();
|
|
2026
|
-
return normalized.startsWith("vk_") || normalized === "sign";
|
|
2027
|
-
})
|
|
2028
|
-
};
|
|
2029
|
-
}
|
|
2030
|
-
return {
|
|
2031
|
-
launchParams: this.launchParams,
|
|
2032
|
-
customLaunchParams: this.readCustomUrlParams((key) => {
|
|
2033
|
-
const normalized = key.toLowerCase();
|
|
2034
|
-
return normalized.startsWith("vk_") || normalized === "sign";
|
|
2035
|
-
})
|
|
2036
|
-
};
|
|
2037
|
-
}
|
|
2038
|
-
async openExternalLink(url) {
|
|
2039
|
-
const a = document.createElement("a");
|
|
2040
|
-
a.href = url;
|
|
2041
|
-
a.target = "_blank";
|
|
2042
|
-
a.rel = "noopener noreferrer";
|
|
2043
|
-
a.style.display = "none";
|
|
2044
|
-
document.body.appendChild(a);
|
|
2045
|
-
a.click();
|
|
2046
|
-
a.remove();
|
|
2047
|
-
}
|
|
2048
|
-
async supports(capability) {
|
|
2049
|
-
switch (capability) {
|
|
2050
|
-
case "haptics": {
|
|
2051
|
-
const [impact, notification, selection] = await Promise.all([
|
|
2052
|
-
this.supportsBridgeMethod("VKWebAppTapticImpactOccurred"),
|
|
2053
|
-
this.supportsBridgeMethod("VKWebAppTapticNotificationOccurred"),
|
|
2054
|
-
this.supportsBridgeMethod("VKWebAppTapticSelectionChanged")
|
|
2055
|
-
]);
|
|
2056
|
-
return impact || notification || selection;
|
|
2057
|
-
}
|
|
2058
|
-
case "qrScanner":
|
|
2059
|
-
return this.supportsBridgeMethod("VKWebAppOpenCodeReader");
|
|
2060
|
-
case "requestPhone": {
|
|
2061
|
-
const [supportsPhoneNumber, supportsPersonalCard] = await Promise.all([
|
|
2062
|
-
this.supportsBridgeMethod("VKWebAppGetPhoneNumber"),
|
|
2063
|
-
this.supportsBridgeMethod("VKWebAppGetPersonalCard")
|
|
2064
|
-
]);
|
|
2065
|
-
return supportsPhoneNumber || supportsPersonalCard;
|
|
2066
|
-
}
|
|
2067
|
-
case "notifications":
|
|
2068
|
-
return this.supportsBridgeMethod("VKWebAppAllowNotifications");
|
|
2069
|
-
case "shareUrl":
|
|
2070
|
-
return this.supportsBridgeMethod("VKWebAppShare");
|
|
2071
|
-
case "shareStory":
|
|
2072
|
-
return this.supportsBridgeMethod("VKWebAppShowStoryBox");
|
|
2073
|
-
case "downloadFile":
|
|
2074
|
-
return this.supportsBridgeMethod("VKWebAppDownloadFile");
|
|
2075
|
-
case "addToHomeScreen":
|
|
2076
|
-
return this.supportsBridgeMethod("VKWebAppAddToHomeScreen");
|
|
2077
|
-
case "denyNotifications":
|
|
2078
|
-
return this.supportsBridgeMethod("VKWebAppDenyNotifications");
|
|
2079
|
-
case "openExternalLink":
|
|
2080
|
-
return true;
|
|
2081
|
-
case "viewVisibility":
|
|
2082
|
-
return true;
|
|
2083
|
-
default:
|
|
2084
|
-
return await super.supports(capability);
|
|
2085
|
-
}
|
|
2086
|
-
}
|
|
2087
|
-
async requestPhone() {
|
|
2088
|
-
const [supportsPhoneNumber, supportsPersonalCard] = await Promise.all([
|
|
2089
|
-
this.supportsBridgeMethod("VKWebAppGetPhoneNumber"),
|
|
2090
|
-
this.supportsBridgeMethod("VKWebAppGetPersonalCard")
|
|
2091
|
-
]);
|
|
2092
|
-
if (!supportsPhoneNumber && !supportsPersonalCard) {
|
|
2093
|
-
return super.requestPhone();
|
|
2094
|
-
}
|
|
2095
|
-
try {
|
|
2096
|
-
if (supportsPhoneNumber) {
|
|
2097
|
-
const result = await bridge.send("VKWebAppGetPhoneNumber");
|
|
2098
|
-
const phoneNumber = result.phone_number;
|
|
2099
|
-
return typeof phoneNumber === "string" && phoneNumber ? phoneNumber : null;
|
|
2100
|
-
}
|
|
2101
|
-
const card = await bridge.send("VKWebAppGetPersonalCard", { type: ["phone"] });
|
|
2102
|
-
const phone = card.phone;
|
|
2103
|
-
return typeof phone === "string" && phone ? phone : null;
|
|
2104
|
-
} catch (error) {
|
|
2105
|
-
console.warn("[tvm-app-adapter] VK requestPhone failed:", error);
|
|
2106
|
-
return null;
|
|
2107
|
-
}
|
|
2108
|
-
}
|
|
2109
|
-
async requestNotificationsPermission() {
|
|
2110
|
-
const supported = await this.supportsBridgeMethod("VKWebAppAllowNotifications");
|
|
2111
|
-
if (!supported) {
|
|
2112
|
-
return super.requestNotificationsPermission();
|
|
2113
|
-
}
|
|
2114
|
-
try {
|
|
2115
|
-
const response = await bridge.send("VKWebAppAllowNotifications");
|
|
2116
|
-
if (response && typeof response === "object" && "result" in response) {
|
|
2117
|
-
return Boolean(response.result);
|
|
2118
|
-
}
|
|
2119
|
-
return true;
|
|
2120
|
-
} catch (error) {
|
|
2121
|
-
console.warn("[tvm-app-adapter] VK allow notifications failed:", error);
|
|
2122
|
-
return false;
|
|
2123
|
-
}
|
|
2124
|
-
}
|
|
2125
|
-
async addToHomeScreen() {
|
|
2126
|
-
const supported = await this.supportsBridgeMethod("VKWebAppAddToHomeScreen");
|
|
2127
|
-
if (!supported) {
|
|
2128
|
-
console.warn("[tvm-app-adapter] VK addToHomeScreen not supported");
|
|
2129
|
-
return super.addToHomeScreen();
|
|
2130
|
-
}
|
|
2131
|
-
try {
|
|
2132
|
-
const response = await bridge.send("VKWebAppAddToHomeScreen");
|
|
2133
|
-
if (response && typeof response === "object" && "result" in response) {
|
|
2134
|
-
return Boolean(response.result);
|
|
2135
|
-
}
|
|
2136
|
-
return true;
|
|
2137
|
-
} catch (error) {
|
|
2138
|
-
console.warn("[tvm-app-adapter] VK addToHomeScreen failed:", error);
|
|
2139
|
-
return false;
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
async denyNotifications() {
|
|
2143
|
-
const supported = await this.supportsBridgeMethod("VKWebAppDenyNotifications");
|
|
2144
|
-
if (!supported) {
|
|
2145
|
-
return super.denyNotifications();
|
|
2146
|
-
}
|
|
2147
|
-
try {
|
|
2148
|
-
const response = await bridge.send("VKWebAppDenyNotifications");
|
|
2149
|
-
if (response && typeof response === "object" && "result" in response) {
|
|
2150
|
-
return Boolean(response.result);
|
|
2151
|
-
}
|
|
2152
|
-
return true;
|
|
2153
|
-
} catch (error) {
|
|
2154
|
-
console.warn("[tvm-app-adapter] VK deny notifications failed:", error);
|
|
2155
|
-
return false;
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
async scanQRCode(options) {
|
|
2159
|
-
const supportsQrScanner = await this.supportsBridgeMethod("VKWebAppOpenCodeReader");
|
|
2160
|
-
if (!supportsQrScanner) {
|
|
2161
|
-
return super.scanQRCode(options);
|
|
2162
|
-
}
|
|
2163
|
-
let result = null;
|
|
2164
|
-
try {
|
|
2165
|
-
const data = await bridge.send("VKWebAppOpenCodeReader");
|
|
2166
|
-
if (data.code_data) {
|
|
2167
|
-
result = data.code_data;
|
|
2168
|
-
}
|
|
2169
|
-
} catch (error) {
|
|
2170
|
-
console.log(error);
|
|
2171
|
-
}
|
|
2172
|
-
return result;
|
|
2173
|
-
}
|
|
2174
|
-
onViewHide(callback) {
|
|
2175
|
-
this.viewHideListeners.add(callback);
|
|
2176
|
-
return () => {
|
|
2177
|
-
this.viewHideListeners.delete(callback);
|
|
2178
|
-
};
|
|
2179
|
-
}
|
|
2180
|
-
onViewRestore(callback) {
|
|
2181
|
-
this.viewRestoreListeners.add(callback);
|
|
2182
|
-
return () => {
|
|
2183
|
-
this.viewRestoreListeners.delete(callback);
|
|
2184
|
-
};
|
|
2185
|
-
}
|
|
2186
|
-
async shareStory(mediaUrl, _options) {
|
|
2187
|
-
const options = _options;
|
|
2188
|
-
const vkOptions = options?.vk;
|
|
2189
|
-
const fallbackAttachment = options?.link ? {
|
|
2190
|
-
type: "url",
|
|
2191
|
-
text: "open",
|
|
2192
|
-
url: options.link.url
|
|
2193
|
-
} : void 0;
|
|
2194
|
-
const fallbackStickers = options?.text ? [{
|
|
2195
|
-
sticker_type: "native",
|
|
2196
|
-
sticker: {
|
|
2197
|
-
action_type: "text",
|
|
2198
|
-
action: {
|
|
2199
|
-
text: options.text,
|
|
2200
|
-
style: "classic",
|
|
2201
|
-
background_style: "none"
|
|
2202
|
-
},
|
|
2203
|
-
transform: {
|
|
2204
|
-
gravity: "center_bottom",
|
|
2205
|
-
translation_y: -0.2
|
|
2206
|
-
}
|
|
2207
|
-
}
|
|
2208
|
-
}] : void 0;
|
|
2209
|
-
const bridgeOptions = {
|
|
2210
|
-
background_type: vkOptions?.backgroundType ?? "image",
|
|
2211
|
-
url: mediaUrl,
|
|
2212
|
-
locked: vkOptions?.locked ?? true,
|
|
2213
|
-
...vkOptions?.attachment ?? fallbackAttachment ? { attachment: vkOptions?.attachment ?? fallbackAttachment } : {},
|
|
2214
|
-
...vkOptions?.stickers ?? fallbackStickers ? { stickers: vkOptions?.stickers ?? fallbackStickers } : {}
|
|
2215
|
-
};
|
|
2216
|
-
await bridge.send("VKWebAppShowStoryBox", bridgeOptions);
|
|
2217
|
-
}
|
|
2218
|
-
shareUrl(url, text) {
|
|
2219
|
-
void this.shareUrlInternal(url, text);
|
|
2220
|
-
}
|
|
2221
|
-
async shareUrlInternal(url, text) {
|
|
2222
|
-
const supported = await this.supportsBridgeMethod("VKWebAppShare");
|
|
2223
|
-
if (!supported) {
|
|
2224
|
-
super.shareUrl(url, text ?? "");
|
|
2225
|
-
return;
|
|
2226
|
-
}
|
|
2227
|
-
try {
|
|
2228
|
-
await bridge.send("VKWebAppShare", {
|
|
2229
|
-
link: url,
|
|
2230
|
-
...text ? { text } : {}
|
|
2231
|
-
});
|
|
2232
|
-
} catch (error) {
|
|
2233
|
-
console.warn("[tvm-app-adapter] VK shareUrl failed:", error);
|
|
2234
|
-
super.shareUrl(url, text ?? "");
|
|
2235
|
-
}
|
|
2236
|
-
}
|
|
2237
|
-
async downloadFile(url, filename) {
|
|
2238
|
-
const supported = await this.supportsBridgeMethod("VKWebAppDownloadFile");
|
|
2239
|
-
if (!supported) {
|
|
2240
|
-
await super.downloadFile(url, filename);
|
|
2241
|
-
return;
|
|
2242
|
-
}
|
|
2243
|
-
try {
|
|
2244
|
-
const response = await bridge.send("VKWebAppDownloadFile", { url, filename });
|
|
2245
|
-
const result = response?.result;
|
|
2246
|
-
if (result === false) {
|
|
2247
|
-
throw new Error("VKWebAppDownloadFile returned result=false");
|
|
2248
|
-
}
|
|
2249
|
-
} catch (error) {
|
|
2250
|
-
console.warn("[tvm-app-adapter] VK downloadFile failed:", error);
|
|
2251
|
-
await super.downloadFile(url, filename);
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
trackConversionEvent(event, payload) {
|
|
2255
|
-
const normalizedEvent = this.normalizeAnalyticsEventName(event);
|
|
2256
|
-
if (!normalizedEvent) {
|
|
2257
|
-
return;
|
|
2258
|
-
}
|
|
2259
|
-
const envelope = {
|
|
2260
|
-
method: "VKWebAppConversionHit",
|
|
2261
|
-
params: {
|
|
2262
|
-
event: normalizedEvent,
|
|
2263
|
-
params: this.normalizeAnalyticsPayload(payload)
|
|
2264
|
-
}
|
|
2265
|
-
};
|
|
2266
|
-
this.dispatchAnalytics(envelope);
|
|
2267
|
-
}
|
|
2268
|
-
trackPixelEvent(event, payload) {
|
|
2269
|
-
const pixelCode2 = getVkPixelCode();
|
|
2270
|
-
if (!pixelCode2) {
|
|
2271
|
-
if (!this.pixelCodeWarningShown) {
|
|
2272
|
-
console.warn("[VKAnalytics] VK pixel code is not configured. Call configureVkPixel() before tracking.");
|
|
2273
|
-
this.pixelCodeWarningShown = true;
|
|
2274
|
-
}
|
|
2275
|
-
return;
|
|
2276
|
-
}
|
|
2277
|
-
const normalizedEvent = this.normalizeAnalyticsEventName(event);
|
|
2278
|
-
if (!normalizedEvent) {
|
|
2279
|
-
return;
|
|
2280
|
-
}
|
|
2281
|
-
const envelope = {
|
|
2282
|
-
method: "VKWebAppRetargetingPixel",
|
|
2283
|
-
params: {
|
|
2284
|
-
pixel_code: pixelCode2,
|
|
2285
|
-
type: normalizedEvent,
|
|
2286
|
-
data: this.normalizeAnalyticsPayload(payload)
|
|
2287
|
-
}
|
|
2288
|
-
};
|
|
2289
|
-
this.pixelCodeWarningShown = false;
|
|
2290
|
-
this.dispatchAnalytics(envelope);
|
|
2291
|
-
}
|
|
2292
|
-
dispatchAnalytics(envelope) {
|
|
2293
|
-
if (typeof bridge.isWebView === "function") {
|
|
2294
|
-
try {
|
|
2295
|
-
if (!bridge.isWebView()) {
|
|
2296
|
-
this.emitAnalyticsFallback(envelope);
|
|
2297
|
-
return;
|
|
2298
|
-
}
|
|
2299
|
-
} catch (error) {
|
|
2300
|
-
console.warn("[VKAnalytics] bridge.isWebView check failed:", error);
|
|
2301
|
-
this.emitAnalyticsFallback(envelope);
|
|
2302
|
-
return;
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
void this.safeBridgeSend(envelope.method, envelope.params);
|
|
2306
|
-
}
|
|
2307
|
-
emitAnalyticsFallback(envelope) {
|
|
2308
|
-
if (typeof window === "undefined") {
|
|
2309
|
-
return;
|
|
2310
|
-
}
|
|
2311
|
-
const detail = {
|
|
2312
|
-
...envelope,
|
|
2313
|
-
timestamp: Date.now()
|
|
2314
|
-
};
|
|
2315
|
-
if (typeof window.dispatchEvent === "function" && typeof window.CustomEvent === "function") {
|
|
2316
|
-
window.dispatchEvent(new CustomEvent(ANALYTICS_FALLBACK_EVENT, { detail }));
|
|
2317
|
-
return;
|
|
2318
|
-
}
|
|
2319
|
-
try {
|
|
2320
|
-
window.postMessage({ type: ANALYTICS_FALLBACK_EVENT, detail }, "*");
|
|
2321
|
-
} catch (error) {
|
|
2322
|
-
console.warn("[VKAnalytics] fallback dispatch failed", error);
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
normalizeAnalyticsEventName(event) {
|
|
2326
|
-
if (typeof event !== "string") {
|
|
2327
|
-
return null;
|
|
2328
|
-
}
|
|
2329
|
-
const trimmed = event.trim();
|
|
2330
|
-
if (!trimmed || !ANALYTICS_EVENT_NAME_PATTERN.test(trimmed)) {
|
|
2331
|
-
console.warn(`[VKAnalytics] Invalid event name: "${event}"`);
|
|
2332
|
-
return null;
|
|
2333
|
-
}
|
|
2334
|
-
return trimmed;
|
|
2335
|
-
}
|
|
2336
|
-
normalizeAnalyticsPayload(payload) {
|
|
2337
|
-
if (!this.isPlainObject(payload)) {
|
|
2338
|
-
return {};
|
|
2339
|
-
}
|
|
2340
|
-
return { ...payload };
|
|
2341
|
-
}
|
|
2342
|
-
isPlainObject(value) {
|
|
2343
|
-
if (value === null || typeof value !== "object") {
|
|
2344
|
-
return false;
|
|
2345
|
-
}
|
|
2346
|
-
if (Array.isArray(value)) {
|
|
2347
|
-
return false;
|
|
2348
|
-
}
|
|
2349
|
-
const proto = Object.getPrototypeOf(value);
|
|
2350
|
-
return proto === Object.prototype || proto === null;
|
|
2351
|
-
}
|
|
2352
|
-
async safeBridgeSend(method, params) {
|
|
2353
|
-
try {
|
|
2354
|
-
const supported = await this.supportsBridgeMethod(method);
|
|
2355
|
-
if (!supported) {
|
|
2356
|
-
return;
|
|
2357
|
-
}
|
|
2358
|
-
await bridge.send(method, params);
|
|
2359
|
-
} catch (error) {
|
|
2360
|
-
console.warn(`[VKAnalytics] ${method} failed`, error);
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
|
-
composeEnvironment(launchParams, queryParams, config) {
|
|
2364
|
-
const language = queryParams.vk_language ?? launchParams.vk_language;
|
|
2365
|
-
const platform = queryParams.vk_platform ?? launchParams.vk_platform;
|
|
2366
|
-
const appId = queryParams.vk_app_id ?? launchParams.vk_app_id;
|
|
2367
|
-
const prefersDark = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-color-scheme: dark)").matches : false;
|
|
2368
|
-
const appearance = this.normalizeAppearance(config?.appearance, config?.scheme) ?? (prefersDark ? "dark" : "light");
|
|
2369
|
-
const configSafeArea = this.extractSafeAreaFromConfig(config);
|
|
2370
|
-
return {
|
|
2371
|
-
platform: "vk",
|
|
2372
|
-
sdkVersion: platform ? String(platform) : void 0,
|
|
2373
|
-
appVersion: typeof appId === "number" ? `vk-app-${appId}` : void 0,
|
|
2374
|
-
languageCode: language ? String(language) : void 0,
|
|
2375
|
-
appearance,
|
|
2376
|
-
isWebView: bridge.isWebView(),
|
|
2377
|
-
safeArea: configSafeArea
|
|
2378
|
-
};
|
|
2379
|
-
}
|
|
2380
|
-
handleBridgeEvent(event) {
|
|
2381
|
-
const { type, data } = event.detail ?? {};
|
|
2382
|
-
if (type === "VKWebAppViewHide") {
|
|
2383
|
-
this.notifyVisibilityListeners(this.viewHideListeners);
|
|
2384
|
-
return;
|
|
2385
|
-
}
|
|
2386
|
-
if (type === "VKWebAppViewRestore") {
|
|
2387
|
-
this.notifyVisibilityListeners(this.viewRestoreListeners);
|
|
2388
|
-
return;
|
|
2389
|
-
}
|
|
2390
|
-
if (type === "VKWebAppUpdateConfig" && data) {
|
|
2391
|
-
const config = data;
|
|
2392
|
-
this.updateEnvironmentFromConfig(config);
|
|
2393
|
-
}
|
|
2394
|
-
}
|
|
2395
|
-
updateEnvironmentFromConfig(config) {
|
|
2396
|
-
if (!this.environment) {
|
|
2397
|
-
return;
|
|
2398
|
-
}
|
|
2399
|
-
const nextAppearance = this.normalizeAppearance(config.appearance, config.scheme);
|
|
2400
|
-
let changed = false;
|
|
2401
|
-
if (nextAppearance && nextAppearance !== this.environment.appearance) {
|
|
2402
|
-
this.environment.appearance = nextAppearance;
|
|
2403
|
-
changed = true;
|
|
2404
|
-
}
|
|
2405
|
-
const prevSafeArea = this.environment.safeArea;
|
|
2406
|
-
const nextSafeArea = this.extractSafeAreaFromConfig(config);
|
|
2407
|
-
if (nextSafeArea) {
|
|
2408
|
-
this.configSafeArea = nextSafeArea;
|
|
2409
|
-
this.environment.safeArea = nextSafeArea;
|
|
2410
|
-
} else {
|
|
2411
|
-
this.configSafeArea = void 0;
|
|
2412
|
-
this.environment.safeArea = void 0;
|
|
2413
|
-
}
|
|
2414
|
-
const combinedSafeArea = this.computeSafeArea() ?? {
|
|
2415
|
-
top: 0,
|
|
2416
|
-
right: 0,
|
|
2417
|
-
bottom: 0,
|
|
2418
|
-
left: 0
|
|
2419
|
-
};
|
|
2420
|
-
const prevForComparison = prevSafeArea;
|
|
2421
|
-
const safeAreaChanged = !prevForComparison || prevForComparison.top !== combinedSafeArea.top || prevForComparison.right !== combinedSafeArea.right || prevForComparison.bottom !== combinedSafeArea.bottom || prevForComparison.left !== combinedSafeArea.left;
|
|
2422
|
-
if (safeAreaChanged) {
|
|
2423
|
-
this.environment.safeArea = combinedSafeArea;
|
|
2424
|
-
changed = true;
|
|
2425
|
-
} else {
|
|
2426
|
-
this.environment.safeArea = prevSafeArea;
|
|
2427
|
-
}
|
|
2428
|
-
this.applyAppearance(this.environment.appearance, config.scheme);
|
|
2429
|
-
if (changed) {
|
|
2430
|
-
this.notifyEnvironmentChanged();
|
|
2431
|
-
}
|
|
2432
|
-
}
|
|
2433
|
-
applyAppearance(appearance, scheme) {
|
|
2434
|
-
if (typeof document === "undefined") {
|
|
2435
|
-
return;
|
|
2436
|
-
}
|
|
2437
|
-
if (appearance) {
|
|
2438
|
-
document.documentElement.dataset.vkAppearance = appearance;
|
|
2439
|
-
document.documentElement.classList.toggle("dark", appearance === "dark");
|
|
2440
|
-
}
|
|
2441
|
-
if (scheme) {
|
|
2442
|
-
document.documentElement.dataset.vkScheme = scheme;
|
|
2443
|
-
if (!appearance) {
|
|
2444
|
-
const normalized = this.normalizeAppearance(void 0, scheme);
|
|
2445
|
-
document.documentElement.classList.toggle("dark", normalized === "dark");
|
|
2446
|
-
}
|
|
2447
|
-
}
|
|
2448
|
-
}
|
|
2449
|
-
resolveStatusBarStyle(color) {
|
|
2450
|
-
const hex = color.replace("#", "");
|
|
2451
|
-
const normalized = hex.length === 3 ? hex.split("").map((symbol) => symbol + symbol).join("") : hex.slice(0, 6);
|
|
2452
|
-
const r = parseInt(normalized.slice(0, 2), 16) / 255;
|
|
2453
|
-
const g = parseInt(normalized.slice(2, 4), 16) / 255;
|
|
2454
|
-
const b = parseInt(normalized.slice(4, 6), 16) / 255;
|
|
2455
|
-
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
2456
|
-
return luminance > 0.6 ? "dark" : "light";
|
|
2457
|
-
}
|
|
2458
|
-
normalizeAppearance(rawAppearance, scheme) {
|
|
2459
|
-
const normalized = rawAppearance?.toLowerCase();
|
|
2460
|
-
if (normalized === "dark" || normalized === "light") {
|
|
2461
|
-
return normalized;
|
|
2462
|
-
}
|
|
2463
|
-
const normalizedScheme = scheme?.toLowerCase();
|
|
2464
|
-
if (normalizedScheme) {
|
|
2465
|
-
if (normalizedScheme.includes("dark") || normalizedScheme.includes("space_gray")) {
|
|
2466
|
-
return "dark";
|
|
2467
|
-
}
|
|
2468
|
-
return "light";
|
|
2469
|
-
}
|
|
2470
|
-
return void 0;
|
|
2471
|
-
}
|
|
2472
|
-
extractSafeAreaFromConfig(config) {
|
|
2473
|
-
const rawInsets = config && "insets" in config ? config.insets : void 0;
|
|
2474
|
-
if (!rawInsets) {
|
|
2475
|
-
return void 0;
|
|
2476
|
-
}
|
|
2477
|
-
const { top = 0, right = 0, bottom = 0, left = 0 } = rawInsets;
|
|
2478
|
-
const values = [top, right, bottom, left].map((value) => typeof value === "number" ? value : Number(value) || 0);
|
|
2479
|
-
const hasInsets = values.some((value) => value !== 0);
|
|
2480
|
-
if (!hasInsets) {
|
|
2481
|
-
return void 0;
|
|
2482
|
-
}
|
|
2483
|
-
const [nTop, nRight, nBottom, nLeft] = values;
|
|
2484
|
-
return { top: nTop, right: nRight, bottom: nBottom, left: nLeft };
|
|
2485
|
-
}
|
|
2486
|
-
notifyVisibilityListeners(listeners) {
|
|
2487
|
-
for (const listener of listeners) {
|
|
2488
|
-
try {
|
|
2489
|
-
listener();
|
|
2490
|
-
} catch (error) {
|
|
2491
|
-
console.warn("[tvm-app-adapter] VK visibility listener failed:", error);
|
|
2492
|
-
}
|
|
2493
|
-
}
|
|
2494
|
-
}
|
|
2495
|
-
computeBaseSafeArea() {
|
|
2496
|
-
return computeCombinedSafeArea({
|
|
2497
|
-
environment: this.configSafeArea,
|
|
2498
|
-
viewport: this.getViewportInsets?.(),
|
|
2499
|
-
css: readCssSafeArea()
|
|
2500
|
-
});
|
|
2501
|
-
}
|
|
2502
|
-
startViewportTracking() {
|
|
2503
|
-
this.stopViewportTracking?.();
|
|
2504
|
-
const dispose = createSafeAreaWatcher({
|
|
2505
|
-
getSafeArea: () => this.computeSafeArea(),
|
|
2506
|
-
onChange: (next) => {
|
|
2507
|
-
this.environment.safeArea = next;
|
|
2508
|
-
this.notifyEnvironmentChanged();
|
|
2509
|
-
}
|
|
2510
|
-
});
|
|
2511
|
-
if (dispose) {
|
|
2512
|
-
this.stopViewportTracking = this.registerDisposable(dispose);
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
resolveOverlayInsets() {
|
|
2516
|
-
if (typeof window === "undefined") {
|
|
2517
|
-
return void 0;
|
|
2518
|
-
}
|
|
2519
|
-
if (!bridge.isWebView()) {
|
|
2520
|
-
return void 0;
|
|
2521
|
-
}
|
|
2522
|
-
if (!this.isLikelyMobilePlatform()) {
|
|
2523
|
-
return void 0;
|
|
2524
|
-
}
|
|
2525
|
-
const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || 0;
|
|
2526
|
-
if (!viewportWidth) {
|
|
2527
|
-
return void 0;
|
|
2528
|
-
}
|
|
2529
|
-
const overlayBreakpoint = 880;
|
|
2530
|
-
if (viewportWidth > overlayBreakpoint) {
|
|
2531
|
-
return void 0;
|
|
2532
|
-
}
|
|
2533
|
-
const orientationQuery = window.matchMedia?.("(orientation: landscape)");
|
|
2534
|
-
const isLandscape = Boolean(orientationQuery?.matches);
|
|
2535
|
-
const top = isLandscape ? 48 : 56;
|
|
2536
|
-
const right = isLandscape ? 72 : 88;
|
|
2537
|
-
return { top, right };
|
|
2538
|
-
}
|
|
2539
|
-
isLikelyMobilePlatform() {
|
|
2540
|
-
const platform = (this.resolveLaunchParam("vk_platform") ?? "").toLowerCase();
|
|
2541
|
-
const device = (this.resolveLaunchParam("vk_viewer_device") ?? "").toLowerCase();
|
|
2542
|
-
const isLayer = this.resolveLaunchParam("vk_is_layer") === "1";
|
|
2543
|
-
if (isLayer) {
|
|
2544
|
-
return false;
|
|
2545
|
-
}
|
|
2546
|
-
const mobilePattern = /(iphone|ipad|ios|android|mobile)/i;
|
|
2547
|
-
const desktopPattern = /(desktop|web|tablet)/i;
|
|
2548
|
-
const matchesMobilePlatform = mobilePattern.test(platform) || mobilePattern.test(device);
|
|
2549
|
-
const matchesDesktopPlatform = desktopPattern.test(platform) || desktopPattern.test(device);
|
|
2550
|
-
if (!matchesMobilePlatform) {
|
|
2551
|
-
return false;
|
|
2552
|
-
}
|
|
2553
|
-
return !matchesDesktopPlatform;
|
|
2554
|
-
}
|
|
2555
|
-
resolveLaunchParam(key) {
|
|
2556
|
-
const queryValue = this.queryParams?.[key];
|
|
2557
|
-
if (typeof queryValue === "string" && queryValue) {
|
|
2558
|
-
return queryValue;
|
|
2559
|
-
}
|
|
2560
|
-
const launchValue = this.launchParams ? this.launchParams[key] : void 0;
|
|
2561
|
-
if (typeof launchValue === "string" && launchValue) {
|
|
2562
|
-
return launchValue;
|
|
2563
|
-
}
|
|
2564
|
-
if (typeof launchValue === "number") {
|
|
2565
|
-
return String(launchValue);
|
|
2566
|
-
}
|
|
2567
|
-
if (typeof launchValue === "boolean") {
|
|
2568
|
-
return launchValue ? "1" : "0";
|
|
2569
|
-
}
|
|
2570
|
-
return void 0;
|
|
2571
|
-
}
|
|
2572
|
-
supportsBridgeMethod(method) {
|
|
2573
|
-
return isBridgeMethodSupported(method, this.supportsAsync);
|
|
2574
|
-
}
|
|
2575
|
-
onDestroy() {
|
|
2576
|
-
this.unsubscribe?.();
|
|
2577
|
-
this.unsubscribe = void 0;
|
|
2578
|
-
this.stopViewportTracking?.();
|
|
2579
|
-
this.stopViewportTracking = void 0;
|
|
2580
|
-
this.viewHideListeners.clear();
|
|
2581
|
-
this.viewRestoreListeners.clear();
|
|
2582
|
-
super.onDestroy();
|
|
2583
|
-
}
|
|
2584
|
-
};
|
|
2585
|
-
|
|
2586
|
-
// src/adapters/webAdapter.ts
|
|
2587
|
-
import jsQR from "jsqr";
|
|
2588
|
-
var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
2589
|
-
constructor() {
|
|
2590
|
-
super("web", {
|
|
2591
|
-
sdkVersion: navigator.userAgent,
|
|
2592
|
-
languageCode: navigator.language,
|
|
2593
|
-
isWebView: false
|
|
2594
|
-
});
|
|
2595
|
-
__publicField(this, "deferredPrompt", null);
|
|
2596
|
-
if (typeof window !== "undefined") {
|
|
2597
|
-
window.addEventListener("beforeinstallprompt", (event) => {
|
|
2598
|
-
event.preventDefault();
|
|
2599
|
-
this.deferredPrompt = event;
|
|
2600
|
-
});
|
|
2601
|
-
}
|
|
2602
|
-
}
|
|
2603
|
-
supports(capability) {
|
|
2604
|
-
switch (capability) {
|
|
2605
|
-
case "copyTextToClipboard":
|
|
2606
|
-
return typeof navigator !== "undefined" && Boolean(navigator.clipboard?.writeText);
|
|
2607
|
-
case "downloadFile":
|
|
2608
|
-
return typeof document !== "undefined";
|
|
2609
|
-
case "shareUrl":
|
|
2610
|
-
return typeof navigator !== "undefined" && (Boolean(navigator.share) || Boolean(navigator.clipboard?.writeText));
|
|
2611
|
-
case "addToHomeScreen":
|
|
2612
|
-
return /android/i.test(navigator.userAgent) && Boolean(this.deferredPrompt);
|
|
2613
|
-
case "checkHomeScreenStatus":
|
|
2614
|
-
return true;
|
|
2615
|
-
default:
|
|
2616
|
-
return super.supports(capability);
|
|
2617
|
-
}
|
|
2618
|
-
}
|
|
2619
|
-
async downloadFile(url, filename) {
|
|
2620
|
-
try {
|
|
2621
|
-
await triggerFileDownload(url, filename, { preferBlob: true });
|
|
2622
|
-
} catch (error) {
|
|
2623
|
-
console.warn("[mini-app-template] Web downloadFile fallback:", error);
|
|
2624
|
-
await super.downloadFile(url, filename);
|
|
2625
|
-
}
|
|
2626
|
-
}
|
|
2627
|
-
scanQRCode(_options) {
|
|
2628
|
-
return new Promise(async (resolve) => {
|
|
2629
|
-
if (typeof document === "undefined") {
|
|
2630
|
-
resolve(null);
|
|
2631
|
-
return;
|
|
2632
|
-
}
|
|
2633
|
-
const prevOverflow = document.body.style.overflow;
|
|
2634
|
-
const prevPos = document.body.style.position;
|
|
2635
|
-
const prevTouch = document.body.style.touchAction;
|
|
2636
|
-
const prevWidth = document.body.style.width;
|
|
2637
|
-
const prevHtmlOverflow = document.documentElement.style.overflow;
|
|
2638
|
-
document.body.style.overflow = "hidden";
|
|
2639
|
-
document.body.style.position = "fixed";
|
|
2640
|
-
document.body.style.width = "100%";
|
|
2641
|
-
document.body.style.touchAction = "none";
|
|
2642
|
-
document.documentElement.style.overflow = "hidden";
|
|
2643
|
-
const overlay = document.createElement("div");
|
|
2644
|
-
overlay.id = "qr-overlay";
|
|
2645
|
-
overlay.style.position = "fixed";
|
|
2646
|
-
overlay.style.top = "0";
|
|
2647
|
-
overlay.style.left = "0";
|
|
2648
|
-
overlay.style.right = "0";
|
|
2649
|
-
overlay.style.bottom = "0";
|
|
2650
|
-
overlay.style.zIndex = "999999999";
|
|
2651
|
-
overlay.style.background = "rgba(0,0,0,0.92)";
|
|
2652
|
-
overlay.style.width = "100%";
|
|
2653
|
-
overlay.style.height = "100%";
|
|
2654
|
-
overlay.style.display = "flex";
|
|
2655
|
-
overlay.style.flexDirection = "column";
|
|
2656
|
-
overlay.style.alignItems = "center";
|
|
2657
|
-
overlay.style.justifyContent = "center";
|
|
2658
|
-
overlay.style.overflow = "hidden";
|
|
2659
|
-
overlay.style.backdropFilter = "blur(3px)";
|
|
2660
|
-
document.body.appendChild(overlay);
|
|
2661
|
-
const closeBtn = document.createElement("button");
|
|
2662
|
-
closeBtn.innerText = "\u2715";
|
|
2663
|
-
closeBtn.style.position = "absolute";
|
|
2664
|
-
closeBtn.style.top = "22px";
|
|
2665
|
-
closeBtn.style.right = "22px";
|
|
2666
|
-
closeBtn.style.fontSize = "32px";
|
|
2667
|
-
closeBtn.style.color = "white";
|
|
2668
|
-
closeBtn.style.background = "transparent";
|
|
2669
|
-
closeBtn.style.border = "none";
|
|
2670
|
-
closeBtn.style.cursor = "pointer";
|
|
2671
|
-
closeBtn.style.zIndex = "9999999999";
|
|
2672
|
-
overlay.appendChild(closeBtn);
|
|
2673
|
-
const scanSize = Math.min(Math.floor(Math.min(window.innerWidth, window.innerHeight) * 0.72), 320);
|
|
2674
|
-
const scanBox = document.createElement("div");
|
|
2675
|
-
scanBox.style.width = `${scanSize}px`;
|
|
2676
|
-
scanBox.style.height = `${scanSize}px`;
|
|
2677
|
-
scanBox.style.position = "relative";
|
|
2678
|
-
scanBox.style.flex = "0 0 auto";
|
|
2679
|
-
scanBox.style.borderRadius = "18px";
|
|
2680
|
-
scanBox.style.overflow = "hidden";
|
|
2681
|
-
overlay.appendChild(scanBox);
|
|
2682
|
-
const scanArea = document.createElement("div");
|
|
2683
|
-
scanArea.style.position = "absolute";
|
|
2684
|
-
scanArea.style.inset = "0";
|
|
2685
|
-
scanArea.style.zIndex = "1";
|
|
2686
|
-
scanArea.style.background = "#000";
|
|
2687
|
-
scanBox.appendChild(scanArea);
|
|
2688
|
-
const video = document.createElement("video");
|
|
2689
|
-
video.setAttribute("playsinline", "true");
|
|
2690
|
-
video.autoplay = true;
|
|
2691
|
-
video.muted = true;
|
|
2692
|
-
video.style.width = "100%";
|
|
2693
|
-
video.style.height = "100%";
|
|
2694
|
-
video.style.objectFit = "cover";
|
|
2695
|
-
video.style.position = "absolute";
|
|
2696
|
-
video.style.inset = "0";
|
|
2697
|
-
scanArea.appendChild(video);
|
|
2698
|
-
const frame = document.createElement("div");
|
|
2699
|
-
frame.style.position = "absolute";
|
|
2700
|
-
frame.style.top = "0";
|
|
2701
|
-
frame.style.left = "0";
|
|
2702
|
-
frame.style.right = "0";
|
|
2703
|
-
frame.style.bottom = "0";
|
|
2704
|
-
frame.style.border = "3px solid rgba(255,255,255,0.9)";
|
|
2705
|
-
frame.style.borderRadius = "18px";
|
|
2706
|
-
frame.style.pointerEvents = "none";
|
|
2707
|
-
frame.style.zIndex = "3";
|
|
2708
|
-
scanBox.appendChild(frame);
|
|
2709
|
-
const line = document.createElement("div");
|
|
2710
|
-
line.style.position = "absolute";
|
|
2711
|
-
line.style.left = "0";
|
|
2712
|
-
line.style.right = "0";
|
|
2713
|
-
line.style.height = "2px";
|
|
2714
|
-
line.style.background = "rgba(255,255,255,0.85)";
|
|
2715
|
-
line.style.borderRadius = "2px";
|
|
2716
|
-
line.style.animation = "qr-line 2s infinite";
|
|
2717
|
-
line.style.zIndex = "4";
|
|
2718
|
-
scanBox.appendChild(line);
|
|
2719
|
-
const styleTag = document.createElement("style");
|
|
2720
|
-
styleTag.innerHTML = `
|
|
3
|
+
`);return window.alert(e),r.buttons?.[0]?.id??"ok"}async scanQRCode(r){return null}async requestPhone(){return null}async requestNotificationsPermission(){if(typeof Notification>"u"||typeof Notification.requestPermission!="function")return!1;try{return await Notification.requestPermission()==="granted"}catch(r){return console.warn("[tvm-app-adapter] requestNotificationsPermission fallback failed:",r),!1}}async addToHomeScreen(){return!1}async checkHomeScreenStatus(){return"unknown"}async denyNotifications(){return console.warn("[tvm-app-adapter] denyNotifications fallback is not supported in this environment."),!1}enableVerticalSwipes(){}disableVerticalSwipes(){}notifyEnvironmentChanged(){for(let r of this.listeners)try{r()}catch(e){console.warn("[tvm-app-adapter] environment listener failed:",e)}}onDestroy(){}registerDisposable(r){return this.disposables.add(r)}readCustomUrlParams(r){if(typeof window>"u")return{};let e={},t=i=>{let s=new Set;for(let[p]of i.entries())s.add(p);for(let p of s){if(r?.(p))continue;let d=i.getAll(p);d.length&&(e[p]=d.length===1?d[0]:d)}};t(new URLSearchParams(window.location.search));let n=window.location.hash.startsWith("#")?window.location.hash.slice(1):window.location.hash;return n&&n.includes("=")&&t(new URLSearchParams(n)),e}applyScrollGuards(){if(typeof document>"u")return;let r=document.documentElement,e=document.body;if(!r||!e)return;let t=r.style.overscrollBehaviorY,n=e.style.overscrollBehaviorY,i=e.style.touchAction;return r.style.overscrollBehaviorY="none",e.style.overscrollBehaviorY="none",e.style.touchAction="manipulation",()=>{r.style.overscrollBehaviorY=t,e.style.overscrollBehaviorY=n,e.style.touchAction=i}}};function k(){return window.WebApp}var q=class extends A{constructor(){super("max");c(this,"backHandlers",new Map);c(this,"initData");c(this,"initDataUnsafe")}async init(e){if(this.ready)return;let t=k();t?.ready?.(),this.initData=t?.initData,this.initDataUnsafe=t?.initDataUnsafe;let n={platform:"max",sdkVersion:t?.version,appVersion:t?.version,languageCode:t?.initDataUnsafe?.user?.language_code,isWebView:!0};this.environment=n,this.ready=!0}supports(e){let t=k();switch(e){case"haptics":return!!t?.HapticFeedback?.impactOccurred;case"qrScanner":return typeof t?.openCodeReader=="function";case"closeApp":return typeof t?.close=="function";case"backButton":return!!t?.BackButton?.onClick;case"backButtonVisibility":return!!(t?.BackButton?.show&&t.BackButton.hide);case"openInternalLink":return typeof t?.openMaxLink=="function";case"downloadFile":return typeof t?.downloadFile=="function";case"requestPhone":return t?typeof t.requestPhoneNumber=="function"||typeof window<"u":!1;case"popup":return!1;default:return!1}}getInitData(){return this.initData}getLaunchParams(){return{launchParams:this.initDataUnsafe,customLaunchParams:this.readCustomUrlParams()}}onBackButton(e){let t=k();if(!t?.BackButton?.onClick)return super.onBackButton(e);let n=()=>e(),i=t.BackButton.onClick(n);t.BackButton.show?.();let s=this.registerDisposable(()=>{typeof i=="function"?i():t.BackButton?.offClick?.(n),this.backHandlers.delete(e),this.backHandlers.size||t.BackButton?.hide?.()});return this.backHandlers.set(e,s),s}setBackButtonVisibility(e){let t=k();t?.BackButton&&(e?t.BackButton.show?.():t.BackButton.hide?.())}async openExternalLink(e){let t=k();if(t?.openExternalLink){t.openExternalLink(e);return}await super.openExternalLink(e)}async openInternalLink(e){let t=k();if(t?.openMaxLink){t.openMaxLink(e);return}await super.openInternalLink(e)}async closeApp(){let e=k();if(e?.close){e.close();return}await super.closeApp()}vibrateImpact(e){let t=k();if(!t?.HapticFeedback?.impactOccurred){super.vibrateImpact(e);return}t.HapticFeedback.impactOccurred(e).catch(n=>{console.warn("[mini-app-template] MAX impact haptic failed:",n)})}vibrateNotification(e){let t=k();if(!t?.HapticFeedback?.notificationOccurred){super.vibrateNotification(e);return}t.HapticFeedback.notificationOccurred(e).catch(n=>{console.warn("[mini-app-template] MAX notification haptic failed:",n)})}vibrateSelection(){let e=k();if(!e?.HapticFeedback?.selectionChanged){super.vibrateSelection();return}e.HapticFeedback.selectionChanged().catch(t=>{console.warn("[mini-app-template] MAX selection haptic failed:",t)})}async scanQRCode(e){let t=k();if(!t?.openCodeReader)return super.scanQRCode(e);try{return(await t.openCodeReader(e?.closeOnCapture!==!1))?.value??null}catch(n){return console.warn("[mini-app-template] MAX QR scanner failed:",n),null}}async requestPhone(){let e=k();if(e?.requestPhoneNumber)try{let t=await e.requestPhoneNumber();return this.extractPhone(t)}catch(t){return console.warn("[mini-app-template] MAX requestPhone failed:",t),null}return this.requestPhoneViaEvent()}async showPopup(e){return super.showPopup(e)}async downloadFile(e,t){let n=k();if(n?.downloadFile)try{await n.downloadFile(e,t);return}catch(i){console.warn("[mini-app-template] MAX downloadFile failed:",i)}await super.downloadFile(e,t)}async requestPhoneViaEvent(){if(typeof window>"u")return null;let e,t={providePromise:n=>{e=n}};if(window.dispatchEvent(new CustomEvent("WebAppRequestPhone",{detail:t})),!e)return console.warn("[mini-app-template] MAX requestPhone not handled: native promise missing"),null;try{let n=await e;return this.extractPhone(n)}catch(n){return console.warn("[mini-app-template] MAX requestPhone promise rejected:",n),null}}extractPhone(e){if(typeof e=="string")return e||null;if(!e||typeof e!="object")return null;let t=e.phone??e.phone_number??e.phoneNumber;if(typeof t=="string"&&t)return t;let n=e.contact;if(n&&typeof n=="object"){let i=n.phone??n.phone_number??n.phoneNumber;if(typeof i=="string"&&i)return i}return null}onDestroy(){this.backHandlers.clear(),super.onDestroy()}};var K=class extends A{constructor(r){super(r,{isWebView:!0,hasNativeQR:!0,hasPush:!0,hasWidgets:!0})}supports(r){switch(r){case"qrScanner":return!0;case"notifications":return!0;default:return super.supports(r)}}async scanQRCode(){try{return await this.shell.openNativeQR()??null}catch(r){return console.warn("[tvm-app-adapter] shell.openNativeQR failed:",r),null}}async requestNotificationsPermission(){return ge()}};import{backButton as V,emitEvent as Ke,hapticFeedback as re,init as Lt,initData as Ae,mockTelegramEnv as Tt,miniApp as g,openLink as ze,popup as Ue,qrScanner as ke,postEvent as Qe,retrieveLaunchParams as Pe,setDebug as _t,themeParams as _,viewport as M}from"@tma.js/sdk-react";import{decodeStartParam as je,closingBehavior as Ge,requestContact as $e,requestPhoneAccess as Se,swipeBehavior as x,viewport as R,shareURL as Ye,copyTextToClipboard as Xe,downloadFile as Je,shareStory as Ze,addToHomeScreen as F,checkHomeScreenStatus as et,on as tt,off as nt}from"@tma.js/sdk";function C(o){if(typeof o!="function")return!1;let r=o;if(typeof r.isAvailable=="function")try{return r.isAvailable()}catch(e){return console.warn("[tvm-app-adapter] feature availability check failed:",e),!1}return!0}function S(o,...r){if(!C(o))return{ok:!1};try{return{ok:!0,value:o(...r)}}catch(e){return console.warn("[tvm-app-adapter] feature call failed:",e),{ok:!1}}}async function ne(o){let{sdkViewport:r,fallbackMount:e}=o;if(r?.isSupported?.()){if(typeof r.isMounted=="function"&&r.isMounted())return;typeof r.mount=="function"&&await r.mount();return}typeof e=="function"&&await e()}async function qe(o){if(await ne(o),typeof o.bindCssVars=="function")try{o.bindCssVars(o.mapper)}catch(r){if(r instanceof Error&&/css variables are already bound/i.test(r.message))return;throw r}}var z=class extends A{constructor(){super("telegram");c(this,"backHandlers",new Map);c(this,"cssVariablesBound",!1);c(this,"appearanceListeners",new Set);c(this,"appearanceWatcherDispose");c(this,"viewHideListeners",new Set);c(this,"viewRestoreListeners",new Set);c(this,"activeWatcherDispose")}async init(e){if(this.ready)return;let t=!!e?.debug,n=!!e?.eruda,i=!!e?.mockForMacOS;if(_t(t),Lt(),g.isSupported()||console.warn("[tvm-app-adapter] miniApp feature is not supported; falling back to limited mode."),n&&typeof window<"u"&&window.eruda&&(window.eruda.init(),window.eruda.position({x:window.innerWidth-150,y:window.innerHeight-150})),i){let a=!1;Tt({onEvent(l,h){if(l.name==="web_app_request_theme"){let u={};return a?u=_.state():(a=!0,u||(u=Pe().tgWebAppThemeParams)),Ke("theme_changed",{theme_params:u})}if(l.name==="web_app_request_safe_area")return Ke("safe_area_changed",{left:0,top:0,right:0,bottom:0});h()}})}Ae.restore(),g.ready();let s=Pe(),p;try{p=g.isDark()?"dark":"light"}catch{p=void 0}let d={platform:"telegram",sdkVersion:s.tgWebAppVersion,languageCode:Ae.user()?.language_code,appearance:p,isWebView:!0};this.environment=d,this.notifyAppearance(d.appearance),V.mount.ifAvailable(),g.mount.isAvailable()&&(_.mount(),g.mount(),this.bindCssVariables()),await this.prepareViewport(),this.setupAppearanceWatcher(),this.setupActiveWatcher(),this.ready=!0}async setColors(e){let t={};if(e.header)if(g.setHeaderColor.isAvailable()){let n=g.setHeaderColor.supports?.("rgb")?e.header:"bg_color",{ok:i}=S(g.setHeaderColor,n);i||(t.header=e.header)}else t.header=e.header;if(e.background)if(g.setBgColor.isAvailable()){let{ok:n}=S(g.setBgColor,e.background);n||(t.background=e.background)}else t.background=e.background;if(e.footer)if(g.setBgColor.isAvailable()){let{ok:n}=S(g.setBottomBarColorFp,e.footer);n||(t.footer=e.footer)}else t.footer=e.footer;(t.header||t.background)&&await super.setColors(t)}copyTextToClipboard(e){return Xe(e)}onBackButton(e){if(!V.isSupported())return super.onBackButton(e);let t=V.onClick(()=>e()),n=this.registerDisposable(()=>{typeof t=="function"&&t(),this.backHandlers.delete(e),this.backHandlers.size||V.hide()});return this.backHandlers.set(e,n),n}async openExternalLink(e){try{ze(e,{tryInstantView:!0});return}catch{}await super.openExternalLink(e)}async openInternalLink(e){Qe("web_app_open_tg_link",{path_full:e})}enableDebug(e){try{e?Ge.enableConfirmation():Ge.disableConfirmation()}catch{}}supports(e){switch(e){case"haptics":return C(re.selectionChanged);case"popup":return C(Ue.show);case"qrScanner":return C(ke.open);case"closeApp":return C(g.close);case"backButton":return V.isSupported();case"backButtonVisibility":return V.hide.isSupported();case"bindCssVariables":return!0;case"openExternalLink":return C(ze);case"openInternalLink":return!0;case"requestFullscreen":return!!(typeof R.requestFullscreen=="function"||M.requestFullscreen?.isAvailable?.());case"verticalSwipes":return!!(x.enableVertical.isAvailable()||x.disableVertical.isAvailable());case"viewVisibility":return!0;case"shareUrl":return typeof Ye=="function";case"shareStory":return typeof Ze=="function";case"copyTextToClipboard":return typeof Xe=="function";case"downloadFile":return typeof Je=="function";case"addToHomeScreen":return typeof F?.isAvailable=="function"?F.isAvailable():typeof F=="function";case"checkHomeScreenStatus":return typeof et=="function";case"requestPhone":return!!(C(Se)||C($e));default:return!1}}bindCssVariables(e){if(!this.cssVariablesBound)try{_.bindCssVars(e),this.cssVariablesBound=!0}catch(t){if(t instanceof Error&&/css variables are already bound/i.test(t.message)){this.cssVariablesBound=!0;return}throw t}}vibrateImpact(e){this.supports("haptics")&&re.impactOccurred(e)}vibrateNotification(e){this.supports("haptics")&&re.notificationOccurred(e)}vibrateSelection(){this.supports("haptics")&&re.selectionChanged()}async showPopup(e){let t=S(Ue.show,{title:e.title,message:e.message,buttons:e.buttons?.map(i=>({id:i.id,text:i.text??i.id,type:i.type??"default"}))});return t.ok?await t.value??null:super.showPopup(e)}async scanQRCode(e){let t=null,n=e?.closeOnCapture??!0,i=S(ke.open,{onCaptured:s=>{t=s,n&&S(ke.close)}});return i.ok?(await i.value,t):super.scanQRCode(e)}async closeApp(){S(g.close).ok||await super.closeApp()}getInitData(){try{return Ae.raw()}catch{return}}getLaunchParams(){let e=this.readCustomUrlParams(n=>n.toLowerCase().startsWith("tgwebapp")),t={};try{let n=Pe(),i=n.tgWebAppStartParam;return typeof i=="string"&&i&&(t=this.normalizeDecodedStartParam(i)),{launchParams:n,customLaunchParams:{...e,...t}}}catch{return{customLaunchParams:{...e,...t}}}}decodeStartParam(e){try{return je(e)}catch{return}}requestFullscreen(){this.requestFullscreenInternal()}getViewportInsets(){try{let e=M.safeAreaInsets(),t=M.contentSafeAreaInsets();return{safeArea:e,contentSafeArea:t}}catch{return}}onViewportChange(e){let t=[],n=()=>typeof window<"u"?window.visualViewport?.height??window.innerHeight:0,i=d=>{let a=d?.height??this.safeHeightFromSdk(),l=d?.stableHeight??this.stableHeightFromSdk(),h=Number.isFinite(a)?a:n(),u=Number.isFinite(l)&&l>0?l:h;e({height:h,stableHeight:u})};(async()=>{try{await ne(this.getViewportMountOptions())}catch(d){console.warn("[tvm-app-adapter] ensureViewportMounted failed:",d)}})().finally(()=>i());let{sdkViewport:p}=this.getViewportMountOptions();if(typeof p.on=="function")try{let d=p.on("change",a=>i(a));typeof d=="function"&&t.push(d)}catch(d){console.warn("[tvm-app-adapter] viewport.on(change) subscription failed:",d)}try{typeof p.height?.sub=="function"&&t.push(p.height.sub(()=>i())),typeof p.stableHeight?.sub=="function"&&t.push(p.stableHeight.sub(()=>i()))}catch(d){console.warn("[tvm-app-adapter] viewport signal subscriptions failed:",d)}if(typeof window<"u"){let d=()=>i();window.visualViewport?.addEventListener("resize",d),window.addEventListener("resize",d),t.push(()=>{window.visualViewport?.removeEventListener("resize",d),window.removeEventListener("resize",d)})}return()=>{t.forEach(d=>{try{d()}catch{}})}}onAppearanceChange(e){return this.appearanceListeners.add(e),e(this.environment.appearance),()=>{this.appearanceListeners.delete(e)}}setBackButtonVisibility(e){V.isSupported()&&(e?V.show():V.hide())}enableVerticalSwipes(){try{let e=x;typeof e.isSupported=="function"&&e.isSupported()?(typeof e.isMounted=="function"&&!e.isMounted()&&e.mount?.(),e.enableVertical?.()):x.enableVertical.isAvailable()&&x.enableVertical()}catch(e){console.warn("[tvm-app-adapter] enableVerticalSwipes failed:",e)}}disableVerticalSwipes(){try{let e=x;typeof e.isSupported=="function"&&e.isSupported()?(typeof e.isMounted=="function"&&!e.isMounted()&&e.mount?.(),e.disableVertical?.()):x.disableVertical.isAvailable()&&x.disableVertical()}catch(e){console.warn("[tvm-app-adapter] disableVerticalSwipes failed:",e)}}onViewHide(e){return this.viewHideListeners.add(e),()=>{this.viewHideListeners.delete(e)}}onViewRestore(e){return this.viewRestoreListeners.add(e),()=>{this.viewRestoreListeners.delete(e)}}shareUrl(e,t){return Ye(e,t)}async downloadFile(e,t){let n=S(Je,e,t);if(n.ok)try{await n.value;return}catch(i){console.warn("[tvm-app-adapter] Telegram downloadFile failed:",i)}await super.downloadFile(e,t)}async shareStory(e,t){let n=t?.telegram?.text??t?.text,i=t?.telegram?.widgetLink??(t?.link?{url:t.link.url,...t.link.name?{name:t.link.name}:{}}:void 0);Ze(e,{...n?{text:n}:{},...i?{widgetLink:i}:{}})}async addToHomeScreen(){return(typeof F?.isAvailable=="function"?F.isAvailable():!0)?new Promise(t=>{let n=()=>{nt("home_screen_added",i),nt("home_screen_failed",s)},i=()=>{n(),t(!0)},s=()=>{n(),t(!1)};tt("home_screen_added",i),tt("home_screen_failed",s);try{F()}catch(p){n(),console.warn("[tvm-app-adapter] Telegram addToHomeScreen failed:",p),t(!1)}}):super.addToHomeScreen()}async checkHomeScreenStatus(){try{let e=await et();return typeof e=="string"?e:typeof e=="boolean"?e?"added":"not_added":"unknown"}catch(e){return console.warn("[tvm-app-adapter] Telegram checkHomeScreenStatus failed:",e),"unknown"}}async requestPhone(){let e=S($e);if(!e.ok)return super.requestPhone();if(Se){let t=S(Se);if(t.ok)try{await t.value}catch(n){console.warn("[tvm-app-adapter] Telegram requestPhone access failed:",n)}}try{let t=await e.value;if(!t||typeof t!="object")return null;let n=t.contact,i=n?.phoneNumber??n?.phone_number??n?.phone??t.phoneNumber??t.phone_number??t.phone;return typeof i=="string"&&i?i:null}catch(t){return console.warn("[tvm-app-adapter] Telegram requestPhone failed:",t),null}}setupAppearanceWatcher(){if(this.appearanceWatcherDispose?.(),typeof _.isDark?.sub=="function"){let e=_.isDark.sub(()=>{let t=_.isDark()?"dark":"light";this.environment.appearance=t,this.notifyAppearance(t)});this.appearanceWatcherDispose=this.registerDisposable(e)}}notifyAppearance(e){for(let t of this.appearanceListeners)t(e)}setupActiveWatcher(){this.activeWatcherDispose?.();let e=g.isActive,t=()=>{try{g.isActive()?this.notifyViewRestore():this.notifyViewHide()}catch(n){console.warn("[tvm-app-adapter] miniApp.isActive() failed:",n)}};if(typeof e?.sub=="function"){let n=e.sub(()=>t());this.activeWatcherDispose=this.registerDisposable(n),t();return}try{t()}catch{}}async prepareViewport(){try{await qe({...this.getViewportMountOptions(),bindCssVars:typeof M.bindCssVars=="function"?M.bindCssVars:void 0})}catch(e){console.warn("[tvm-app-adapter] prepareViewport failed:",e)}}async requestFullscreenInternal(){try{let e=this.getViewportMountOptions();await ne(e);let{sdkViewport:t}=e;(typeof t.isSupported=="function"?t.isSupported():!1)&&typeof t.requestFullscreen=="function"?await t.requestFullscreen():M.requestFullscreen&&M.requestFullscreen.isAvailable?.()?await M.requestFullscreen():Qe("web_app_request_fullscreen"),this.disableVerticalSwipes()}catch(e){console.warn("[tvm-app-adapter] Telegram requestFullscreen failed:",e)}}getViewportMountOptions(){return{sdkViewport:R,fallbackMount:async()=>{M.mount?.isAvailable?.()&&await M.mount()}}}safeHeightFromSdk(){try{if(typeof R.height=="function")return R.height()}catch{return}}stableHeightFromSdk(){try{if(typeof R.stableHeight=="function")return R.stableHeight()}catch{return}}notifyViewHide(){for(let e of this.viewHideListeners)try{e()}catch(t){console.warn("[tvm-app-adapter] onViewHide listener failed:",t)}}normalizeDecodedStartParam(e){let t;try{t=je(e)}catch{t=e}if(t&&typeof t=="object"&&!Array.isArray(t))return{...t};if(typeof t=="string"&&t){let n=this.parseQueryString(t);return Object.keys(n).length?n:{startParam:t}}return{}}parseQueryString(e){let t=e.startsWith("?")?e.slice(1):e,n=new URLSearchParams(t),i={},s=new Set;for(let[p]of n.entries())s.add(p);for(let p of s){let d=n.getAll(p);d.length&&(i[p]=d.length===1?d[0]:d)}return i}notifyViewRestore(){for(let e of this.viewRestoreListeners)try{e()}catch(t){console.warn("[tvm-app-adapter] onViewRestore listener failed:",t)}}onDestroy(){this.appearanceWatcherDispose?.(),this.appearanceWatcherDispose=void 0,this.activeWatcherDispose?.(),this.activeWatcherDispose=void 0,this.appearanceListeners.clear(),this.viewHideListeners.clear(),this.viewRestoreListeners.clear(),this.backHandlers.clear(),super.onDestroy()}};import f,{parseURLSearchParamsForGetLaunchParams as Rt}from"@vkontakte/vk-bridge";var Me=null;function ie(o){if(typeof o!="string"){Me=null;return}Me=o.trim()||null}function Ce(){return Me}async function rt(o,r){if(typeof r!="function")return!1;try{return await r(o)}catch(e){return console.warn("[tvm-app-adapter] bridge.supportsAsync failed:",e),!1}}var Ft=/^[a-z0-9][a-z0-9_.:-]{0,63}$/i,it="VK_ANALYTICS_EVENT",U=class extends A{constructor(){super("vk");c(this,"configSafeArea");c(this,"stopViewportTracking");c(this,"supportsAsync",typeof f.supportsAsync=="function"?f.supportsAsync.bind(f):void 0);c(this,"pixelCodeWarningShown",!1);c(this,"unsubscribe");c(this,"launchParams");c(this,"queryParams");c(this,"viewHideListeners",new Set);c(this,"viewRestoreListeners",new Set)}computeSafeArea(){let e=this.computeBaseSafeArea(),t=this.resolveOverlayInsets();return t?D({environment:e,minimum:t}):e}async init(e){if(this.ready)return;let t=a=>this.handleBridgeEvent(a);f.subscribe(t),this.unsubscribe=this.registerDisposable(()=>f.unsubscribe(t));let n;try{n=await f.send("VKWebAppGetConfig")}catch(a){console.warn("[tvm-app-adapter] VKWebAppGetConfig failed:",a)}try{let a=await f.send("VKWebAppInit");a&&"result"in a&&a.result===!1&&console.warn("[tvm-app-adapter] VKWebAppInit returned result=false.")}catch(a){throw console.error("[tvm-app-adapter] VKWebAppInit failed:",a),this.unsubscribe?.(),this.unsubscribe=void 0,a}let i;try{i=await f.send("VKWebAppGetLaunchParams")}catch(a){throw console.error("[tvm-app-adapter] VKWebAppGetLaunchParams failed:",a),this.unsubscribe?.(),this.unsubscribe=void 0,a}let s=typeof window<"u"?window.location.search:"",p=Rt(s);this.launchParams=i,this.queryParams=p,this.environment=this.composeEnvironment(i,p,n),this.configSafeArea=this.environment.safeArea;let d=this.computeSafeArea();this.environment.safeArea=d,this.applyAppearance(this.environment.appearance,n?.scheme),this.notifyEnvironmentChanged(),this.ready=!0,this.startViewportTracking()}async vibrateImpact(e){await this.supportsBridgeMethod("VKWebAppTapticImpactOccurred")&&f.send("VKWebAppTapticImpactOccurred",{style:e})}async vibrateNotification(e){await this.supportsBridgeMethod("VKWebAppTapticNotificationOccurred")&&f.send("VKWebAppTapticNotificationOccurred",{type:e})}async vibrateSelection(){await this.supportsBridgeMethod("VKWebAppTapticSelectionChanged")&&f.send("VKWebAppTapticSelectionChanged")}async setColors(e){let{header:t,background:n}=e;if((t||n)&&await this.supportsBridgeMethod("VKWebAppSetViewSettings")){let s=t?this.resolveStatusBarStyle(t):this.environment.appearance?.includes("dark")?"light":"dark";await f.send("VKWebAppSetViewSettings",{status_bar_style:s,...t?{action_bar_color:t}:{},...n?{navigation_bar_color:n}:{}})}await super.setColors(e)}getEnvironment(){return{...this.environment,isWebView:f.isWebView()}}getLaunchParams(){return this.launchParams?{launchParams:this.launchParams,customLaunchParams:this.readCustomUrlParams(e=>{let t=e.toLowerCase();return t.startsWith("vk_")||t==="sign"})}:{customLaunchParams:this.readCustomUrlParams(e=>{let t=e.toLowerCase();return t.startsWith("vk_")||t==="sign"})}}async openExternalLink(e){let t=document.createElement("a");t.href=e,t.target="_blank",t.rel="noopener noreferrer",t.style.display="none",document.body.appendChild(t),t.click(),t.remove()}async supports(e){switch(e){case"haptics":{let[t,n,i]=await Promise.all([this.supportsBridgeMethod("VKWebAppTapticImpactOccurred"),this.supportsBridgeMethod("VKWebAppTapticNotificationOccurred"),this.supportsBridgeMethod("VKWebAppTapticSelectionChanged")]);return t||n||i}case"qrScanner":return this.supportsBridgeMethod("VKWebAppOpenCodeReader");case"requestPhone":{let[t,n]=await Promise.all([this.supportsBridgeMethod("VKWebAppGetPhoneNumber"),this.supportsBridgeMethod("VKWebAppGetPersonalCard")]);return t||n}case"notifications":return this.supportsBridgeMethod("VKWebAppAllowNotifications");case"shareUrl":return this.supportsBridgeMethod("VKWebAppShare");case"shareStory":return this.supportsBridgeMethod("VKWebAppShowStoryBox");case"downloadFile":return this.supportsBridgeMethod("VKWebAppDownloadFile");case"addToHomeScreen":return this.supportsBridgeMethod("VKWebAppAddToHomeScreen");case"denyNotifications":return this.supportsBridgeMethod("VKWebAppDenyNotifications");case"openExternalLink":return!0;case"viewVisibility":return!0;default:return await super.supports(e)}}async requestPhone(){let[e,t]=await Promise.all([this.supportsBridgeMethod("VKWebAppGetPhoneNumber"),this.supportsBridgeMethod("VKWebAppGetPersonalCard")]);if(!e&&!t)return super.requestPhone();try{if(e){let p=(await f.send("VKWebAppGetPhoneNumber")).phone_number;return typeof p=="string"&&p?p:null}let i=(await f.send("VKWebAppGetPersonalCard",{type:["phone"]})).phone;return typeof i=="string"&&i?i:null}catch(n){return console.warn("[tvm-app-adapter] VK requestPhone failed:",n),null}}async requestNotificationsPermission(){if(!await this.supportsBridgeMethod("VKWebAppAllowNotifications"))return super.requestNotificationsPermission();try{let t=await f.send("VKWebAppAllowNotifications");return t&&typeof t=="object"&&"result"in t?!!t.result:!0}catch(t){return console.warn("[tvm-app-adapter] VK allow notifications failed:",t),!1}}async addToHomeScreen(){if(!await this.supportsBridgeMethod("VKWebAppAddToHomeScreen"))return console.warn("[tvm-app-adapter] VK addToHomeScreen not supported"),super.addToHomeScreen();try{let t=await f.send("VKWebAppAddToHomeScreen");return t&&typeof t=="object"&&"result"in t?!!t.result:!0}catch(t){return console.warn("[tvm-app-adapter] VK addToHomeScreen failed:",t),!1}}async denyNotifications(){if(!await this.supportsBridgeMethod("VKWebAppDenyNotifications"))return super.denyNotifications();try{let t=await f.send("VKWebAppDenyNotifications");return t&&typeof t=="object"&&"result"in t?!!t.result:!0}catch(t){return console.warn("[tvm-app-adapter] VK deny notifications failed:",t),!1}}async scanQRCode(e){if(!await this.supportsBridgeMethod("VKWebAppOpenCodeReader"))return super.scanQRCode(e);let n=null;try{let i=await f.send("VKWebAppOpenCodeReader");i.code_data&&(n=i.code_data)}catch(i){console.log(i)}return n}onViewHide(e){return this.viewHideListeners.add(e),()=>{this.viewHideListeners.delete(e)}}onViewRestore(e){return this.viewRestoreListeners.add(e),()=>{this.viewRestoreListeners.delete(e)}}async shareStory(e,t){let n=t,i=n?.vk,s=n?.link?{type:"url",text:"open",url:n.link.url}:void 0,p=n?.text?[{sticker_type:"native",sticker:{action_type:"text",action:{text:n.text,style:"classic",background_style:"none"},transform:{gravity:"center_bottom",translation_y:-.2}}}]:void 0,d={background_type:i?.backgroundType??"image",url:e,locked:i?.locked??!0,...i?.attachment??s?{attachment:i?.attachment??s}:{},...i?.stickers??p?{stickers:i?.stickers??p}:{}};await f.send("VKWebAppShowStoryBox",d)}shareUrl(e,t){this.shareUrlInternal(e,t)}async shareUrlInternal(e,t){if(!await this.supportsBridgeMethod("VKWebAppShare")){super.shareUrl(e,t??"");return}try{await f.send("VKWebAppShare",{link:e,...t?{text:t}:{}})}catch(i){console.warn("[tvm-app-adapter] VK shareUrl failed:",i),super.shareUrl(e,t??"")}}async downloadFile(e,t){if(!await this.supportsBridgeMethod("VKWebAppDownloadFile")){await super.downloadFile(e,t);return}try{if((await f.send("VKWebAppDownloadFile",{url:e,filename:t}))?.result===!1)throw new Error("VKWebAppDownloadFile returned result=false")}catch(i){console.warn("[tvm-app-adapter] VK downloadFile failed:",i),await super.downloadFile(e,t)}}trackConversionEvent(e,t){let n=this.normalizeAnalyticsEventName(e);if(!n)return;let i={method:"VKWebAppConversionHit",params:{event:n,params:this.normalizeAnalyticsPayload(t)}};this.dispatchAnalytics(i)}trackPixelEvent(e,t){let n=Ce();if(!n){this.pixelCodeWarningShown||(console.warn("[VKAnalytics] VK pixel code is not configured. Call configureVkPixel() before tracking."),this.pixelCodeWarningShown=!0);return}let i=this.normalizeAnalyticsEventName(e);if(!i)return;let s={method:"VKWebAppRetargetingPixel",params:{pixel_code:n,type:i,data:this.normalizeAnalyticsPayload(t)}};this.pixelCodeWarningShown=!1,this.dispatchAnalytics(s)}dispatchAnalytics(e){if(typeof f.isWebView=="function")try{if(!f.isWebView()){this.emitAnalyticsFallback(e);return}}catch(t){console.warn("[VKAnalytics] bridge.isWebView check failed:",t),this.emitAnalyticsFallback(e);return}this.safeBridgeSend(e.method,e.params)}emitAnalyticsFallback(e){if(typeof window>"u")return;let t={...e,timestamp:Date.now()};if(typeof window.dispatchEvent=="function"&&typeof window.CustomEvent=="function"){window.dispatchEvent(new CustomEvent(it,{detail:t}));return}try{window.postMessage({type:it,detail:t},"*")}catch(n){console.warn("[VKAnalytics] fallback dispatch failed",n)}}normalizeAnalyticsEventName(e){if(typeof e!="string")return null;let t=e.trim();return!t||!Ft.test(t)?(console.warn(`[VKAnalytics] Invalid event name: "${e}"`),null):t}normalizeAnalyticsPayload(e){return this.isPlainObject(e)?{...e}:{}}isPlainObject(e){if(e===null||typeof e!="object"||Array.isArray(e))return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}async safeBridgeSend(e,t){try{if(!await this.supportsBridgeMethod(e))return;await f.send(e,t)}catch(n){console.warn(`[VKAnalytics] ${e} failed`,n)}}composeEnvironment(e,t,n){let i=t.vk_language??e.vk_language,s=t.vk_platform??e.vk_platform,p=t.vk_app_id??e.vk_app_id,d=typeof window<"u"&&typeof window.matchMedia=="function"?window.matchMedia("(prefers-color-scheme: dark)").matches:!1,a=this.normalizeAppearance(n?.appearance,n?.scheme)??(d?"dark":"light"),l=this.extractSafeAreaFromConfig(n);return{platform:"vk",sdkVersion:s?String(s):void 0,appVersion:typeof p=="number"?`vk-app-${p}`:void 0,languageCode:i?String(i):void 0,appearance:a,isWebView:f.isWebView(),safeArea:l}}handleBridgeEvent(e){let{type:t,data:n}=e.detail??{};if(t==="VKWebAppViewHide"){this.notifyVisibilityListeners(this.viewHideListeners);return}if(t==="VKWebAppViewRestore"){this.notifyVisibilityListeners(this.viewRestoreListeners);return}if(t==="VKWebAppUpdateConfig"&&n){let i=n;this.updateEnvironmentFromConfig(i)}}updateEnvironmentFromConfig(e){if(!this.environment)return;let t=this.normalizeAppearance(e.appearance,e.scheme),n=!1;t&&t!==this.environment.appearance&&(this.environment.appearance=t,n=!0);let i=this.environment.safeArea,s=this.extractSafeAreaFromConfig(e);s?(this.configSafeArea=s,this.environment.safeArea=s):(this.configSafeArea=void 0,this.environment.safeArea=void 0);let p=this.computeSafeArea()??{top:0,right:0,bottom:0,left:0},d=i;!d||d.top!==p.top||d.right!==p.right||d.bottom!==p.bottom||d.left!==p.left?(this.environment.safeArea=p,n=!0):this.environment.safeArea=i,this.applyAppearance(this.environment.appearance,e.scheme),n&&this.notifyEnvironmentChanged()}applyAppearance(e,t){if(!(typeof document>"u")&&(e&&(document.documentElement.dataset.vkAppearance=e,document.documentElement.classList.toggle("dark",e==="dark")),t&&(document.documentElement.dataset.vkScheme=t,!e))){let n=this.normalizeAppearance(void 0,t);document.documentElement.classList.toggle("dark",n==="dark")}}resolveStatusBarStyle(e){let t=e.replace("#",""),n=t.length===3?t.split("").map(a=>a+a).join(""):t.slice(0,6),i=parseInt(n.slice(0,2),16)/255,s=parseInt(n.slice(2,4),16)/255,p=parseInt(n.slice(4,6),16)/255;return .2126*i+.7152*s+.0722*p>.6?"dark":"light"}normalizeAppearance(e,t){let n=e?.toLowerCase();if(n==="dark"||n==="light")return n;let i=t?.toLowerCase();if(i)return i.includes("dark")||i.includes("space_gray")?"dark":"light"}extractSafeAreaFromConfig(e){let t=e&&"insets"in e?e.insets:void 0;if(!t)return;let{top:n=0,right:i=0,bottom:s=0,left:p=0}=t,d=[n,i,s,p].map(m=>typeof m=="number"?m:Number(m)||0);if(!d.some(m=>m!==0))return;let[l,h,u,b]=d;return{top:l,right:h,bottom:u,left:b}}notifyVisibilityListeners(e){for(let t of e)try{t()}catch(n){console.warn("[tvm-app-adapter] VK visibility listener failed:",n)}}computeBaseSafeArea(){return D({environment:this.configSafeArea,viewport:this.getViewportInsets?.(),css:Z()})}startViewportTracking(){this.stopViewportTracking?.();let e=Oe({getSafeArea:()=>this.computeSafeArea(),onChange:t=>{this.environment.safeArea=t,this.notifyEnvironmentChanged()}});e&&(this.stopViewportTracking=this.registerDisposable(e))}resolveOverlayInsets(){if(typeof window>"u"||!f.isWebView()||!this.isLikelyMobilePlatform())return;let e=window.innerWidth||document.documentElement?.clientWidth||0;if(!e||e>880)return;let i=!!window.matchMedia?.("(orientation: landscape)")?.matches;return{top:i?48:56,right:i?72:88}}isLikelyMobilePlatform(){let e=(this.resolveLaunchParam("vk_platform")??"").toLowerCase(),t=(this.resolveLaunchParam("vk_viewer_device")??"").toLowerCase();if(this.resolveLaunchParam("vk_is_layer")==="1")return!1;let i=/(iphone|ipad|ios|android|mobile)/i,s=/(desktop|web|tablet)/i,p=i.test(e)||i.test(t),d=s.test(e)||s.test(t);return p?!d:!1}resolveLaunchParam(e){let t=this.queryParams?.[e];if(typeof t=="string"&&t)return t;let n=this.launchParams?this.launchParams[e]:void 0;if(typeof n=="string"&&n)return n;if(typeof n=="number")return String(n);if(typeof n=="boolean")return n?"1":"0"}supportsBridgeMethod(e){return rt(e,this.supportsAsync)}onDestroy(){this.unsubscribe?.(),this.unsubscribe=void 0,this.stopViewportTracking?.(),this.stopViewportTracking=void 0,this.viewHideListeners.clear(),this.viewRestoreListeners.clear(),super.onDestroy()}};import It from"jsqr";var Q=class extends A{constructor(){super("web",{sdkVersion:navigator.userAgent,languageCode:navigator.language,isWebView:!1});c(this,"deferredPrompt",null);typeof window<"u"&&window.addEventListener("beforeinstallprompt",e=>{e.preventDefault(),this.deferredPrompt=e})}supports(e){switch(e){case"copyTextToClipboard":return typeof navigator<"u"&&!!navigator.clipboard?.writeText;case"downloadFile":return typeof document<"u";case"shareUrl":return typeof navigator<"u"&&(!!navigator.share||!!navigator.clipboard?.writeText);case"addToHomeScreen":return/android/i.test(navigator.userAgent)&&!!this.deferredPrompt;case"checkHomeScreenStatus":return!0;default:return super.supports(e)}}async downloadFile(e,t){try{await J(e,t,{preferBlob:!0})}catch(n){console.warn("[mini-app-template] Web downloadFile fallback:",n),await super.downloadFile(e,t)}}scanQRCode(e){return new Promise(async t=>{if(typeof document>"u"){t(null);return}let n=document.body.style.overflow,i=document.body.style.position,s=document.body.style.touchAction,p=document.body.style.width,d=document.documentElement.style.overflow;document.body.style.overflow="hidden",document.body.style.position="fixed",document.body.style.width="100%",document.body.style.touchAction="none",document.documentElement.style.overflow="hidden";let a=document.createElement("div");a.id="qr-overlay",a.style.position="fixed",a.style.top="0",a.style.left="0",a.style.right="0",a.style.bottom="0",a.style.zIndex="999999999",a.style.background="rgba(0,0,0,0.92)",a.style.width="100%",a.style.height="100%",a.style.display="flex",a.style.flexDirection="column",a.style.alignItems="center",a.style.justifyContent="center",a.style.gap="20px",a.style.padding="24px",a.style.boxSizing="border-box",a.style.overflow="hidden",a.style.backdropFilter="blur(3px)",document.body.appendChild(a);let l=document.createElement("button");l.innerText="\u2715",l.style.position="absolute",l.style.top="22px",l.style.right="22px",l.style.fontSize="32px",l.style.color="white",l.style.background="transparent",l.style.border="none",l.style.cursor="pointer",l.style.zIndex="9999999999",a.appendChild(l);let h=Math.min(Math.floor(Math.min(window.innerWidth,window.innerHeight)*.72),320),u=document.createElement("div");u.style.width=`${h}px`,u.style.height=`${h}px`,u.style.position="relative",u.style.flex="0 0 auto",u.style.borderRadius="18px",u.style.overflow="hidden",a.appendChild(u);let b=document.createElement("div");b.style.position="absolute",b.style.inset="0",b.style.zIndex="1",b.style.background="#000",u.appendChild(b);let m=document.createElement("video");m.setAttribute("playsinline","true"),m.autoplay=!0,m.muted=!0,m.style.width="100%",m.style.height="100%",m.style.objectFit="cover",m.style.position="absolute",m.style.inset="0",b.appendChild(m);let v=document.createElement("div");v.style.position="absolute",v.style.top="0",v.style.left="0",v.style.right="0",v.style.bottom="0",v.style.border="3px solid rgba(255,255,255,0.9)",v.style.borderRadius="18px",v.style.pointerEvents="none",v.style.zIndex="3",u.appendChild(v);let y=document.createElement("div");y.style.position="absolute",y.style.left="0",y.style.right="0",y.style.height="2px",y.style.background="rgba(255,255,255,0.85)",y.style.borderRadius="2px",y.style.animation="qr-line 2s infinite",y.style.zIndex="4",u.appendChild(y);let ae=document.createElement("style");ae.innerHTML=`
|
|
2721
4
|
@keyframes qr-line {
|
|
2722
5
|
0% { top: 0; }
|
|
2723
6
|
50% { top: calc(100% - 2px); }
|
|
2724
7
|
100% { top: 0; }
|
|
2725
8
|
}
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
const hint = document.createElement("div");
|
|
2729
|
-
hint.innerText = "\u041D\u0430\u0432\u0435\u0434\u0438\u0442\u0435 \u043A\u0430\u043C\u0435\u0440\u0443 \u043D\u0430 QR-\u043A\u043E\u0434";
|
|
2730
|
-
hint.style.color = "white";
|
|
2731
|
-
hint.style.marginTop = "30px";
|
|
2732
|
-
hint.style.fontSize = "17px";
|
|
2733
|
-
hint.style.opacity = "0.9";
|
|
2734
|
-
overlay.appendChild(hint);
|
|
2735
|
-
const canvas = document.createElement("canvas");
|
|
2736
|
-
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
2737
|
-
let stream = null;
|
|
2738
|
-
let rafId = null;
|
|
2739
|
-
let lastScanAt = 0;
|
|
2740
|
-
let closed = false;
|
|
2741
|
-
const finalize = async (result) => {
|
|
2742
|
-
if (closed) {
|
|
2743
|
-
return;
|
|
2744
|
-
}
|
|
2745
|
-
closed = true;
|
|
2746
|
-
if (rafId !== null) {
|
|
2747
|
-
cancelAnimationFrame(rafId);
|
|
2748
|
-
rafId = null;
|
|
2749
|
-
}
|
|
2750
|
-
if (stream) {
|
|
2751
|
-
stream.getTracks().forEach((track) => track.stop());
|
|
2752
|
-
stream = null;
|
|
2753
|
-
}
|
|
2754
|
-
video.srcObject = null;
|
|
2755
|
-
overlay.remove();
|
|
2756
|
-
styleTag.remove();
|
|
2757
|
-
document.body.style.overflow = prevOverflow;
|
|
2758
|
-
document.body.style.position = prevPos;
|
|
2759
|
-
document.body.style.width = prevWidth;
|
|
2760
|
-
document.body.style.touchAction = prevTouch;
|
|
2761
|
-
document.documentElement.style.overflow = prevHtmlOverflow;
|
|
2762
|
-
resolve(result);
|
|
2763
|
-
};
|
|
2764
|
-
const removeFromBag = this.registerDisposable(() => {
|
|
2765
|
-
void finalize(null);
|
|
2766
|
-
});
|
|
2767
|
-
const closeScanner = (result) => {
|
|
2768
|
-
void finalize(result);
|
|
2769
|
-
removeFromBag();
|
|
2770
|
-
};
|
|
2771
|
-
closeBtn.onclick = () => closeScanner(null);
|
|
2772
|
-
try {
|
|
2773
|
-
const constraints = [
|
|
2774
|
-
{ video: { facingMode: { ideal: "environment" } }, audio: false },
|
|
2775
|
-
{ video: { facingMode: "environment" }, audio: false },
|
|
2776
|
-
{ video: true, audio: false }
|
|
2777
|
-
];
|
|
2778
|
-
let lastError = null;
|
|
2779
|
-
for (const constraint of constraints) {
|
|
2780
|
-
try {
|
|
2781
|
-
stream = await navigator.mediaDevices.getUserMedia(constraint);
|
|
2782
|
-
break;
|
|
2783
|
-
} catch (error) {
|
|
2784
|
-
lastError = error;
|
|
2785
|
-
}
|
|
2786
|
-
}
|
|
2787
|
-
if (!stream) {
|
|
2788
|
-
throw lastError instanceof Error ? lastError : new Error("Unable to access camera");
|
|
2789
|
-
}
|
|
2790
|
-
video.srcObject = stream;
|
|
2791
|
-
await video.play();
|
|
2792
|
-
const scanFrame = (now) => {
|
|
2793
|
-
if (closed) {
|
|
2794
|
-
return;
|
|
2795
|
-
}
|
|
2796
|
-
if (now - lastScanAt >= 100 && context && video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
2797
|
-
const width = video.videoWidth;
|
|
2798
|
-
const height = video.videoHeight;
|
|
2799
|
-
if (width > 0 && height > 0) {
|
|
2800
|
-
canvas.width = width;
|
|
2801
|
-
canvas.height = height;
|
|
2802
|
-
context.drawImage(video, 0, 0, width, height);
|
|
2803
|
-
const imageData = context.getImageData(0, 0, width, height);
|
|
2804
|
-
const result = jsQR(imageData.data, width, height, {
|
|
2805
|
-
inversionAttempts: "attemptBoth"
|
|
2806
|
-
});
|
|
2807
|
-
if (result?.data) {
|
|
2808
|
-
closeScanner(result.data);
|
|
2809
|
-
return;
|
|
2810
|
-
}
|
|
2811
|
-
lastScanAt = now;
|
|
2812
|
-
}
|
|
2813
|
-
}
|
|
2814
|
-
rafId = requestAnimationFrame(scanFrame);
|
|
2815
|
-
};
|
|
2816
|
-
rafId = requestAnimationFrame(scanFrame);
|
|
2817
|
-
} catch (error) {
|
|
2818
|
-
console.error("QR Start error", error);
|
|
2819
|
-
closeScanner(null);
|
|
2820
|
-
}
|
|
2821
|
-
});
|
|
2822
|
-
}
|
|
2823
|
-
shareUrl(url, text) {
|
|
2824
|
-
if (navigator.share) {
|
|
2825
|
-
try {
|
|
2826
|
-
navigator.share({ title: text, text, url });
|
|
2827
|
-
return;
|
|
2828
|
-
} catch (err) {
|
|
2829
|
-
console.warn("Share cancelled or failed:", err);
|
|
2830
|
-
}
|
|
2831
|
-
}
|
|
2832
|
-
const payload = text ? `${text}
|
|
2833
|
-
${url}` : url;
|
|
2834
|
-
this.copyTextToClipboard(payload).catch((err) => {
|
|
2835
|
-
console.warn("Share fallback (clipboard) failed:", err);
|
|
2836
|
-
});
|
|
2837
|
-
}
|
|
2838
|
-
async addToHomeScreen() {
|
|
2839
|
-
const isAndroid = /android/i.test(navigator.userAgent);
|
|
2840
|
-
if (!isAndroid || !this.deferredPrompt) {
|
|
2841
|
-
return super.addToHomeScreen();
|
|
2842
|
-
}
|
|
2843
|
-
try {
|
|
2844
|
-
this.deferredPrompt.prompt();
|
|
2845
|
-
const choice = await this.deferredPrompt.userChoice;
|
|
2846
|
-
this.deferredPrompt = null;
|
|
2847
|
-
return choice?.outcome === "accepted";
|
|
2848
|
-
} catch (error) {
|
|
2849
|
-
console.warn("[tvm-app-adapter] Web addToHomeScreen failed:", error);
|
|
2850
|
-
this.deferredPrompt = null;
|
|
2851
|
-
return false;
|
|
2852
|
-
}
|
|
2853
|
-
}
|
|
2854
|
-
async checkHomeScreenStatus() {
|
|
2855
|
-
try {
|
|
2856
|
-
const isStandalone = typeof window !== "undefined" && window.matchMedia?.("(display-mode: standalone)").matches || // iOS Safari specific flag
|
|
2857
|
-
typeof navigator !== "undefined" && navigator.standalone === true;
|
|
2858
|
-
if (isStandalone) {
|
|
2859
|
-
return "added";
|
|
2860
|
-
}
|
|
2861
|
-
return "unknown";
|
|
2862
|
-
} catch {
|
|
2863
|
-
return "unknown";
|
|
2864
|
-
}
|
|
2865
|
-
}
|
|
2866
|
-
};
|
|
2867
|
-
|
|
2868
|
-
// src/adapters/index.ts
|
|
2869
|
-
var CONFIRMED_PLATFORM_STORAGE_KEY = "mini-app-adapter:confirmed-platform";
|
|
2870
|
-
var CONFIRMED_PLATFORM_TTL_MS = 30 * 60 * 1e3;
|
|
2871
|
-
function detectPlatform() {
|
|
2872
|
-
if (typeof window === "undefined") {
|
|
2873
|
-
return "web";
|
|
2874
|
-
}
|
|
2875
|
-
const searchParams = new URLSearchParams(window.location.search);
|
|
2876
|
-
const hashParams = (() => {
|
|
2877
|
-
const hash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
|
|
2878
|
-
return new URLSearchParams(hash);
|
|
2879
|
-
})();
|
|
2880
|
-
const getParam = (name) => searchParams.get(name) ?? hashParams.get(name);
|
|
2881
|
-
const hasParam = (...names) => names.some((name) => getParam(name));
|
|
2882
|
-
const readConfirmedPlatform = () => {
|
|
2883
|
-
try {
|
|
2884
|
-
const raw = window.sessionStorage.getItem(CONFIRMED_PLATFORM_STORAGE_KEY);
|
|
2885
|
-
if (!raw) {
|
|
2886
|
-
return null;
|
|
2887
|
-
}
|
|
2888
|
-
const parsed = JSON.parse(raw);
|
|
2889
|
-
if (!parsed?.platform || typeof parsed.ts !== "number") {
|
|
2890
|
-
return null;
|
|
2891
|
-
}
|
|
2892
|
-
if (Date.now() - parsed.ts > CONFIRMED_PLATFORM_TTL_MS) {
|
|
2893
|
-
return null;
|
|
2894
|
-
}
|
|
2895
|
-
return parsed.platform;
|
|
2896
|
-
} catch {
|
|
2897
|
-
return null;
|
|
2898
|
-
}
|
|
2899
|
-
};
|
|
2900
|
-
const persistConfirmedPlatform = (platform) => {
|
|
2901
|
-
if (platform === "web") {
|
|
2902
|
-
return;
|
|
2903
|
-
}
|
|
2904
|
-
try {
|
|
2905
|
-
window.sessionStorage.setItem(
|
|
2906
|
-
CONFIRMED_PLATFORM_STORAGE_KEY,
|
|
2907
|
-
JSON.stringify({ platform, ts: Date.now() })
|
|
2908
|
-
);
|
|
2909
|
-
} catch {
|
|
2910
|
-
}
|
|
2911
|
-
};
|
|
2912
|
-
const shellPlatform = readShellPlatform();
|
|
2913
|
-
if (shellPlatform) {
|
|
2914
|
-
persistConfirmedPlatform(shellPlatform);
|
|
2915
|
-
return shellPlatform;
|
|
2916
|
-
}
|
|
2917
|
-
const userAgent = navigator.userAgent.toLowerCase();
|
|
2918
|
-
const hasNativeBridge = typeof window.NativeBridge?.postMessage === "function";
|
|
2919
|
-
if (hasNativeBridge) {
|
|
2920
|
-
if (userAgent.includes("android")) {
|
|
2921
|
-
persistConfirmedPlatform("shell_android");
|
|
2922
|
-
return "shell_android";
|
|
2923
|
-
}
|
|
2924
|
-
persistConfirmedPlatform("shell_ios");
|
|
2925
|
-
return "shell_ios";
|
|
2926
|
-
}
|
|
2927
|
-
const telegramGlobals = window;
|
|
2928
|
-
const hasTelegramGlobal = Boolean(window.Telegram?.WebApp) || typeof telegramGlobals.TelegramWebviewProxy !== "undefined" || typeof telegramGlobals.TelegramGameProxy !== "undefined";
|
|
2929
|
-
const hasTelegramParams = hasParam("tgWebAppPlatform", "tgWebAppVersion", "tgWebAppData", "tgWebAppLanguage");
|
|
2930
|
-
if (hasTelegramGlobal || hasTelegramParams || userAgent.includes("telegram")) {
|
|
2931
|
-
persistConfirmedPlatform("telegram");
|
|
2932
|
-
return "telegram";
|
|
2933
|
-
}
|
|
2934
|
-
if (window.WebApp) {
|
|
2935
|
-
persistConfirmedPlatform("max");
|
|
2936
|
-
return "max";
|
|
2937
|
-
}
|
|
2938
|
-
if (window.MaxMiniApp) {
|
|
2939
|
-
persistConfirmedPlatform("max");
|
|
2940
|
-
return "max";
|
|
2941
|
-
}
|
|
2942
|
-
const hasVkParams = hasParam("vk_app_id", "vk_platform", "vk_user_id", "vk_language", "sign");
|
|
2943
|
-
const hasVkUserAgentSignal = userAgent.includes("vkclient") || userAgent.includes("vk-android") || userAgent.includes("vkontakte");
|
|
2944
|
-
if (hasVkParams || hasVkUserAgentSignal) {
|
|
2945
|
-
persistConfirmedPlatform("vk");
|
|
2946
|
-
return "vk";
|
|
2947
|
-
}
|
|
2948
|
-
const confirmedPlatform = readConfirmedPlatform();
|
|
2949
|
-
if (confirmedPlatform && confirmedPlatform !== "web") {
|
|
2950
|
-
return confirmedPlatform;
|
|
2951
|
-
}
|
|
2952
|
-
return "web";
|
|
2953
|
-
}
|
|
2954
|
-
function createAdapter(input) {
|
|
2955
|
-
const options = normalizeCreateAdapterOptions(input);
|
|
2956
|
-
const platform = options.platform ?? detectPlatform();
|
|
2957
|
-
if (platform === "vk") {
|
|
2958
|
-
setVkPixelCode(options.vk?.pixelCode ?? null);
|
|
2959
|
-
}
|
|
2960
|
-
switch (platform) {
|
|
2961
|
-
case "shell_ios":
|
|
2962
|
-
case "shell_android":
|
|
2963
|
-
return new ShellMiniAppAdapter(platform);
|
|
2964
|
-
case "telegram":
|
|
2965
|
-
return new TelegramMiniAppAdapter();
|
|
2966
|
-
case "vk":
|
|
2967
|
-
return new VKMiniAppAdapter();
|
|
2968
|
-
case "max":
|
|
2969
|
-
return new MaxMiniAppAdapter();
|
|
2970
|
-
default:
|
|
2971
|
-
return new WebMiniAppAdapter();
|
|
2972
|
-
}
|
|
2973
|
-
}
|
|
2974
|
-
function normalizeCreateAdapterOptions(input) {
|
|
2975
|
-
if (!input) {
|
|
2976
|
-
return {};
|
|
2977
|
-
}
|
|
2978
|
-
if (typeof input === "string") {
|
|
2979
|
-
return { platform: input };
|
|
2980
|
-
}
|
|
2981
|
-
return input;
|
|
2982
|
-
}
|
|
2983
|
-
|
|
2984
|
-
// src/components/AdapterProvider.tsx
|
|
2985
|
-
import { createContext, useContext, useEffect, useMemo } from "react";
|
|
2986
|
-
|
|
2987
|
-
// src/registry.ts
|
|
2988
|
-
var currentAdapter = null;
|
|
2989
|
-
function setActiveAdapter(adapter) {
|
|
2990
|
-
currentAdapter = adapter;
|
|
2991
|
-
}
|
|
2992
|
-
function getActiveAdapter() {
|
|
2993
|
-
return currentAdapter;
|
|
2994
|
-
}
|
|
2995
|
-
|
|
2996
|
-
// src/components/AdapterProvider.tsx
|
|
2997
|
-
import { jsx } from "react/jsx-runtime";
|
|
2998
|
-
var AdapterContext = createContext(null);
|
|
2999
|
-
function AdapterProvider({ adapter, children }) {
|
|
3000
|
-
const proxiedAdapter = useMemo(() => {
|
|
3001
|
-
return new Proxy(adapter, {
|
|
3002
|
-
get(target, prop, receiver) {
|
|
3003
|
-
const value = Reflect.get(target, prop, receiver);
|
|
3004
|
-
if (typeof value === "function") {
|
|
3005
|
-
return value.bind(target);
|
|
3006
|
-
}
|
|
3007
|
-
return value;
|
|
3008
|
-
}
|
|
3009
|
-
});
|
|
3010
|
-
}, [adapter]);
|
|
3011
|
-
useEffect(() => {
|
|
3012
|
-
setActiveAdapter(proxiedAdapter);
|
|
3013
|
-
return () => {
|
|
3014
|
-
setActiveAdapter(null);
|
|
3015
|
-
if (typeof proxiedAdapter.destroy === "function") {
|
|
3016
|
-
try {
|
|
3017
|
-
proxiedAdapter.destroy();
|
|
3018
|
-
} catch (error) {
|
|
3019
|
-
console.warn("[tvm-app-adapter] adapter destroy failed:", error);
|
|
3020
|
-
}
|
|
3021
|
-
}
|
|
3022
|
-
};
|
|
3023
|
-
}, [proxiedAdapter]);
|
|
3024
|
-
return /* @__PURE__ */ jsx(AdapterContext.Provider, { value: proxiedAdapter, children });
|
|
3025
|
-
}
|
|
3026
|
-
function useMiniAppAdapter() {
|
|
3027
|
-
const adapter = useContext(AdapterContext);
|
|
3028
|
-
if (!adapter) {
|
|
3029
|
-
throw new Error("useMiniAppAdapter must be used inside <AdapterProvider/>.");
|
|
3030
|
-
}
|
|
3031
|
-
return adapter;
|
|
3032
|
-
}
|
|
3033
|
-
|
|
3034
|
-
// src/hooks/useAdapterTheme.ts
|
|
3035
|
-
import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useState } from "react";
|
|
3036
|
-
var STORAGE_KEY = "loyalka-theme-preference";
|
|
3037
|
-
function useAdapterTheme() {
|
|
3038
|
-
const adapter = useMiniAppAdapter();
|
|
3039
|
-
const [systemPrefersDark, setSystemPrefersDark] = useState(() => {
|
|
3040
|
-
const { appearance: appearance2 } = adapter.getEnvironment();
|
|
3041
|
-
if (adapter.onAppearanceChange && appearance2) {
|
|
3042
|
-
return appearance2 === "dark";
|
|
3043
|
-
}
|
|
3044
|
-
if (typeof window !== "undefined" && window.matchMedia) {
|
|
3045
|
-
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
3046
|
-
}
|
|
3047
|
-
return false;
|
|
3048
|
-
});
|
|
3049
|
-
const [preference, setPreferenceState] = useState(() => {
|
|
3050
|
-
try {
|
|
3051
|
-
const saved = localStorage.getItem(STORAGE_KEY);
|
|
3052
|
-
if (saved === "light" || saved === "dark" || saved === "system") return saved;
|
|
3053
|
-
} catch {
|
|
3054
|
-
}
|
|
3055
|
-
return "system";
|
|
3056
|
-
});
|
|
3057
|
-
useEffect2(() => {
|
|
3058
|
-
if (adapter.onAppearanceChange) {
|
|
3059
|
-
return adapter.onAppearanceChange((appearance2) => {
|
|
3060
|
-
if (appearance2) {
|
|
3061
|
-
setSystemPrefersDark(appearance2 === "dark");
|
|
3062
|
-
}
|
|
3063
|
-
});
|
|
3064
|
-
}
|
|
3065
|
-
if (typeof window !== "undefined" && window.matchMedia) {
|
|
3066
|
-
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
3067
|
-
const handleChange = (event) => setSystemPrefersDark(event.matches);
|
|
3068
|
-
media.addEventListener("change", handleChange);
|
|
3069
|
-
return () => media.removeEventListener("change", handleChange);
|
|
3070
|
-
}
|
|
3071
|
-
return void 0;
|
|
3072
|
-
}, [adapter]);
|
|
3073
|
-
const isDark = useMemo2(() => {
|
|
3074
|
-
if (preference === "dark" || preference === "system" && systemPrefersDark) {
|
|
3075
|
-
return true;
|
|
3076
|
-
}
|
|
3077
|
-
return false;
|
|
3078
|
-
}, [preference, systemPrefersDark]);
|
|
3079
|
-
const appearance = isDark ? "dark" : "light";
|
|
3080
|
-
useEffect2(() => {
|
|
3081
|
-
const root = document.documentElement;
|
|
3082
|
-
if (isDark) {
|
|
3083
|
-
root.classList.add("dark");
|
|
3084
|
-
} else {
|
|
3085
|
-
root.classList.remove("dark");
|
|
3086
|
-
}
|
|
3087
|
-
}, [isDark]);
|
|
3088
|
-
const setPreference = useCallback((pref) => {
|
|
3089
|
-
setPreferenceState(pref);
|
|
3090
|
-
try {
|
|
3091
|
-
localStorage.setItem(STORAGE_KEY, pref);
|
|
3092
|
-
} catch {
|
|
3093
|
-
}
|
|
3094
|
-
}, []);
|
|
3095
|
-
const toggle = useCallback(() => {
|
|
3096
|
-
document.documentElement.classList.add("theme-transition");
|
|
3097
|
-
setTimeout(() => {
|
|
3098
|
-
setTimeout(() => {
|
|
3099
|
-
document.documentElement.classList.remove("theme-transition");
|
|
3100
|
-
}, 400);
|
|
3101
|
-
}, 50);
|
|
3102
|
-
const root = document.documentElement;
|
|
3103
|
-
const styles = getComputedStyle(root);
|
|
3104
|
-
const primaryColor = styles.getPropertyValue("--primary").trim();
|
|
3105
|
-
const backgroundColor = styles.getPropertyValue("--background").trim();
|
|
3106
|
-
adapter.setColors({
|
|
3107
|
-
header: primaryColor,
|
|
3108
|
-
background: backgroundColor,
|
|
3109
|
-
footer: backgroundColor
|
|
3110
|
-
});
|
|
3111
|
-
setPreference(preference === "dark" ? "light" : "dark");
|
|
3112
|
-
}, [adapter, preference, setPreference]);
|
|
3113
|
-
return {
|
|
3114
|
-
isDark,
|
|
3115
|
-
appearance,
|
|
3116
|
-
preference,
|
|
3117
|
-
setPreference,
|
|
3118
|
-
toggle
|
|
3119
|
-
};
|
|
3120
|
-
}
|
|
3121
|
-
|
|
3122
|
-
// src/hooks/useSafeArea.ts
|
|
3123
|
-
import { useCallback as useCallback2, useEffect as useEffect3, useState as useState2 } from "react";
|
|
3124
|
-
var ZERO_SAFE_AREA = {
|
|
3125
|
-
top: 0,
|
|
3126
|
-
right: 0,
|
|
3127
|
-
bottom: 0,
|
|
3128
|
-
left: 0
|
|
3129
|
-
};
|
|
3130
|
-
function useSafeArea() {
|
|
3131
|
-
const adapter = useMiniAppAdapter();
|
|
3132
|
-
const computeSafeArea = useCallback2(
|
|
3133
|
-
() => adapter.computeSafeArea() ?? ZERO_SAFE_AREA,
|
|
3134
|
-
[adapter]
|
|
3135
|
-
);
|
|
3136
|
-
const [safeArea, setSafeArea] = useState2(computeSafeArea);
|
|
3137
|
-
useEffect3(() => {
|
|
3138
|
-
setSafeArea(computeSafeArea());
|
|
3139
|
-
const unsubscribe = adapter.subscribe?.(() => {
|
|
3140
|
-
setSafeArea(computeSafeArea());
|
|
3141
|
-
});
|
|
3142
|
-
return () => unsubscribe?.();
|
|
3143
|
-
}, [adapter, computeSafeArea]);
|
|
3144
|
-
return safeArea ?? ZERO_SAFE_AREA;
|
|
3145
|
-
}
|
|
3146
|
-
|
|
3147
|
-
// src/platform.ts
|
|
3148
|
-
var cachedPlatform = null;
|
|
3149
|
-
function getPlatform() {
|
|
3150
|
-
const adapter = getActiveAdapter();
|
|
3151
|
-
if (adapter) {
|
|
3152
|
-
if (adapter.platform !== "web") {
|
|
3153
|
-
cachedPlatform = adapter.platform;
|
|
3154
|
-
}
|
|
3155
|
-
return adapter.platform;
|
|
3156
|
-
}
|
|
3157
|
-
if (cachedPlatform && cachedPlatform !== "web") {
|
|
3158
|
-
return cachedPlatform;
|
|
3159
|
-
}
|
|
3160
|
-
const detectedPlatform = detectPlatform();
|
|
3161
|
-
if (detectedPlatform !== "web") {
|
|
3162
|
-
cachedPlatform = detectedPlatform;
|
|
3163
|
-
}
|
|
3164
|
-
return detectedPlatform;
|
|
3165
|
-
}
|
|
3166
|
-
|
|
3167
|
-
// src/analytics.ts
|
|
3168
|
-
function configureVkPixel(pixelCode2) {
|
|
3169
|
-
setVkPixelCode(pixelCode2);
|
|
3170
|
-
}
|
|
3171
|
-
function trackConversionEvent(event, payload) {
|
|
3172
|
-
const adapter = resolveVkAdapter();
|
|
3173
|
-
adapter?.trackConversionEvent(event, payload);
|
|
3174
|
-
}
|
|
3175
|
-
function trackPixelEvent(event, payload) {
|
|
3176
|
-
const adapter = resolveVkAdapter();
|
|
3177
|
-
adapter?.trackPixelEvent(event, payload);
|
|
3178
|
-
}
|
|
3179
|
-
function resolveVkAdapter() {
|
|
3180
|
-
const adapter = getActiveAdapter();
|
|
3181
|
-
if (adapter) {
|
|
3182
|
-
return adapter.platform === "vk" ? adapter : null;
|
|
3183
|
-
}
|
|
3184
|
-
if (getPlatform() !== "vk") {
|
|
3185
|
-
return null;
|
|
3186
|
-
}
|
|
3187
|
-
return null;
|
|
3188
|
-
}
|
|
3189
|
-
export {
|
|
3190
|
-
AdapterProvider,
|
|
3191
|
-
BaseMiniAppAdapter,
|
|
3192
|
-
MaxMiniAppAdapter,
|
|
3193
|
-
ShellMiniAppAdapter,
|
|
3194
|
-
TelegramMiniAppAdapter,
|
|
3195
|
-
VKMiniAppAdapter,
|
|
3196
|
-
WebMiniAppAdapter,
|
|
3197
|
-
configureVkPixel,
|
|
3198
|
-
createAdapter,
|
|
3199
|
-
createShellAPI,
|
|
3200
|
-
detectPlatform,
|
|
3201
|
-
getActiveAdapter,
|
|
3202
|
-
getPlatform,
|
|
3203
|
-
isShell,
|
|
3204
|
-
isShellAndroid,
|
|
3205
|
-
isShellIOS,
|
|
3206
|
-
readShellPlatform,
|
|
3207
|
-
requestShellPushPermission,
|
|
3208
|
-
shell,
|
|
3209
|
-
storeShellToken,
|
|
3210
|
-
trackConversionEvent,
|
|
3211
|
-
trackPixelEvent,
|
|
3212
|
-
useAdapterTheme,
|
|
3213
|
-
useMiniAppAdapter,
|
|
3214
|
-
useSafeArea
|
|
3215
|
-
};
|
|
3216
|
-
//# sourceMappingURL=index.js.map
|
|
9
|
+
`,document.head.appendChild(ae);let I=document.createElement("div");I.innerText="\u041D\u0430\u0432\u0435\u0434\u0438\u0442\u0435 \u043A\u0430\u043C\u0435\u0440\u0443 \u043D\u0430 QR-\u043A\u043E\u0434",I.style.color="white",I.style.fontSize="17px",I.style.opacity="0.9",a.appendChild(I);let w=document.createElement("button");w.innerText="\u0417\u0430\u043A\u0440\u044B\u0442\u044C",w.style.minWidth=`${Math.min(h,220)}px`,w.style.height="48px",w.style.padding="0 20px",w.style.border="1px solid rgba(255,255,255,0.24)",w.style.borderRadius="14px",w.style.background="rgba(255,255,255,0.12)",w.style.color="white",w.style.fontSize="16px",w.style.fontWeight="600",w.style.cursor="pointer",w.style.backdropFilter="blur(8px)",a.appendChild(w);let se=document.createElement("canvas"),pe=se.getContext("2d",{willReadFrequently:!0}),B=null,W=null,Be=0,de=!1,Ee=async E=>{de||(de=!0,W!==null&&(cancelAnimationFrame(W),W=null),B&&(B.getTracks().forEach(H=>H.stop()),B=null),m.srcObject=null,a.remove(),ae.remove(),document.body.style.overflow=n,document.body.style.position=i,document.body.style.width=p,document.body.style.touchAction=s,document.documentElement.style.overflow=d,t(E))},mt=this.registerDisposable(()=>{Ee(null)}),Y=E=>{Ee(E),mt()};l.onclick=()=>Y(null),w.onclick=()=>Y(null);try{let E=[{video:{facingMode:{ideal:"environment"},width:{ideal:1920},height:{ideal:1080},aspectRatio:{ideal:1.7777777778}},audio:!1},{video:{facingMode:"environment",width:{ideal:1280},height:{ideal:720}},audio:!1},{video:!0,audio:!1}],H=null;for(let T of E)try{B=await navigator.mediaDevices.getUserMedia(T);break}catch(L){H=L}if(!B)throw H instanceof Error?H:new Error("Unable to access camera");let[Le]=B.getVideoTracks();if(Le)try{let T=[{focusMode:"continuous"},{exposureMode:"continuous"},{whiteBalanceMode:"continuous"}];await Le.applyConstraints({advanced:T})}catch{}m.srcObject=B,await m.play();let Te=T=>{if(!de){if(T-Be>=80&&pe&&m.readyState>=HTMLMediaElement.HAVE_CURRENT_DATA){let L=m.videoWidth,O=m.videoHeight;if(L>0&&O>0){se.width=L,se.height=O,pe.drawImage(m,0,0,L,O);let ht=pe.getImageData(0,0,L,O),_e=It(ht.data,L,O,{inversionAttempts:"attemptBoth"});if(_e?.data){Y(_e.data);return}Be=T}}W=requestAnimationFrame(Te)}};W=requestAnimationFrame(Te)}catch(E){console.error("QR Start error",E),Y(null)}})}shareUrl(e,t){if(navigator.share)try{navigator.share({title:t,text:t,url:e});return}catch(i){console.warn("Share cancelled or failed:",i)}let n=t?`${t}
|
|
10
|
+
${e}`:e;this.copyTextToClipboard(n).catch(i=>{console.warn("Share fallback (clipboard) failed:",i)})}async addToHomeScreen(){if(!/android/i.test(navigator.userAgent)||!this.deferredPrompt)return super.addToHomeScreen();try{this.deferredPrompt.prompt();let t=await this.deferredPrompt.userChoice;return this.deferredPrompt=null,t?.outcome==="accepted"}catch(t){return console.warn("[tvm-app-adapter] Web addToHomeScreen failed:",t),this.deferredPrompt=null,!1}}async checkHomeScreenStatus(){try{return typeof window<"u"&&window.matchMedia?.("(display-mode: standalone)").matches||typeof navigator<"u"&&navigator.standalone===!0?"added":"unknown"}catch{return"unknown"}}};var ot="mini-app-adapter:confirmed-platform",Wt=1800*1e3;function oe(){if(typeof window>"u")return"web";let o=new URLSearchParams(window.location.search),r=(()=>{let v=window.location.hash.startsWith("#")?window.location.hash.slice(1):window.location.hash;return new URLSearchParams(v)})(),e=v=>o.get(v)??r.get(v),t=(...v)=>v.some(y=>e(y)),n=()=>{try{let v=window.sessionStorage.getItem(ot);if(!v)return null;let y=JSON.parse(v);return!y?.platform||typeof y.ts!="number"||Date.now()-y.ts>Wt?null:y.platform}catch{return null}},i=v=>{if(v!=="web")try{window.sessionStorage.setItem(ot,JSON.stringify({platform:v,ts:Date.now()}))}catch{}},s=ee();if(s)return i(s),s;let p=navigator.userAgent.toLowerCase();if(typeof window.NativeBridge?.postMessage=="function")return p.includes("android")?(i("shell_android"),"shell_android"):(i("shell_ios"),"shell_ios");let a=window,l=!!window.Telegram?.WebApp||typeof a.TelegramWebviewProxy<"u"||typeof a.TelegramGameProxy<"u",h=t("tgWebAppPlatform","tgWebAppVersion","tgWebAppData","tgWebAppLanguage");if(l||h||p.includes("telegram"))return i("telegram"),"telegram";if(window.WebApp||window.MaxMiniApp)return i("max"),"max";let u=t("vk_app_id","vk_platform","vk_user_id","vk_language","sign"),b=p.includes("vkclient")||p.includes("vk-android")||p.includes("vkontakte");if(u||b)return i("vk"),"vk";let m=n();return m&&m!=="web"?m:"web"}function Ht(o){let r=Ot(o),e=r.platform??oe();switch(e==="vk"&&ie(r.vk?.pixelCode??null),e){case"shell_ios":case"shell_android":return new K(e);case"telegram":return new z;case"vk":return new U;case"max":return new q;default:return new Q}}function Ot(o){return o?typeof o=="string"?{platform:o}:o:{}}import{createContext as Nt,useContext as Dt,useEffect as qt,useMemo as Kt}from"react";var at=null;function Ve(o){at=o}function j(){return at}import{jsx as Ut}from"react/jsx-runtime";var st=Nt(null);function zt({adapter:o,children:r}){let e=Kt(()=>new Proxy(o,{get(t,n,i){let s=Reflect.get(t,n,i);return typeof s=="function"?s.bind(t):s}}),[o]);return qt(()=>(Ve(e),()=>{if(Ve(null),typeof e.destroy=="function")try{e.destroy()}catch(t){console.warn("[tvm-app-adapter] adapter destroy failed:",t)}}),[e]),Ut(st.Provider,{value:e,children:r})}function G(){let o=Dt(st);if(!o)throw new Error("useMiniAppAdapter must be used inside <AdapterProvider/>.");return o}import{useCallback as pt,useEffect as dt,useMemo as Qt,useState as lt}from"react";var ct="loyalka-theme-preference";function jt(){let o=G(),[r,e]=lt(()=>{let{appearance:a}=o.getEnvironment();return o.onAppearanceChange&&a?a==="dark":typeof window<"u"&&window.matchMedia?window.matchMedia("(prefers-color-scheme: dark)").matches:!1}),[t,n]=lt(()=>{try{let a=localStorage.getItem(ct);if(a==="light"||a==="dark"||a==="system")return a}catch{}return"system"});dt(()=>{if(o.onAppearanceChange)return o.onAppearanceChange(a=>{a&&e(a==="dark")});if(typeof window<"u"&&window.matchMedia){let a=window.matchMedia("(prefers-color-scheme: dark)"),l=h=>e(h.matches);return a.addEventListener("change",l),()=>a.removeEventListener("change",l)}},[o]);let i=Qt(()=>!!(t==="dark"||t==="system"&&r),[t,r]),s=i?"dark":"light";dt(()=>{let a=document.documentElement;i?a.classList.add("dark"):a.classList.remove("dark")},[i]);let p=pt(a=>{n(a);try{localStorage.setItem(ct,a)}catch{}},[]),d=pt(()=>{document.documentElement.classList.add("theme-transition"),setTimeout(()=>{setTimeout(()=>{document.documentElement.classList.remove("theme-transition")},400)},50);let a=document.documentElement,l=getComputedStyle(a),h=l.getPropertyValue("--primary").trim(),u=l.getPropertyValue("--background").trim();o.setColors({header:h,background:u,footer:u}),p(t==="dark"?"light":"dark")},[o,t,p]);return{isDark:i,appearance:s,preference:t,setPreference:p,toggle:d}}import{useCallback as Gt,useEffect as $t,useState as Yt}from"react";var ut={top:0,right:0,bottom:0,left:0};function Xt(){let o=G(),r=Gt(()=>o.computeSafeArea()??ut,[o]),[e,t]=Yt(r);return $t(()=>{t(r());let n=o.subscribe?.(()=>{t(r())});return()=>n?.()},[o,r]),e??ut}var $=null;function xe(){let o=j();if(o)return o.platform!=="web"&&($=o.platform),o.platform;if($&&$!=="web")return $;let r=oe();return r!=="web"&&($=r),r}function Jt(o){ie(o)}function Zt(o,r){ft()?.trackConversionEvent(o,r)}function en(o,r){ft()?.trackPixelEvent(o,r)}function ft(){let o=j();return o?o.platform==="vk"?o:null:(xe()!=="vk",null)}export{zt as AdapterProvider,A as BaseMiniAppAdapter,q as MaxMiniAppAdapter,K as ShellMiniAppAdapter,z as TelegramMiniAppAdapter,U as VKMiniAppAdapter,Q as WebMiniAppAdapter,Jt as configureVkPixel,Ht as createAdapter,te as createShellAPI,oe as detectPlatform,j as getActiveAdapter,xe as getPlatform,De as isShell,St as isShellAndroid,Pt as isShellIOS,ee as readShellPlatform,ge as requestShellPushPermission,Mt as shell,Ct as storeShellToken,Zt as trackConversionEvent,en as trackPixelEvent,jt as useAdapterTheme,G as useMiniAppAdapter,Xt as useSafeArea};
|