@wowlabtech/mini-app-adapter 0.1.0
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 +98 -0
- package/dist/index.cjs +2646 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +511 -0
- package/dist/index.d.ts +511 -0
- package/dist/index.js +2616 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2646 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
31
|
+
|
|
32
|
+
// src/index.ts
|
|
33
|
+
var index_exports = {};
|
|
34
|
+
__export(index_exports, {
|
|
35
|
+
AdapterProvider: () => AdapterProvider,
|
|
36
|
+
BaseMiniAppAdapter: () => BaseMiniAppAdapter,
|
|
37
|
+
MaxMiniAppAdapter: () => MaxMiniAppAdapter,
|
|
38
|
+
ShellMiniAppAdapter: () => ShellMiniAppAdapter,
|
|
39
|
+
TelegramMiniAppAdapter: () => TelegramMiniAppAdapter,
|
|
40
|
+
VKMiniAppAdapter: () => VKMiniAppAdapter,
|
|
41
|
+
WebMiniAppAdapter: () => WebMiniAppAdapter,
|
|
42
|
+
configureVkPixel: () => configureVkPixel,
|
|
43
|
+
createAdapter: () => createAdapter,
|
|
44
|
+
createShellAPI: () => createShellAPI,
|
|
45
|
+
detectPlatform: () => detectPlatform,
|
|
46
|
+
getActiveAdapter: () => getActiveAdapter,
|
|
47
|
+
getPlatform: () => getPlatform,
|
|
48
|
+
isShell: () => isShell,
|
|
49
|
+
isShellAndroid: () => isShellAndroid,
|
|
50
|
+
isShellIOS: () => isShellIOS,
|
|
51
|
+
readShellPlatform: () => readShellPlatform,
|
|
52
|
+
requestShellPushPermission: () => requestShellPushPermission,
|
|
53
|
+
shell: () => shell,
|
|
54
|
+
storeShellToken: () => storeShellToken,
|
|
55
|
+
trackConversionEvent: () => trackConversionEvent,
|
|
56
|
+
trackPixelEvent: () => trackPixelEvent,
|
|
57
|
+
useAdapterTheme: () => useAdapterTheme,
|
|
58
|
+
useMiniAppAdapter: () => useMiniAppAdapter,
|
|
59
|
+
useSafeArea: () => useSafeArea
|
|
60
|
+
});
|
|
61
|
+
module.exports = __toCommonJS(index_exports);
|
|
62
|
+
|
|
63
|
+
// src/lib/disposables.ts
|
|
64
|
+
var DisposableBag = class {
|
|
65
|
+
constructor() {
|
|
66
|
+
__publicField(this, "disposers", /* @__PURE__ */ new Set());
|
|
67
|
+
}
|
|
68
|
+
add(disposable) {
|
|
69
|
+
if (!disposable) {
|
|
70
|
+
return () => {
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const dispose = typeof disposable === "function" ? disposable : typeof disposable.dispose === "function" ? disposable.dispose.bind(disposable) : void 0;
|
|
74
|
+
if (!dispose) {
|
|
75
|
+
return () => {
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
let called = false;
|
|
79
|
+
const wrapped = () => {
|
|
80
|
+
if (called) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
called = true;
|
|
84
|
+
try {
|
|
85
|
+
dispose();
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.warn("[tvm-app-adapter] disposable failed:", error);
|
|
88
|
+
} finally {
|
|
89
|
+
this.disposers.delete(wrapped);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
this.disposers.add(wrapped);
|
|
93
|
+
return wrapped;
|
|
94
|
+
}
|
|
95
|
+
disposeAll() {
|
|
96
|
+
for (const disposer of Array.from(this.disposers)) {
|
|
97
|
+
try {
|
|
98
|
+
disposer();
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.warn("[tvm-app-adapter] disposeAll failed:", error);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
this.disposers.clear();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// src/lib/download.ts
|
|
108
|
+
var isClient = typeof window !== "undefined" && typeof document !== "undefined";
|
|
109
|
+
function getFallbackFileName(url, provided) {
|
|
110
|
+
if (provided && provided.trim().length > 0) {
|
|
111
|
+
return provided;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const parsed = new URL(url);
|
|
115
|
+
const path = parsed.pathname.split("/").filter(Boolean).pop();
|
|
116
|
+
if (path) {
|
|
117
|
+
return path;
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
return "download";
|
|
122
|
+
}
|
|
123
|
+
function triggerAnchorDownload(href, fileName, { targetBlank }) {
|
|
124
|
+
if (!isClient) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const anchor = document.createElement("a");
|
|
128
|
+
anchor.href = href;
|
|
129
|
+
anchor.download = fileName;
|
|
130
|
+
anchor.rel = "noopener noreferrer";
|
|
131
|
+
if (targetBlank) {
|
|
132
|
+
anchor.target = "_blank";
|
|
133
|
+
}
|
|
134
|
+
document.body.appendChild(anchor);
|
|
135
|
+
anchor.click();
|
|
136
|
+
document.body.removeChild(anchor);
|
|
137
|
+
}
|
|
138
|
+
async function triggerFileDownload(url, fileName, options) {
|
|
139
|
+
if (!url) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const safeFileName = getFallbackFileName(url, fileName);
|
|
143
|
+
const preferBlob = options?.preferBlob ?? false;
|
|
144
|
+
if (!isClient) {
|
|
145
|
+
if (typeof window !== "undefined") {
|
|
146
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (preferBlob && typeof fetch === "function") {
|
|
151
|
+
try {
|
|
152
|
+
const response = await fetch(url, { credentials: "include" });
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
throw new Error(`Failed to download file: ${response.status}`);
|
|
155
|
+
}
|
|
156
|
+
const blob = await response.blob();
|
|
157
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
158
|
+
triggerAnchorDownload(objectUrl, safeFileName, { targetBlank: false });
|
|
159
|
+
setTimeout(() => URL.revokeObjectURL(objectUrl), 3e4);
|
|
160
|
+
return;
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.warn("[tvm-app-adapter] blob download failed, falling back to direct link:", error);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
triggerAnchorDownload(url, safeFileName, { targetBlank: true });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/lib/safeArea.ts
|
|
169
|
+
var SAFE_AREA_EDGES = ["top", "right", "bottom", "left"];
|
|
170
|
+
var DEFAULT_SAFE_AREA = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
171
|
+
function cloneSafeArea(source) {
|
|
172
|
+
return { ...source };
|
|
173
|
+
}
|
|
174
|
+
function addSafeArea(target, source) {
|
|
175
|
+
if (!source) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
for (const edge of SAFE_AREA_EDGES) {
|
|
179
|
+
const value = source[edge];
|
|
180
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
181
|
+
target[edge] += value;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function applyMinimum(target, source) {
|
|
186
|
+
if (!source) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
for (const edge of SAFE_AREA_EDGES) {
|
|
190
|
+
const value = source[edge];
|
|
191
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
192
|
+
target[edge] = Math.max(target[edge], value);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function areSafeAreasEqual(a, b) {
|
|
197
|
+
if (!a || !b) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
return SAFE_AREA_EDGES.every((edge) => a[edge] === b[edge]);
|
|
201
|
+
}
|
|
202
|
+
function computeCombinedSafeArea(options = {}) {
|
|
203
|
+
const safeArea = cloneSafeArea(DEFAULT_SAFE_AREA);
|
|
204
|
+
addSafeArea(safeArea, options.environment);
|
|
205
|
+
if (options.viewport) {
|
|
206
|
+
addSafeArea(safeArea, options.viewport.safeArea);
|
|
207
|
+
addSafeArea(safeArea, options.viewport.contentSafeArea);
|
|
208
|
+
}
|
|
209
|
+
addSafeArea(safeArea, options.additions);
|
|
210
|
+
applyMinimum(safeArea, options.css);
|
|
211
|
+
applyMinimum(safeArea, options.minimum);
|
|
212
|
+
return safeArea;
|
|
213
|
+
}
|
|
214
|
+
function readCssSafeArea() {
|
|
215
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
216
|
+
return void 0;
|
|
217
|
+
}
|
|
218
|
+
const styles = getComputedStyle(document.documentElement);
|
|
219
|
+
const readEdge = (prop) => {
|
|
220
|
+
const value = parseFloat(styles.getPropertyValue(prop));
|
|
221
|
+
return Number.isFinite(value) ? value : 0;
|
|
222
|
+
};
|
|
223
|
+
const top = readEdge("--safe-area-inset-top");
|
|
224
|
+
const right = readEdge("--safe-area-inset-right");
|
|
225
|
+
const bottom = readEdge("--safe-area-inset-bottom");
|
|
226
|
+
const left = readEdge("--safe-area-inset-left");
|
|
227
|
+
if (top || right || bottom || left) {
|
|
228
|
+
return { top, right, bottom, left };
|
|
229
|
+
}
|
|
230
|
+
return void 0;
|
|
231
|
+
}
|
|
232
|
+
function normalizeSafeArea(value) {
|
|
233
|
+
if (!value) {
|
|
234
|
+
return void 0;
|
|
235
|
+
}
|
|
236
|
+
const safeArea = { ...DEFAULT_SAFE_AREA };
|
|
237
|
+
addSafeArea(safeArea, value);
|
|
238
|
+
return safeArea;
|
|
239
|
+
}
|
|
240
|
+
function createSafeAreaWatcher(options) {
|
|
241
|
+
const targetWindow = options.windowObj ?? (typeof window !== "undefined" ? window : void 0);
|
|
242
|
+
if (!targetWindow) {
|
|
243
|
+
return void 0;
|
|
244
|
+
}
|
|
245
|
+
let previous = normalizeSafeArea(options.getSafeArea());
|
|
246
|
+
if (previous) {
|
|
247
|
+
options.onChange(previous);
|
|
248
|
+
previous = cloneSafeArea(previous);
|
|
249
|
+
}
|
|
250
|
+
const handler = () => {
|
|
251
|
+
const next = normalizeSafeArea(options.getSafeArea());
|
|
252
|
+
if (!next) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (!previous || !areSafeAreasEqual(previous, next)) {
|
|
256
|
+
options.onChange(next);
|
|
257
|
+
previous = cloneSafeArea(next);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
handler();
|
|
261
|
+
const events = options.events ?? ["resize", "orientationchange"];
|
|
262
|
+
for (const eventName of events) {
|
|
263
|
+
targetWindow.addEventListener(eventName, handler);
|
|
264
|
+
}
|
|
265
|
+
return () => {
|
|
266
|
+
for (const eventName of events) {
|
|
267
|
+
targetWindow.removeEventListener(eventName, handler);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/lib/shell.ts
|
|
273
|
+
var SHELL_PLATFORMS = ["shell_ios", "shell_android"];
|
|
274
|
+
var SHELL_QR_TIMEOUT_MS = 6e4;
|
|
275
|
+
var pushListeners = /* @__PURE__ */ new Set();
|
|
276
|
+
var deepLinkListeners = /* @__PURE__ */ new Set();
|
|
277
|
+
var activeListeners = /* @__PURE__ */ new Set();
|
|
278
|
+
var backgroundListeners = /* @__PURE__ */ new Set();
|
|
279
|
+
var lastPushToken = null;
|
|
280
|
+
var pendingQrRequest = null;
|
|
281
|
+
var DEFAULT_BRIDGE_CONFIG = {
|
|
282
|
+
platformFlag: "nativePlatform",
|
|
283
|
+
pushTokenCallback: "nativePushToken",
|
|
284
|
+
qrResultCallback: "nativeQRResult",
|
|
285
|
+
deepLinkCallback: "nativeDeepLink",
|
|
286
|
+
appActiveCallback: "nativeAppActive",
|
|
287
|
+
appBackgroundCallback: "nativeAppBackground"
|
|
288
|
+
};
|
|
289
|
+
var bridgeConfig = { ...DEFAULT_BRIDGE_CONFIG };
|
|
290
|
+
var installedCallbackNames = {};
|
|
291
|
+
function notifyPushListeners(token) {
|
|
292
|
+
lastPushToken = token;
|
|
293
|
+
for (const listener of pushListeners) {
|
|
294
|
+
try {
|
|
295
|
+
listener(token);
|
|
296
|
+
} catch (error) {
|
|
297
|
+
console.warn("[tvm-app-adapter] push token listener failed:", error);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function isShell(platform) {
|
|
302
|
+
return SHELL_PLATFORMS.includes(platform);
|
|
303
|
+
}
|
|
304
|
+
function isShellIOS(platform) {
|
|
305
|
+
return platform === "shell_ios";
|
|
306
|
+
}
|
|
307
|
+
function isShellAndroid(platform) {
|
|
308
|
+
return platform === "shell_android";
|
|
309
|
+
}
|
|
310
|
+
function readShellPlatform() {
|
|
311
|
+
const shellWindow = getShellWindow();
|
|
312
|
+
if (!shellWindow) {
|
|
313
|
+
return void 0;
|
|
314
|
+
}
|
|
315
|
+
const platform = shellWindow[bridgeConfig.platformFlag];
|
|
316
|
+
if (platform === "shell_ios" || platform === "shell_android") {
|
|
317
|
+
return platform;
|
|
318
|
+
}
|
|
319
|
+
return void 0;
|
|
320
|
+
}
|
|
321
|
+
function createShellAPI(platform) {
|
|
322
|
+
installGlobalCallbacks();
|
|
323
|
+
const resolvePlatform = typeof platform === "function" ? platform : () => platform;
|
|
324
|
+
return {
|
|
325
|
+
async openNativeQR() {
|
|
326
|
+
const currentPlatform = resolvePlatform();
|
|
327
|
+
if (isShell(currentPlatform)) {
|
|
328
|
+
return openNativeQrViaBridge();
|
|
329
|
+
}
|
|
330
|
+
return startHtml5Qrcode();
|
|
331
|
+
},
|
|
332
|
+
onPushToken(callback) {
|
|
333
|
+
pushListeners.add(callback);
|
|
334
|
+
if (lastPushToken) {
|
|
335
|
+
queueMicrotask(() => {
|
|
336
|
+
try {
|
|
337
|
+
callback(lastPushToken);
|
|
338
|
+
} catch (error) {
|
|
339
|
+
console.warn("[tvm-app-adapter] push token listener failed:", error);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
return () => pushListeners.delete(callback);
|
|
344
|
+
},
|
|
345
|
+
onDeepLink(callback) {
|
|
346
|
+
deepLinkListeners.add(callback);
|
|
347
|
+
return () => deepLinkListeners.delete(callback);
|
|
348
|
+
},
|
|
349
|
+
onAppActive(callback) {
|
|
350
|
+
activeListeners.add(callback);
|
|
351
|
+
return () => activeListeners.delete(callback);
|
|
352
|
+
},
|
|
353
|
+
onAppBackground(callback) {
|
|
354
|
+
backgroundListeners.add(callback);
|
|
355
|
+
return () => backgroundListeners.delete(callback);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
var shell = createShellAPI(() => readShellPlatform() ?? "web");
|
|
360
|
+
function storeShellToken(payload) {
|
|
361
|
+
return sendBridgeCommand({ type: "storeToken", payload });
|
|
362
|
+
}
|
|
363
|
+
function requestShellPushPermission() {
|
|
364
|
+
return sendBridgeCommand({ type: "requestPushPermission" });
|
|
365
|
+
}
|
|
366
|
+
function getShellWindow() {
|
|
367
|
+
return typeof window === "undefined" ? null : window;
|
|
368
|
+
}
|
|
369
|
+
function getNativeBridge() {
|
|
370
|
+
const shellWindow = getShellWindow();
|
|
371
|
+
if (!shellWindow) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
const bridge2 = shellWindow.NativeBridge;
|
|
375
|
+
if (!bridge2 || typeof bridge2.postMessage !== "function") {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
return bridge2;
|
|
379
|
+
}
|
|
380
|
+
function sendBridgeCommand(command) {
|
|
381
|
+
const bridge2 = getNativeBridge();
|
|
382
|
+
if (!bridge2) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
bridge2.postMessage(command);
|
|
387
|
+
return true;
|
|
388
|
+
} catch (error) {
|
|
389
|
+
console.warn("[tvm-app-adapter] NativeBridge.postMessage failed:", error);
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function installGlobalCallbacks() {
|
|
394
|
+
const shellWindow = getShellWindow();
|
|
395
|
+
if (!shellWindow) {
|
|
396
|
+
installedCallbackNames = {};
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const target = shellWindow;
|
|
400
|
+
const assignCallback = (key, handler) => {
|
|
401
|
+
const nextName = bridgeConfig[key];
|
|
402
|
+
const previousName = installedCallbackNames[key];
|
|
403
|
+
if (previousName && previousName !== nextName) {
|
|
404
|
+
delete target[previousName];
|
|
405
|
+
}
|
|
406
|
+
installedCallbackNames[key] = nextName;
|
|
407
|
+
target[nextName] = handler;
|
|
408
|
+
};
|
|
409
|
+
assignCallback("pushTokenCallback", (token) => {
|
|
410
|
+
if (typeof token !== "string") {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
console.log("[tvm-app-adapter] nativePushToken", token);
|
|
414
|
+
notifyPushListeners(token);
|
|
415
|
+
});
|
|
416
|
+
assignCallback("deepLinkCallback", (path) => {
|
|
417
|
+
if (typeof path !== "string") {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
for (const listener of deepLinkListeners) {
|
|
421
|
+
try {
|
|
422
|
+
listener(path);
|
|
423
|
+
} catch (error) {
|
|
424
|
+
console.warn("[tvm-app-adapter] deep link listener failed:", error);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
assignCallback("appActiveCallback", () => {
|
|
429
|
+
for (const listener of activeListeners) {
|
|
430
|
+
try {
|
|
431
|
+
listener();
|
|
432
|
+
} catch (error) {
|
|
433
|
+
console.warn("[tvm-app-adapter] app active listener failed:", error);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
assignCallback("appBackgroundCallback", () => {
|
|
438
|
+
for (const listener of backgroundListeners) {
|
|
439
|
+
try {
|
|
440
|
+
listener();
|
|
441
|
+
} catch (error) {
|
|
442
|
+
console.warn("[tvm-app-adapter] app background listener failed:", error);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
assignCallback("qrResultCallback", (value) => {
|
|
447
|
+
if (typeof value !== "string") {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (!pendingQrRequest) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
clearTimeout(pendingQrRequest.timeoutId);
|
|
454
|
+
pendingQrRequest.resolve(value);
|
|
455
|
+
pendingQrRequest = null;
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
function openNativeQrViaBridge() {
|
|
459
|
+
return new Promise((resolve, reject) => {
|
|
460
|
+
if (pendingQrRequest) {
|
|
461
|
+
pendingQrRequest.reject(new Error("QR request superseded by a new call."));
|
|
462
|
+
clearTimeout(pendingQrRequest.timeoutId);
|
|
463
|
+
pendingQrRequest = null;
|
|
464
|
+
}
|
|
465
|
+
if (!sendBridgeCommand({ type: "openNativeQR" })) {
|
|
466
|
+
reject(new Error("Native bridge is unavailable."));
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const timeoutId = setTimeout(() => {
|
|
470
|
+
if (pendingQrRequest) {
|
|
471
|
+
pendingQrRequest.reject(new Error("Native QR request timed out."));
|
|
472
|
+
pendingQrRequest = null;
|
|
473
|
+
}
|
|
474
|
+
}, SHELL_QR_TIMEOUT_MS);
|
|
475
|
+
pendingQrRequest = { resolve, reject, timeoutId };
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
async function startHtml5Qrcode() {
|
|
479
|
+
if (typeof document === "undefined") {
|
|
480
|
+
throw new Error("QR scanning requires a browser environment.");
|
|
481
|
+
}
|
|
482
|
+
const [{ Html5Qrcode: Html5Qrcode2 }] = await Promise.all([import("html5-qrcode")]);
|
|
483
|
+
return new Promise((resolve, reject) => {
|
|
484
|
+
const elementId = `native-shell-qr-${Date.now()}`;
|
|
485
|
+
const overlay = document.createElement("div");
|
|
486
|
+
overlay.style.position = "fixed";
|
|
487
|
+
overlay.style.inset = "0";
|
|
488
|
+
overlay.style.background = "rgba(0, 0, 0, 0.9)";
|
|
489
|
+
overlay.style.display = "flex";
|
|
490
|
+
overlay.style.flexDirection = "column";
|
|
491
|
+
overlay.style.alignItems = "center";
|
|
492
|
+
overlay.style.justifyContent = "center";
|
|
493
|
+
overlay.style.zIndex = "2147483647";
|
|
494
|
+
overlay.style.backdropFilter = "blur(2px)";
|
|
495
|
+
const reader = document.createElement("div");
|
|
496
|
+
reader.id = elementId;
|
|
497
|
+
reader.style.width = "280px";
|
|
498
|
+
reader.style.height = "280px";
|
|
499
|
+
reader.style.borderRadius = "16px";
|
|
500
|
+
reader.style.overflow = "hidden";
|
|
501
|
+
reader.style.position = "relative";
|
|
502
|
+
const closeButton = document.createElement("button");
|
|
503
|
+
closeButton.type = "button";
|
|
504
|
+
closeButton.textContent = "\u2715";
|
|
505
|
+
closeButton.style.position = "absolute";
|
|
506
|
+
closeButton.style.top = "16px";
|
|
507
|
+
closeButton.style.right = "16px";
|
|
508
|
+
closeButton.style.background = "transparent";
|
|
509
|
+
closeButton.style.border = "none";
|
|
510
|
+
closeButton.style.color = "#fff";
|
|
511
|
+
closeButton.style.fontSize = "28px";
|
|
512
|
+
closeButton.style.cursor = "pointer";
|
|
513
|
+
overlay.appendChild(closeButton);
|
|
514
|
+
overlay.appendChild(reader);
|
|
515
|
+
document.body.appendChild(overlay);
|
|
516
|
+
const previousOverflow = document.body.style.overflow;
|
|
517
|
+
document.body.style.overflow = "hidden";
|
|
518
|
+
let disposed = false;
|
|
519
|
+
const scanner = new Html5Qrcode2(elementId);
|
|
520
|
+
const cleanup = async (result, error) => {
|
|
521
|
+
if (disposed) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
disposed = true;
|
|
525
|
+
try {
|
|
526
|
+
await scanner.stop();
|
|
527
|
+
const maybeClear = scanner.clear;
|
|
528
|
+
if (typeof maybeClear === "function") {
|
|
529
|
+
await Promise.resolve(maybeClear.call(scanner));
|
|
530
|
+
}
|
|
531
|
+
} catch {
|
|
532
|
+
}
|
|
533
|
+
overlay.remove();
|
|
534
|
+
document.body.style.overflow = previousOverflow;
|
|
535
|
+
if (typeof result === "string") {
|
|
536
|
+
resolve(result);
|
|
537
|
+
} else if (error) {
|
|
538
|
+
reject(error);
|
|
539
|
+
} else {
|
|
540
|
+
reject(new Error("QR scanning was cancelled."));
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
closeButton.addEventListener("click", () => {
|
|
544
|
+
void cleanup(void 0, new Error("QR scanning was cancelled by the user."));
|
|
545
|
+
});
|
|
546
|
+
scanner.start(
|
|
547
|
+
{ facingMode: "environment" },
|
|
548
|
+
{ fps: 10, qrbox: { width: 240, height: 240 } },
|
|
549
|
+
(decodedText) => {
|
|
550
|
+
void cleanup(decodedText);
|
|
551
|
+
},
|
|
552
|
+
() => {
|
|
553
|
+
}
|
|
554
|
+
).catch((error) => {
|
|
555
|
+
const cause = error instanceof Error ? error : new Error("Unable to start HTML5 QR scanner.");
|
|
556
|
+
void cleanup(void 0, cause);
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// src/adapters/baseAdapter.ts
|
|
562
|
+
var BaseMiniAppAdapter = class {
|
|
563
|
+
constructor(platform, environment) {
|
|
564
|
+
__publicField(this, "ready", false);
|
|
565
|
+
__publicField(this, "environment");
|
|
566
|
+
__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
567
|
+
__publicField(this, "disposables", new DisposableBag());
|
|
568
|
+
__publicField(this, "shell");
|
|
569
|
+
this.shell = createShellAPI(platform);
|
|
570
|
+
this.environment = {
|
|
571
|
+
platform,
|
|
572
|
+
...environment
|
|
573
|
+
};
|
|
574
|
+
if (typeof window !== "undefined") {
|
|
575
|
+
const resizeHandler = () => this.notifyEnvironmentChanged();
|
|
576
|
+
window.addEventListener("resize", resizeHandler);
|
|
577
|
+
this.registerDisposable(() => window.removeEventListener("resize", resizeHandler));
|
|
578
|
+
if (platform !== "web") {
|
|
579
|
+
const cleanup = this.applyScrollGuards();
|
|
580
|
+
if (cleanup) {
|
|
581
|
+
this.registerDisposable(cleanup);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
supports(_capability) {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
get platform() {
|
|
590
|
+
return this.environment.platform;
|
|
591
|
+
}
|
|
592
|
+
async init(_options) {
|
|
593
|
+
this.ready = true;
|
|
594
|
+
}
|
|
595
|
+
isReady() {
|
|
596
|
+
return this.ready;
|
|
597
|
+
}
|
|
598
|
+
getEnvironment() {
|
|
599
|
+
return { ...this.environment };
|
|
600
|
+
}
|
|
601
|
+
destroy() {
|
|
602
|
+
try {
|
|
603
|
+
this.onDestroy();
|
|
604
|
+
} finally {
|
|
605
|
+
this.disposables.disposeAll();
|
|
606
|
+
this.listeners.clear();
|
|
607
|
+
this.ready = false;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
subscribe(listener) {
|
|
611
|
+
this.listeners.add(listener);
|
|
612
|
+
return () => {
|
|
613
|
+
this.listeners.delete(listener);
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
async setColors(colors) {
|
|
617
|
+
if (colors.background) {
|
|
618
|
+
document.body.style.backgroundColor = colors.background;
|
|
619
|
+
}
|
|
620
|
+
if (colors.header) {
|
|
621
|
+
const meta = document.querySelector('meta[name="theme-color"]');
|
|
622
|
+
if (meta) {
|
|
623
|
+
meta.setAttribute("content", colors.header);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
onBackButton(callback) {
|
|
628
|
+
const handler = () => callback();
|
|
629
|
+
window.addEventListener("popstate", handler);
|
|
630
|
+
return () => window.removeEventListener("popstate", handler);
|
|
631
|
+
}
|
|
632
|
+
onPushToken(callback) {
|
|
633
|
+
return this.shell.onPushToken(callback);
|
|
634
|
+
}
|
|
635
|
+
onDeepLink(callback) {
|
|
636
|
+
return this.shell.onDeepLink(callback);
|
|
637
|
+
}
|
|
638
|
+
async openExternalLink(url) {
|
|
639
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
640
|
+
}
|
|
641
|
+
async openInternalLink(_url) {
|
|
642
|
+
}
|
|
643
|
+
async closeApp() {
|
|
644
|
+
if (window.history.length > 1) {
|
|
645
|
+
window.history.back();
|
|
646
|
+
} else {
|
|
647
|
+
window.close();
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
setBackButtonVisibility(_visible) {
|
|
651
|
+
}
|
|
652
|
+
onAppearanceChange(callback) {
|
|
653
|
+
callback(this.environment.appearance);
|
|
654
|
+
return () => {
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
getInitData() {
|
|
658
|
+
return void 0;
|
|
659
|
+
}
|
|
660
|
+
getLaunchParams() {
|
|
661
|
+
return void 0;
|
|
662
|
+
}
|
|
663
|
+
decodeStartParam(_param) {
|
|
664
|
+
return void 0;
|
|
665
|
+
}
|
|
666
|
+
requestFullscreen() {
|
|
667
|
+
}
|
|
668
|
+
getViewportInsets() {
|
|
669
|
+
return void 0;
|
|
670
|
+
}
|
|
671
|
+
shareMessage(_message) {
|
|
672
|
+
return Promise.resolve();
|
|
673
|
+
}
|
|
674
|
+
shareUrl(_url, _text) {
|
|
675
|
+
}
|
|
676
|
+
async downloadFile(url, filename) {
|
|
677
|
+
await triggerFileDownload(url, filename);
|
|
678
|
+
}
|
|
679
|
+
shareStory(_mediaUrl, _options) {
|
|
680
|
+
return Promise.resolve();
|
|
681
|
+
}
|
|
682
|
+
trackConversionEvent(_event, _payload) {
|
|
683
|
+
}
|
|
684
|
+
trackPixelEvent(_event, _payload) {
|
|
685
|
+
}
|
|
686
|
+
copyTextToClipboard(text) {
|
|
687
|
+
return navigator.clipboard.writeText(text).catch(() => {
|
|
688
|
+
const textarea = document.createElement("textarea");
|
|
689
|
+
textarea.value = text;
|
|
690
|
+
textarea.style.position = "fixed";
|
|
691
|
+
document.body.appendChild(textarea);
|
|
692
|
+
textarea.focus();
|
|
693
|
+
textarea.select();
|
|
694
|
+
try {
|
|
695
|
+
document.execCommand("copy");
|
|
696
|
+
} catch {
|
|
697
|
+
}
|
|
698
|
+
document.body.removeChild(textarea);
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
computeSafeArea() {
|
|
702
|
+
const viewportInsets = this.getViewportInsets?.();
|
|
703
|
+
const cssSafeArea = readCssSafeArea();
|
|
704
|
+
return computeCombinedSafeArea({
|
|
705
|
+
environment: this.environment.safeArea,
|
|
706
|
+
viewport: viewportInsets,
|
|
707
|
+
css: cssSafeArea
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
bindCssVariables(_mapper) {
|
|
711
|
+
}
|
|
712
|
+
vibrateImpact(_style) {
|
|
713
|
+
navigator.vibrate?.(10);
|
|
714
|
+
}
|
|
715
|
+
vibrateNotification(_type) {
|
|
716
|
+
navigator.vibrate?.([10, 30, 10]);
|
|
717
|
+
}
|
|
718
|
+
vibrateSelection() {
|
|
719
|
+
navigator.vibrate?.(5);
|
|
720
|
+
}
|
|
721
|
+
onViewHide(_callback) {
|
|
722
|
+
return () => {
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
onViewRestore(_callback) {
|
|
726
|
+
return () => {
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
async showPopup(options) {
|
|
730
|
+
const message = [options.title, options.message].filter(Boolean).join("\n\n");
|
|
731
|
+
window.alert(message);
|
|
732
|
+
const firstButton = options.buttons?.[0];
|
|
733
|
+
return firstButton?.id ?? "ok";
|
|
734
|
+
}
|
|
735
|
+
async scanQRCode(_options) {
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
async requestPhone() {
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
async requestNotificationsPermission() {
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
enableVerticalSwipes() {
|
|
745
|
+
}
|
|
746
|
+
disableVerticalSwipes() {
|
|
747
|
+
}
|
|
748
|
+
notifyEnvironmentChanged() {
|
|
749
|
+
for (const listener of this.listeners) {
|
|
750
|
+
try {
|
|
751
|
+
listener();
|
|
752
|
+
} catch (error) {
|
|
753
|
+
console.warn("[tvm-app-adapter] environment listener failed:", error);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
onDestroy() {
|
|
758
|
+
}
|
|
759
|
+
registerDisposable(disposable) {
|
|
760
|
+
return this.disposables.add(disposable);
|
|
761
|
+
}
|
|
762
|
+
applyScrollGuards() {
|
|
763
|
+
if (typeof document === "undefined") {
|
|
764
|
+
return void 0;
|
|
765
|
+
}
|
|
766
|
+
const html = document.documentElement;
|
|
767
|
+
const body = document.body;
|
|
768
|
+
if (!html || !body) {
|
|
769
|
+
return void 0;
|
|
770
|
+
}
|
|
771
|
+
const prevHtmlOverscroll = html.style.overscrollBehaviorY;
|
|
772
|
+
const prevBodyOverscroll = body.style.overscrollBehaviorY;
|
|
773
|
+
const prevBodyTouchAction = body.style.touchAction;
|
|
774
|
+
html.style.overscrollBehaviorY = "none";
|
|
775
|
+
body.style.overscrollBehaviorY = "none";
|
|
776
|
+
body.style.touchAction = "manipulation";
|
|
777
|
+
return () => {
|
|
778
|
+
html.style.overscrollBehaviorY = prevHtmlOverscroll;
|
|
779
|
+
body.style.overscrollBehaviorY = prevBodyOverscroll;
|
|
780
|
+
body.style.touchAction = prevBodyTouchAction;
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
// src/adapters/maxAdapter.ts
|
|
786
|
+
function getMaxBridge() {
|
|
787
|
+
return window.WebApp;
|
|
788
|
+
}
|
|
789
|
+
var MaxMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
790
|
+
constructor() {
|
|
791
|
+
super("max");
|
|
792
|
+
__publicField(this, "backHandlers", /* @__PURE__ */ new Map());
|
|
793
|
+
__publicField(this, "initData");
|
|
794
|
+
__publicField(this, "initDataUnsafe");
|
|
795
|
+
}
|
|
796
|
+
async init(_options) {
|
|
797
|
+
if (this.ready) {
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
const bridge2 = getMaxBridge();
|
|
801
|
+
bridge2?.ready?.();
|
|
802
|
+
this.initData = bridge2?.initData;
|
|
803
|
+
this.initDataUnsafe = bridge2?.initDataUnsafe;
|
|
804
|
+
const environment = {
|
|
805
|
+
platform: "max",
|
|
806
|
+
sdkVersion: bridge2?.version,
|
|
807
|
+
appVersion: bridge2?.version,
|
|
808
|
+
languageCode: bridge2?.initDataUnsafe?.user?.language_code,
|
|
809
|
+
isWebView: true
|
|
810
|
+
};
|
|
811
|
+
this.environment = environment;
|
|
812
|
+
this.ready = true;
|
|
813
|
+
}
|
|
814
|
+
supports(capability) {
|
|
815
|
+
const bridge2 = getMaxBridge();
|
|
816
|
+
switch (capability) {
|
|
817
|
+
case "haptics":
|
|
818
|
+
return Boolean(bridge2?.HapticFeedback?.impactOccurred);
|
|
819
|
+
case "qrScanner":
|
|
820
|
+
return typeof bridge2?.openCodeReader === "function";
|
|
821
|
+
case "closeApp":
|
|
822
|
+
return typeof bridge2?.close === "function";
|
|
823
|
+
case "backButton":
|
|
824
|
+
return Boolean(bridge2?.BackButton?.onClick);
|
|
825
|
+
case "backButtonVisibility":
|
|
826
|
+
return Boolean(bridge2?.BackButton?.show && bridge2.BackButton.hide);
|
|
827
|
+
case "requestPhone":
|
|
828
|
+
if (!bridge2) {
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
831
|
+
return typeof bridge2.requestPhoneNumber === "function" || typeof window !== "undefined";
|
|
832
|
+
case "popup":
|
|
833
|
+
return false;
|
|
834
|
+
default:
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
getInitData() {
|
|
839
|
+
return this.initData;
|
|
840
|
+
}
|
|
841
|
+
getLaunchParams() {
|
|
842
|
+
return this.initDataUnsafe ?? void 0;
|
|
843
|
+
}
|
|
844
|
+
onBackButton(callback) {
|
|
845
|
+
const bridge2 = getMaxBridge();
|
|
846
|
+
if (!bridge2?.BackButton?.onClick) {
|
|
847
|
+
return super.onBackButton(callback);
|
|
848
|
+
}
|
|
849
|
+
const wrapped = () => callback();
|
|
850
|
+
const disposer = bridge2.BackButton.onClick(wrapped);
|
|
851
|
+
bridge2.BackButton.show?.();
|
|
852
|
+
const removeFromBag = this.registerDisposable(() => {
|
|
853
|
+
if (typeof disposer === "function") {
|
|
854
|
+
disposer();
|
|
855
|
+
} else {
|
|
856
|
+
bridge2.BackButton?.offClick?.(wrapped);
|
|
857
|
+
}
|
|
858
|
+
this.backHandlers.delete(callback);
|
|
859
|
+
if (!this.backHandlers.size) {
|
|
860
|
+
bridge2.BackButton?.hide?.();
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
this.backHandlers.set(callback, removeFromBag);
|
|
864
|
+
return removeFromBag;
|
|
865
|
+
}
|
|
866
|
+
setBackButtonVisibility(visible) {
|
|
867
|
+
const bridge2 = getMaxBridge();
|
|
868
|
+
if (!bridge2?.BackButton) {
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
visible ? bridge2.BackButton.show?.() : bridge2.BackButton.hide?.();
|
|
872
|
+
}
|
|
873
|
+
async openExternalLink(url) {
|
|
874
|
+
const bridge2 = getMaxBridge();
|
|
875
|
+
if (bridge2?.openExternalLink) {
|
|
876
|
+
bridge2.openExternalLink(url);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
await super.openExternalLink(url);
|
|
880
|
+
}
|
|
881
|
+
async closeApp() {
|
|
882
|
+
const bridge2 = getMaxBridge();
|
|
883
|
+
if (bridge2?.close) {
|
|
884
|
+
bridge2.close();
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
await super.closeApp();
|
|
888
|
+
}
|
|
889
|
+
vibrateImpact(style) {
|
|
890
|
+
const bridge2 = getMaxBridge();
|
|
891
|
+
if (!bridge2?.HapticFeedback?.impactOccurred) {
|
|
892
|
+
super.vibrateImpact(style);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
void bridge2.HapticFeedback.impactOccurred(style).catch((error) => {
|
|
896
|
+
console.warn("[mini-app-template] MAX impact haptic failed:", error);
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
vibrateNotification(type) {
|
|
900
|
+
const bridge2 = getMaxBridge();
|
|
901
|
+
if (!bridge2?.HapticFeedback?.notificationOccurred) {
|
|
902
|
+
super.vibrateNotification(type);
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
void bridge2.HapticFeedback.notificationOccurred(type).catch((error) => {
|
|
906
|
+
console.warn("[mini-app-template] MAX notification haptic failed:", error);
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
vibrateSelection() {
|
|
910
|
+
const bridge2 = getMaxBridge();
|
|
911
|
+
if (!bridge2?.HapticFeedback?.selectionChanged) {
|
|
912
|
+
super.vibrateSelection();
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
void bridge2.HapticFeedback.selectionChanged().catch((error) => {
|
|
916
|
+
console.warn("[mini-app-template] MAX selection haptic failed:", error);
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
async scanQRCode(options) {
|
|
920
|
+
const bridge2 = getMaxBridge();
|
|
921
|
+
if (!bridge2?.openCodeReader) {
|
|
922
|
+
return super.scanQRCode(options);
|
|
923
|
+
}
|
|
924
|
+
try {
|
|
925
|
+
const result = await bridge2.openCodeReader(options?.closeOnCapture !== false);
|
|
926
|
+
return result?.value ?? null;
|
|
927
|
+
} catch (error) {
|
|
928
|
+
console.warn("[mini-app-template] MAX QR scanner failed:", error);
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
async requestPhone() {
|
|
933
|
+
const bridge2 = getMaxBridge();
|
|
934
|
+
if (bridge2?.requestPhoneNumber) {
|
|
935
|
+
try {
|
|
936
|
+
const response = await bridge2.requestPhoneNumber();
|
|
937
|
+
return this.extractPhone(response);
|
|
938
|
+
} catch (error) {
|
|
939
|
+
console.warn("[mini-app-template] MAX requestPhone failed:", error);
|
|
940
|
+
return null;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return this.requestPhoneViaEvent();
|
|
944
|
+
}
|
|
945
|
+
async showPopup(options) {
|
|
946
|
+
return super.showPopup(options);
|
|
947
|
+
}
|
|
948
|
+
async downloadFile(url, filename) {
|
|
949
|
+
const bridge2 = getMaxBridge();
|
|
950
|
+
if (bridge2?.downloadFile) {
|
|
951
|
+
try {
|
|
952
|
+
await bridge2.downloadFile(url, filename);
|
|
953
|
+
return;
|
|
954
|
+
} catch (error) {
|
|
955
|
+
console.warn("[mini-app-template] MAX downloadFile failed:", error);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
await super.downloadFile(url, filename);
|
|
959
|
+
}
|
|
960
|
+
async requestPhoneViaEvent() {
|
|
961
|
+
if (typeof window === "undefined") {
|
|
962
|
+
return null;
|
|
963
|
+
}
|
|
964
|
+
let providedPromise;
|
|
965
|
+
const detail = {
|
|
966
|
+
providePromise: (promise) => {
|
|
967
|
+
providedPromise = promise;
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
window.dispatchEvent(new CustomEvent("WebAppRequestPhone", { detail }));
|
|
971
|
+
if (!providedPromise) {
|
|
972
|
+
console.warn("[mini-app-template] MAX requestPhone not handled: native promise missing");
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
try {
|
|
976
|
+
const result = await providedPromise;
|
|
977
|
+
return this.extractPhone(result);
|
|
978
|
+
} catch (error) {
|
|
979
|
+
console.warn("[mini-app-template] MAX requestPhone promise rejected:", error);
|
|
980
|
+
return null;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
extractPhone(data) {
|
|
984
|
+
if (typeof data === "string") {
|
|
985
|
+
return data || null;
|
|
986
|
+
}
|
|
987
|
+
if (!data || typeof data !== "object") {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
const directPhone = data.phone ?? data.phone_number ?? data.phoneNumber;
|
|
991
|
+
if (typeof directPhone === "string" && directPhone) {
|
|
992
|
+
return directPhone;
|
|
993
|
+
}
|
|
994
|
+
const contact = data.contact;
|
|
995
|
+
if (contact && typeof contact === "object") {
|
|
996
|
+
const nested = contact.phone ?? contact.phone_number ?? contact.phoneNumber;
|
|
997
|
+
if (typeof nested === "string" && nested) {
|
|
998
|
+
return nested;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
return null;
|
|
1002
|
+
}
|
|
1003
|
+
onDestroy() {
|
|
1004
|
+
this.backHandlers.clear();
|
|
1005
|
+
super.onDestroy();
|
|
1006
|
+
}
|
|
1007
|
+
};
|
|
1008
|
+
|
|
1009
|
+
// src/adapters/shellAdapter.ts
|
|
1010
|
+
var ShellMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
1011
|
+
constructor(platform) {
|
|
1012
|
+
super(platform, {
|
|
1013
|
+
isWebView: true,
|
|
1014
|
+
hasNativeQR: true,
|
|
1015
|
+
hasPush: true,
|
|
1016
|
+
hasWidgets: true
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
supports(capability) {
|
|
1020
|
+
switch (capability) {
|
|
1021
|
+
case "qrScanner":
|
|
1022
|
+
return true;
|
|
1023
|
+
case "notifications":
|
|
1024
|
+
return true;
|
|
1025
|
+
default:
|
|
1026
|
+
return super.supports(capability);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
async scanQRCode() {
|
|
1030
|
+
try {
|
|
1031
|
+
const value = await this.shell.openNativeQR();
|
|
1032
|
+
return value ?? null;
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
console.warn("[tvm-app-adapter] shell.openNativeQR failed:", error);
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
async requestNotificationsPermission() {
|
|
1039
|
+
return requestShellPushPermission();
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
// src/adapters/telegramAdapter.ts
|
|
1044
|
+
var import_sdk_react = require("@tma.js/sdk-react");
|
|
1045
|
+
var import_sdk = require("@tma.js/sdk");
|
|
1046
|
+
|
|
1047
|
+
// src/lib/features.ts
|
|
1048
|
+
function isFeatureAvailable(feature) {
|
|
1049
|
+
if (typeof feature !== "function") {
|
|
1050
|
+
return false;
|
|
1051
|
+
}
|
|
1052
|
+
const candidate = feature;
|
|
1053
|
+
if (typeof candidate.isAvailable === "function") {
|
|
1054
|
+
try {
|
|
1055
|
+
return candidate.isAvailable();
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
console.warn("[tvm-app-adapter] feature availability check failed:", error);
|
|
1058
|
+
return false;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
return true;
|
|
1062
|
+
}
|
|
1063
|
+
function ensureFeature(feature, ...args) {
|
|
1064
|
+
if (!isFeatureAvailable(feature)) {
|
|
1065
|
+
return { ok: false };
|
|
1066
|
+
}
|
|
1067
|
+
try {
|
|
1068
|
+
const value = feature(...args);
|
|
1069
|
+
return { ok: true, value };
|
|
1070
|
+
} catch (error) {
|
|
1071
|
+
console.warn("[tvm-app-adapter] feature call failed:", error);
|
|
1072
|
+
return { ok: false };
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// src/lib/viewport.ts
|
|
1077
|
+
async function ensureViewportMounted(options) {
|
|
1078
|
+
const { sdkViewport, fallbackMount } = options;
|
|
1079
|
+
if (sdkViewport?.isSupported?.()) {
|
|
1080
|
+
if (typeof sdkViewport.isMounted === "function" && sdkViewport.isMounted()) {
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
if (typeof sdkViewport.mount === "function") {
|
|
1084
|
+
await sdkViewport.mount();
|
|
1085
|
+
}
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
if (typeof fallbackMount === "function") {
|
|
1089
|
+
await fallbackMount();
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
async function bindViewportCssVars(options) {
|
|
1093
|
+
await ensureViewportMounted(options);
|
|
1094
|
+
if (typeof options.bindCssVars !== "function") {
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
try {
|
|
1098
|
+
options.bindCssVars(options.mapper);
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
if (error instanceof Error && /css variables are already bound/i.test(error.message)) {
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
throw error;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// src/adapters/telegramAdapter.ts
|
|
1108
|
+
var TelegramMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
1109
|
+
constructor() {
|
|
1110
|
+
super("telegram");
|
|
1111
|
+
__publicField(this, "backHandlers", /* @__PURE__ */ new Map());
|
|
1112
|
+
__publicField(this, "cssVariablesBound", false);
|
|
1113
|
+
__publicField(this, "appearanceListeners", /* @__PURE__ */ new Set());
|
|
1114
|
+
__publicField(this, "appearanceWatcherDispose");
|
|
1115
|
+
__publicField(this, "viewHideListeners", /* @__PURE__ */ new Set());
|
|
1116
|
+
__publicField(this, "viewRestoreListeners", /* @__PURE__ */ new Set());
|
|
1117
|
+
__publicField(this, "activeWatcherDispose");
|
|
1118
|
+
}
|
|
1119
|
+
async init(options) {
|
|
1120
|
+
if (this.ready) {
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
const debug = Boolean(options?.debug);
|
|
1124
|
+
const eruda = Boolean(options?.eruda);
|
|
1125
|
+
const mockForMacOS = Boolean(options?.mockForMacOS);
|
|
1126
|
+
(0, import_sdk_react.setDebug)(debug);
|
|
1127
|
+
(0, import_sdk_react.init)();
|
|
1128
|
+
if (!import_sdk_react.miniApp.isSupported()) {
|
|
1129
|
+
console.warn("[tvm-app-adapter] miniApp feature is not supported; falling back to limited mode.");
|
|
1130
|
+
}
|
|
1131
|
+
if (eruda) {
|
|
1132
|
+
void import("eruda").then(({ default: erudaInstance }) => {
|
|
1133
|
+
erudaInstance.init();
|
|
1134
|
+
erudaInstance.position({ x: window.innerWidth - 150, y: window.innerHeight - 150 });
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
if (mockForMacOS) {
|
|
1138
|
+
let firstThemeSent = false;
|
|
1139
|
+
(0, import_sdk_react.mockTelegramEnv)({
|
|
1140
|
+
onEvent(event, next) {
|
|
1141
|
+
if (event.name === "web_app_request_theme") {
|
|
1142
|
+
let tp = {};
|
|
1143
|
+
if (firstThemeSent) {
|
|
1144
|
+
tp = import_sdk_react.themeParams.state();
|
|
1145
|
+
} else {
|
|
1146
|
+
firstThemeSent = true;
|
|
1147
|
+
tp || (tp = (0, import_sdk_react.retrieveLaunchParams)().tgWebAppThemeParams);
|
|
1148
|
+
}
|
|
1149
|
+
return (0, import_sdk_react.emitEvent)("theme_changed", { theme_params: tp });
|
|
1150
|
+
}
|
|
1151
|
+
if (event.name === "web_app_request_safe_area") {
|
|
1152
|
+
return (0, import_sdk_react.emitEvent)("safe_area_changed", { left: 0, top: 0, right: 0, bottom: 0 });
|
|
1153
|
+
}
|
|
1154
|
+
next();
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
import_sdk_react.initData.restore();
|
|
1159
|
+
import_sdk_react.miniApp.ready();
|
|
1160
|
+
const launchParams = (0, import_sdk_react.retrieveLaunchParams)();
|
|
1161
|
+
let appearance;
|
|
1162
|
+
try {
|
|
1163
|
+
appearance = import_sdk_react.miniApp.isDark() ? "dark" : "light";
|
|
1164
|
+
} catch {
|
|
1165
|
+
appearance = void 0;
|
|
1166
|
+
}
|
|
1167
|
+
const environment = {
|
|
1168
|
+
platform: "telegram",
|
|
1169
|
+
sdkVersion: launchParams.tgWebAppVersion,
|
|
1170
|
+
languageCode: import_sdk_react.initData.user()?.language_code,
|
|
1171
|
+
appearance,
|
|
1172
|
+
isWebView: true
|
|
1173
|
+
};
|
|
1174
|
+
this.environment = environment;
|
|
1175
|
+
this.notifyAppearance(environment.appearance);
|
|
1176
|
+
import_sdk_react.backButton.mount.ifAvailable();
|
|
1177
|
+
if (import_sdk_react.miniApp.mount.isAvailable()) {
|
|
1178
|
+
import_sdk_react.themeParams.mount();
|
|
1179
|
+
import_sdk_react.miniApp.mount();
|
|
1180
|
+
this.bindCssVariables();
|
|
1181
|
+
}
|
|
1182
|
+
await this.prepareViewport();
|
|
1183
|
+
this.setupAppearanceWatcher();
|
|
1184
|
+
this.setupActiveWatcher();
|
|
1185
|
+
this.ready = true;
|
|
1186
|
+
}
|
|
1187
|
+
async setColors(colors) {
|
|
1188
|
+
const fallback = {};
|
|
1189
|
+
if (colors.header) {
|
|
1190
|
+
if (import_sdk_react.miniApp.setHeaderColor.isAvailable()) {
|
|
1191
|
+
const headerColor = import_sdk_react.miniApp.setHeaderColor.supports?.("rgb") ? colors.header : "bg_color";
|
|
1192
|
+
const { ok } = ensureFeature(import_sdk_react.miniApp.setHeaderColor, headerColor);
|
|
1193
|
+
if (!ok) {
|
|
1194
|
+
fallback.header = colors.header;
|
|
1195
|
+
}
|
|
1196
|
+
} else {
|
|
1197
|
+
fallback.header = colors.header;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
if (colors.background) {
|
|
1201
|
+
if (import_sdk_react.miniApp.setBgColor.isAvailable()) {
|
|
1202
|
+
const { ok } = ensureFeature(import_sdk_react.miniApp.setBgColor, colors.background);
|
|
1203
|
+
if (!ok) {
|
|
1204
|
+
fallback.background = colors.background;
|
|
1205
|
+
}
|
|
1206
|
+
} else {
|
|
1207
|
+
fallback.background = colors.background;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
if (colors.footer) {
|
|
1211
|
+
if (import_sdk_react.miniApp.setBgColor.isAvailable()) {
|
|
1212
|
+
const { ok } = ensureFeature(import_sdk_react.miniApp.setBottomBarColorFp, colors.footer);
|
|
1213
|
+
if (!ok) {
|
|
1214
|
+
fallback.footer = colors.footer;
|
|
1215
|
+
}
|
|
1216
|
+
} else {
|
|
1217
|
+
fallback.footer = colors.footer;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
if (fallback.header || fallback.background) {
|
|
1221
|
+
await super.setColors(fallback);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
copyTextToClipboard(text) {
|
|
1225
|
+
return (0, import_sdk.copyTextToClipboard)(text);
|
|
1226
|
+
}
|
|
1227
|
+
onBackButton(callback) {
|
|
1228
|
+
if (!import_sdk_react.backButton.isSupported()) {
|
|
1229
|
+
return super.onBackButton(callback);
|
|
1230
|
+
}
|
|
1231
|
+
const dispose = import_sdk_react.backButton.onClick(() => callback());
|
|
1232
|
+
const removeFromBag = this.registerDisposable(() => {
|
|
1233
|
+
if (typeof dispose === "function") {
|
|
1234
|
+
dispose();
|
|
1235
|
+
}
|
|
1236
|
+
this.backHandlers.delete(callback);
|
|
1237
|
+
if (!this.backHandlers.size) {
|
|
1238
|
+
import_sdk_react.backButton.hide();
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
this.backHandlers.set(callback, removeFromBag);
|
|
1242
|
+
return removeFromBag;
|
|
1243
|
+
}
|
|
1244
|
+
async openExternalLink(url) {
|
|
1245
|
+
try {
|
|
1246
|
+
(0, import_sdk_react.openLink)(url, { tryInstantView: true });
|
|
1247
|
+
return;
|
|
1248
|
+
} catch {
|
|
1249
|
+
}
|
|
1250
|
+
await super.openExternalLink(url);
|
|
1251
|
+
}
|
|
1252
|
+
async openInternalLink(url) {
|
|
1253
|
+
(0, import_sdk_react.postEvent)("web_app_open_tg_link", { path_full: url });
|
|
1254
|
+
}
|
|
1255
|
+
enableDebug(state) {
|
|
1256
|
+
try {
|
|
1257
|
+
state ? import_sdk.closingBehavior.enableConfirmation() : import_sdk.closingBehavior.disableConfirmation();
|
|
1258
|
+
} catch {
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
supports(capability) {
|
|
1262
|
+
switch (capability) {
|
|
1263
|
+
case "haptics":
|
|
1264
|
+
return isFeatureAvailable(import_sdk_react.hapticFeedback.selectionChanged);
|
|
1265
|
+
case "popup":
|
|
1266
|
+
return isFeatureAvailable(import_sdk_react.popup.show);
|
|
1267
|
+
case "qrScanner":
|
|
1268
|
+
return isFeatureAvailable(import_sdk_react.qrScanner.open);
|
|
1269
|
+
case "closeApp":
|
|
1270
|
+
return isFeatureAvailable(import_sdk_react.miniApp.close);
|
|
1271
|
+
case "backButton":
|
|
1272
|
+
return import_sdk_react.backButton.isSupported();
|
|
1273
|
+
case "backButtonVisibility":
|
|
1274
|
+
return import_sdk_react.backButton.hide.isSupported();
|
|
1275
|
+
case "bindCssVariables":
|
|
1276
|
+
return true;
|
|
1277
|
+
case "requestPhone": {
|
|
1278
|
+
return Boolean(isFeatureAvailable(import_sdk.requestPhoneAccess) || isFeatureAvailable(import_sdk.requestContact));
|
|
1279
|
+
}
|
|
1280
|
+
default:
|
|
1281
|
+
return false;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
bindCssVariables(mapper) {
|
|
1285
|
+
if (this.cssVariablesBound) {
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
try {
|
|
1289
|
+
import_sdk_react.themeParams.bindCssVars(mapper);
|
|
1290
|
+
this.cssVariablesBound = true;
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
if (error instanceof Error && /css variables are already bound/i.test(error.message)) {
|
|
1293
|
+
this.cssVariablesBound = true;
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
throw error;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
vibrateImpact(style) {
|
|
1300
|
+
if (this.supports("haptics")) {
|
|
1301
|
+
import_sdk_react.hapticFeedback.impactOccurred(style);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
vibrateNotification(type) {
|
|
1305
|
+
if (this.supports("haptics")) {
|
|
1306
|
+
import_sdk_react.hapticFeedback.notificationOccurred(type);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
vibrateSelection() {
|
|
1310
|
+
if (this.supports("haptics")) {
|
|
1311
|
+
import_sdk_react.hapticFeedback.selectionChanged();
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
async showPopup(options) {
|
|
1315
|
+
const popupResult = ensureFeature(import_sdk_react.popup.show, {
|
|
1316
|
+
title: options.title,
|
|
1317
|
+
message: options.message,
|
|
1318
|
+
buttons: options.buttons?.map((button) => ({
|
|
1319
|
+
id: button.id,
|
|
1320
|
+
text: button.text ?? button.id,
|
|
1321
|
+
type: button.type ?? "default"
|
|
1322
|
+
}))
|
|
1323
|
+
});
|
|
1324
|
+
if (!popupResult.ok) {
|
|
1325
|
+
return super.showPopup(options);
|
|
1326
|
+
}
|
|
1327
|
+
const response = await popupResult.value;
|
|
1328
|
+
return response ?? null;
|
|
1329
|
+
}
|
|
1330
|
+
async scanQRCode(options) {
|
|
1331
|
+
let result = null;
|
|
1332
|
+
const closeOnCapture = options?.closeOnCapture ?? true;
|
|
1333
|
+
const qrScannerResult = ensureFeature(import_sdk_react.qrScanner.open, {
|
|
1334
|
+
onCaptured: (qr) => {
|
|
1335
|
+
result = qr;
|
|
1336
|
+
if (closeOnCapture) {
|
|
1337
|
+
void ensureFeature(import_sdk_react.qrScanner.close);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
});
|
|
1341
|
+
if (!qrScannerResult.ok) {
|
|
1342
|
+
return super.scanQRCode(options);
|
|
1343
|
+
}
|
|
1344
|
+
await qrScannerResult.value;
|
|
1345
|
+
return result;
|
|
1346
|
+
}
|
|
1347
|
+
async closeApp() {
|
|
1348
|
+
const closeResult = ensureFeature(import_sdk_react.miniApp.close);
|
|
1349
|
+
if (closeResult.ok) {
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
await super.closeApp();
|
|
1353
|
+
}
|
|
1354
|
+
getInitData() {
|
|
1355
|
+
try {
|
|
1356
|
+
return import_sdk_react.initData.raw();
|
|
1357
|
+
} catch {
|
|
1358
|
+
return void 0;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
getLaunchParams() {
|
|
1362
|
+
try {
|
|
1363
|
+
return (0, import_sdk_react.retrieveLaunchParams)();
|
|
1364
|
+
} catch {
|
|
1365
|
+
return void 0;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
decodeStartParam(param) {
|
|
1369
|
+
try {
|
|
1370
|
+
return (0, import_sdk.decodeStartParam)(param);
|
|
1371
|
+
} catch {
|
|
1372
|
+
return void 0;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
requestFullscreen() {
|
|
1376
|
+
void this.requestFullscreenInternal();
|
|
1377
|
+
}
|
|
1378
|
+
getViewportInsets() {
|
|
1379
|
+
try {
|
|
1380
|
+
const safeArea = import_sdk_react.viewport.safeAreaInsets();
|
|
1381
|
+
const contentSafeArea = import_sdk_react.viewport.contentSafeAreaInsets();
|
|
1382
|
+
return {
|
|
1383
|
+
safeArea,
|
|
1384
|
+
contentSafeArea
|
|
1385
|
+
};
|
|
1386
|
+
} catch {
|
|
1387
|
+
return void 0;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
onAppearanceChange(callback) {
|
|
1391
|
+
this.appearanceListeners.add(callback);
|
|
1392
|
+
callback(this.environment.appearance);
|
|
1393
|
+
return () => {
|
|
1394
|
+
this.appearanceListeners.delete(callback);
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
setBackButtonVisibility(visible) {
|
|
1398
|
+
if (!import_sdk_react.backButton.isSupported()) {
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
if (visible) {
|
|
1402
|
+
import_sdk_react.backButton.show();
|
|
1403
|
+
} else {
|
|
1404
|
+
import_sdk_react.backButton.hide();
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
enableVerticalSwipes() {
|
|
1408
|
+
try {
|
|
1409
|
+
const sdkSwipe = import_sdk.swipeBehavior;
|
|
1410
|
+
if (typeof sdkSwipe.isSupported === "function" && sdkSwipe.isSupported()) {
|
|
1411
|
+
if (typeof sdkSwipe.isMounted === "function" && !sdkSwipe.isMounted()) {
|
|
1412
|
+
sdkSwipe.mount?.();
|
|
1413
|
+
}
|
|
1414
|
+
sdkSwipe.enableVertical?.();
|
|
1415
|
+
} else if (import_sdk.swipeBehavior.enableVertical.isAvailable()) {
|
|
1416
|
+
import_sdk.swipeBehavior.enableVertical();
|
|
1417
|
+
}
|
|
1418
|
+
} catch (error) {
|
|
1419
|
+
console.warn("[tvm-app-adapter] enableVerticalSwipes failed:", error);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
disableVerticalSwipes() {
|
|
1423
|
+
try {
|
|
1424
|
+
const sdkSwipe = import_sdk.swipeBehavior;
|
|
1425
|
+
if (typeof sdkSwipe.isSupported === "function" && sdkSwipe.isSupported()) {
|
|
1426
|
+
if (typeof sdkSwipe.isMounted === "function" && !sdkSwipe.isMounted()) {
|
|
1427
|
+
sdkSwipe.mount?.();
|
|
1428
|
+
}
|
|
1429
|
+
sdkSwipe.disableVertical?.();
|
|
1430
|
+
} else if (import_sdk.swipeBehavior.disableVertical.isAvailable()) {
|
|
1431
|
+
import_sdk.swipeBehavior.disableVertical();
|
|
1432
|
+
}
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
console.warn("[tvm-app-adapter] disableVerticalSwipes failed:", error);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
onViewHide(callback) {
|
|
1438
|
+
this.viewHideListeners.add(callback);
|
|
1439
|
+
return () => {
|
|
1440
|
+
this.viewHideListeners.delete(callback);
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
onViewRestore(callback) {
|
|
1444
|
+
this.viewRestoreListeners.add(callback);
|
|
1445
|
+
return () => {
|
|
1446
|
+
this.viewRestoreListeners.delete(callback);
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
shareUrl(url, text) {
|
|
1450
|
+
return (0, import_sdk.shareURL)(url, text);
|
|
1451
|
+
}
|
|
1452
|
+
async downloadFile(url, filename) {
|
|
1453
|
+
const result = ensureFeature(import_sdk.downloadFile, url, filename);
|
|
1454
|
+
if (result.ok) {
|
|
1455
|
+
try {
|
|
1456
|
+
await result.value;
|
|
1457
|
+
return;
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
console.warn("[tvm-app-adapter] Telegram downloadFile failed:", error);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
await super.downloadFile(url, filename);
|
|
1463
|
+
}
|
|
1464
|
+
async shareStory(mediaUrl, options) {
|
|
1465
|
+
(0, import_sdk.shareStory)(mediaUrl, options);
|
|
1466
|
+
}
|
|
1467
|
+
async requestPhone() {
|
|
1468
|
+
const contactFeature = ensureFeature(import_sdk.requestContact);
|
|
1469
|
+
if (!contactFeature.ok) {
|
|
1470
|
+
return super.requestPhone();
|
|
1471
|
+
}
|
|
1472
|
+
if (import_sdk.requestPhoneAccess) {
|
|
1473
|
+
const accessFeature = ensureFeature(import_sdk.requestPhoneAccess);
|
|
1474
|
+
if (accessFeature.ok) {
|
|
1475
|
+
try {
|
|
1476
|
+
await accessFeature.value;
|
|
1477
|
+
} catch (error) {
|
|
1478
|
+
console.warn("[tvm-app-adapter] Telegram requestPhone access failed:", error);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
try {
|
|
1483
|
+
const result = await contactFeature.value;
|
|
1484
|
+
if (!result || typeof result !== "object") {
|
|
1485
|
+
return null;
|
|
1486
|
+
}
|
|
1487
|
+
const contact = result.contact;
|
|
1488
|
+
const phone = contact?.phoneNumber ?? contact?.phone_number ?? contact?.phone ?? result.phoneNumber ?? result.phone_number ?? result.phone;
|
|
1489
|
+
return typeof phone === "string" && phone ? phone : null;
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
console.warn("[tvm-app-adapter] Telegram requestPhone failed:", error);
|
|
1492
|
+
return null;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
setupAppearanceWatcher() {
|
|
1496
|
+
this.appearanceWatcherDispose?.();
|
|
1497
|
+
if (typeof import_sdk_react.themeParams.isDark?.sub === "function") {
|
|
1498
|
+
const disposer = import_sdk_react.themeParams.isDark.sub(() => {
|
|
1499
|
+
const appearance = import_sdk_react.themeParams.isDark() ? "dark" : "light";
|
|
1500
|
+
this.environment.appearance = appearance;
|
|
1501
|
+
this.notifyAppearance(appearance);
|
|
1502
|
+
});
|
|
1503
|
+
this.appearanceWatcherDispose = this.registerDisposable(disposer);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
notifyAppearance(appearance) {
|
|
1507
|
+
for (const listener of this.appearanceListeners) {
|
|
1508
|
+
listener(appearance);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
setupActiveWatcher() {
|
|
1512
|
+
this.activeWatcherDispose?.();
|
|
1513
|
+
const activeSignal = import_sdk_react.miniApp.isActive;
|
|
1514
|
+
const invoke = () => {
|
|
1515
|
+
try {
|
|
1516
|
+
const isActive = import_sdk_react.miniApp.isActive();
|
|
1517
|
+
if (isActive) {
|
|
1518
|
+
this.notifyViewRestore();
|
|
1519
|
+
} else {
|
|
1520
|
+
this.notifyViewHide();
|
|
1521
|
+
}
|
|
1522
|
+
} catch (error) {
|
|
1523
|
+
console.warn("[tvm-app-adapter] miniApp.isActive() failed:", error);
|
|
1524
|
+
}
|
|
1525
|
+
};
|
|
1526
|
+
if (typeof activeSignal?.sub === "function") {
|
|
1527
|
+
const disposer = activeSignal.sub(() => invoke());
|
|
1528
|
+
this.activeWatcherDispose = this.registerDisposable(disposer);
|
|
1529
|
+
invoke();
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
try {
|
|
1533
|
+
invoke();
|
|
1534
|
+
} catch {
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
async prepareViewport() {
|
|
1538
|
+
try {
|
|
1539
|
+
await bindViewportCssVars({
|
|
1540
|
+
...this.getViewportMountOptions(),
|
|
1541
|
+
bindCssVars: typeof import_sdk_react.viewport.bindCssVars === "function" ? import_sdk_react.viewport.bindCssVars : void 0
|
|
1542
|
+
});
|
|
1543
|
+
} catch (error) {
|
|
1544
|
+
console.warn("[tvm-app-adapter] prepareViewport failed:", error);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
async requestFullscreenInternal() {
|
|
1548
|
+
try {
|
|
1549
|
+
const viewportOptions = this.getViewportMountOptions();
|
|
1550
|
+
await ensureViewportMounted(viewportOptions);
|
|
1551
|
+
const { sdkViewport } = viewportOptions;
|
|
1552
|
+
const canUseRaw = typeof sdkViewport.isSupported === "function" ? sdkViewport.isSupported() : false;
|
|
1553
|
+
if (canUseRaw && typeof sdkViewport.requestFullscreen === "function") {
|
|
1554
|
+
await sdkViewport.requestFullscreen();
|
|
1555
|
+
} else if (import_sdk_react.viewport.requestFullscreen && import_sdk_react.viewport.requestFullscreen.isAvailable?.()) {
|
|
1556
|
+
await import_sdk_react.viewport.requestFullscreen();
|
|
1557
|
+
} else {
|
|
1558
|
+
(0, import_sdk_react.postEvent)("web_app_request_fullscreen");
|
|
1559
|
+
}
|
|
1560
|
+
this.disableVerticalSwipes();
|
|
1561
|
+
} catch (error) {
|
|
1562
|
+
console.warn("[tvm-app-adapter] Telegram requestFullscreen failed:", error);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
getViewportMountOptions() {
|
|
1566
|
+
const sdkViewport = import_sdk.viewport;
|
|
1567
|
+
return {
|
|
1568
|
+
sdkViewport,
|
|
1569
|
+
fallbackMount: async () => {
|
|
1570
|
+
if (import_sdk_react.viewport.mount?.isAvailable?.()) {
|
|
1571
|
+
await import_sdk_react.viewport.mount();
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
notifyViewHide() {
|
|
1577
|
+
for (const listener of this.viewHideListeners) {
|
|
1578
|
+
try {
|
|
1579
|
+
listener();
|
|
1580
|
+
} catch (error) {
|
|
1581
|
+
console.warn("[tvm-app-adapter] onViewHide listener failed:", error);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
notifyViewRestore() {
|
|
1586
|
+
for (const listener of this.viewRestoreListeners) {
|
|
1587
|
+
try {
|
|
1588
|
+
listener();
|
|
1589
|
+
} catch (error) {
|
|
1590
|
+
console.warn("[tvm-app-adapter] onViewRestore listener failed:", error);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
onDestroy() {
|
|
1595
|
+
this.appearanceWatcherDispose?.();
|
|
1596
|
+
this.appearanceWatcherDispose = void 0;
|
|
1597
|
+
this.activeWatcherDispose?.();
|
|
1598
|
+
this.activeWatcherDispose = void 0;
|
|
1599
|
+
this.appearanceListeners.clear();
|
|
1600
|
+
this.viewHideListeners.clear();
|
|
1601
|
+
this.viewRestoreListeners.clear();
|
|
1602
|
+
this.backHandlers.clear();
|
|
1603
|
+
super.onDestroy();
|
|
1604
|
+
}
|
|
1605
|
+
};
|
|
1606
|
+
|
|
1607
|
+
// src/adapters/vkAdapter.ts
|
|
1608
|
+
var import_vk_bridge = __toESM(require("@vkontakte/vk-bridge"), 1);
|
|
1609
|
+
|
|
1610
|
+
// src/config/vkAnalytics.ts
|
|
1611
|
+
var pixelCode = null;
|
|
1612
|
+
function setVkPixelCode(next) {
|
|
1613
|
+
if (typeof next !== "string") {
|
|
1614
|
+
pixelCode = null;
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
const normalized = next.trim();
|
|
1618
|
+
pixelCode = normalized || null;
|
|
1619
|
+
}
|
|
1620
|
+
function getVkPixelCode() {
|
|
1621
|
+
return pixelCode;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// src/lib/bridge.ts
|
|
1625
|
+
async function isBridgeMethodSupported(method, supportsAsync) {
|
|
1626
|
+
if (typeof supportsAsync !== "function") {
|
|
1627
|
+
return false;
|
|
1628
|
+
}
|
|
1629
|
+
try {
|
|
1630
|
+
return await supportsAsync(method);
|
|
1631
|
+
} catch (error) {
|
|
1632
|
+
console.warn("[tvm-app-adapter] bridge.supportsAsync failed:", error);
|
|
1633
|
+
return false;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// src/adapters/vkAdapter.ts
|
|
1638
|
+
var ANALYTICS_EVENT_NAME_PATTERN = /^[a-z0-9][a-z0-9_.:-]{0,63}$/i;
|
|
1639
|
+
var ANALYTICS_FALLBACK_EVENT = "VK_ANALYTICS_EVENT";
|
|
1640
|
+
var VKMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
1641
|
+
constructor() {
|
|
1642
|
+
super("vk");
|
|
1643
|
+
__publicField(this, "configSafeArea");
|
|
1644
|
+
__publicField(this, "stopViewportTracking");
|
|
1645
|
+
__publicField(this, "supportsAsync", typeof import_vk_bridge.default.supportsAsync === "function" ? import_vk_bridge.default.supportsAsync.bind(import_vk_bridge.default) : void 0);
|
|
1646
|
+
__publicField(this, "pixelCodeWarningShown", false);
|
|
1647
|
+
__publicField(this, "unsubscribe");
|
|
1648
|
+
__publicField(this, "launchParams");
|
|
1649
|
+
__publicField(this, "queryParams");
|
|
1650
|
+
__publicField(this, "viewHideListeners", /* @__PURE__ */ new Set());
|
|
1651
|
+
__publicField(this, "viewRestoreListeners", /* @__PURE__ */ new Set());
|
|
1652
|
+
}
|
|
1653
|
+
computeSafeArea() {
|
|
1654
|
+
const baseSafeArea = this.computeBaseSafeArea();
|
|
1655
|
+
const overlayInsets = this.resolveOverlayInsets();
|
|
1656
|
+
if (overlayInsets) {
|
|
1657
|
+
return computeCombinedSafeArea({
|
|
1658
|
+
environment: baseSafeArea,
|
|
1659
|
+
minimum: overlayInsets
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
return baseSafeArea;
|
|
1663
|
+
}
|
|
1664
|
+
async init(_options) {
|
|
1665
|
+
if (this.ready) {
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
const handler = (event) => this.handleBridgeEvent(event);
|
|
1669
|
+
import_vk_bridge.default.subscribe(handler);
|
|
1670
|
+
this.unsubscribe = this.registerDisposable(() => import_vk_bridge.default.unsubscribe(handler));
|
|
1671
|
+
let initialConfig;
|
|
1672
|
+
try {
|
|
1673
|
+
initialConfig = await import_vk_bridge.default.send("VKWebAppGetConfig");
|
|
1674
|
+
} catch (error) {
|
|
1675
|
+
console.warn("[tvm-app-adapter] VKWebAppGetConfig failed:", error);
|
|
1676
|
+
}
|
|
1677
|
+
try {
|
|
1678
|
+
const initResult = await import_vk_bridge.default.send("VKWebAppInit");
|
|
1679
|
+
if (initResult && "result" in initResult && initResult.result === false) {
|
|
1680
|
+
console.warn("[tvm-app-adapter] VKWebAppInit returned result=false.");
|
|
1681
|
+
}
|
|
1682
|
+
} catch (error) {
|
|
1683
|
+
console.error("[tvm-app-adapter] VKWebAppInit failed:", error);
|
|
1684
|
+
this.unsubscribe?.();
|
|
1685
|
+
this.unsubscribe = void 0;
|
|
1686
|
+
throw error;
|
|
1687
|
+
}
|
|
1688
|
+
let launchParams;
|
|
1689
|
+
try {
|
|
1690
|
+
launchParams = await import_vk_bridge.default.send("VKWebAppGetLaunchParams");
|
|
1691
|
+
} catch (error) {
|
|
1692
|
+
console.error("[tvm-app-adapter] VKWebAppGetLaunchParams failed:", error);
|
|
1693
|
+
this.unsubscribe?.();
|
|
1694
|
+
this.unsubscribe = void 0;
|
|
1695
|
+
throw error;
|
|
1696
|
+
}
|
|
1697
|
+
const search = typeof window !== "undefined" ? window.location.search : "";
|
|
1698
|
+
const queryParams = (0, import_vk_bridge.parseURLSearchParamsForGetLaunchParams)(search);
|
|
1699
|
+
this.launchParams = launchParams;
|
|
1700
|
+
this.queryParams = queryParams;
|
|
1701
|
+
this.environment = this.composeEnvironment(launchParams, queryParams, initialConfig);
|
|
1702
|
+
this.configSafeArea = this.environment.safeArea;
|
|
1703
|
+
const combinedSafeArea = this.computeSafeArea();
|
|
1704
|
+
this.environment.safeArea = combinedSafeArea;
|
|
1705
|
+
this.applyAppearance(this.environment.appearance, initialConfig?.scheme);
|
|
1706
|
+
this.notifyEnvironmentChanged();
|
|
1707
|
+
this.ready = true;
|
|
1708
|
+
this.startViewportTracking();
|
|
1709
|
+
}
|
|
1710
|
+
async vibrateImpact(style) {
|
|
1711
|
+
if (await this.supportsBridgeMethod("VKWebAppTapticImpactOccurred")) {
|
|
1712
|
+
import_vk_bridge.default.send("VKWebAppTapticImpactOccurred", { style });
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
async vibrateNotification(type) {
|
|
1716
|
+
if (await this.supportsBridgeMethod("VKWebAppTapticNotificationOccurred")) {
|
|
1717
|
+
import_vk_bridge.default.send("VKWebAppTapticNotificationOccurred", { type });
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
async vibrateSelection() {
|
|
1721
|
+
if (await this.supportsBridgeMethod("VKWebAppTapticSelectionChanged")) {
|
|
1722
|
+
import_vk_bridge.default.send("VKWebAppTapticSelectionChanged");
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
async setColors(colors) {
|
|
1726
|
+
const { header, background } = colors;
|
|
1727
|
+
if (header || background) {
|
|
1728
|
+
const canApplyViewSettings = await this.supportsBridgeMethod("VKWebAppSetViewSettings");
|
|
1729
|
+
if (canApplyViewSettings) {
|
|
1730
|
+
const statusBarStyle = header ? this.resolveStatusBarStyle(header) : this.environment.appearance?.includes("dark") ? "light" : "dark";
|
|
1731
|
+
await import_vk_bridge.default.send("VKWebAppSetViewSettings", {
|
|
1732
|
+
status_bar_style: statusBarStyle,
|
|
1733
|
+
...header ? { action_bar_color: header } : {},
|
|
1734
|
+
...background ? { navigation_bar_color: background } : {}
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
await super.setColors(colors);
|
|
1739
|
+
}
|
|
1740
|
+
getEnvironment() {
|
|
1741
|
+
return {
|
|
1742
|
+
...this.environment,
|
|
1743
|
+
isWebView: import_vk_bridge.default.isWebView()
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
getLaunchParams() {
|
|
1747
|
+
if (!this.launchParams) {
|
|
1748
|
+
return void 0;
|
|
1749
|
+
}
|
|
1750
|
+
return {
|
|
1751
|
+
launchParams: this.launchParams,
|
|
1752
|
+
queryParams: this.queryParams
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
async supports(capability) {
|
|
1756
|
+
if (capability === "requestPhone") {
|
|
1757
|
+
const [supportsPhoneNumber, supportsPersonalCard] = await Promise.all([
|
|
1758
|
+
this.supportsBridgeMethod("VKWebAppGetPhoneNumber"),
|
|
1759
|
+
this.supportsBridgeMethod("VKWebAppGetPersonalCard")
|
|
1760
|
+
]);
|
|
1761
|
+
return supportsPhoneNumber || supportsPersonalCard;
|
|
1762
|
+
}
|
|
1763
|
+
if (capability === "notifications") {
|
|
1764
|
+
return this.supportsBridgeMethod("VKWebAppAllowNotifications");
|
|
1765
|
+
}
|
|
1766
|
+
return await super.supports(capability);
|
|
1767
|
+
}
|
|
1768
|
+
async requestPhone() {
|
|
1769
|
+
const [supportsPhoneNumber, supportsPersonalCard] = await Promise.all([
|
|
1770
|
+
this.supportsBridgeMethod("VKWebAppGetPhoneNumber"),
|
|
1771
|
+
this.supportsBridgeMethod("VKWebAppGetPersonalCard")
|
|
1772
|
+
]);
|
|
1773
|
+
if (!supportsPhoneNumber && !supportsPersonalCard) {
|
|
1774
|
+
return super.requestPhone();
|
|
1775
|
+
}
|
|
1776
|
+
try {
|
|
1777
|
+
if (supportsPhoneNumber) {
|
|
1778
|
+
const result = await import_vk_bridge.default.send("VKWebAppGetPhoneNumber");
|
|
1779
|
+
const phoneNumber = result.phone_number;
|
|
1780
|
+
return typeof phoneNumber === "string" && phoneNumber ? phoneNumber : null;
|
|
1781
|
+
}
|
|
1782
|
+
const card = await import_vk_bridge.default.send("VKWebAppGetPersonalCard", { type: ["phone"] });
|
|
1783
|
+
const phone = card.phone;
|
|
1784
|
+
return typeof phone === "string" && phone ? phone : null;
|
|
1785
|
+
} catch (error) {
|
|
1786
|
+
console.warn("[tvm-app-adapter] VK requestPhone failed:", error);
|
|
1787
|
+
return null;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
async requestNotificationsPermission() {
|
|
1791
|
+
const supported = await this.supportsBridgeMethod("VKWebAppAllowNotifications");
|
|
1792
|
+
if (!supported) {
|
|
1793
|
+
return super.requestNotificationsPermission();
|
|
1794
|
+
}
|
|
1795
|
+
try {
|
|
1796
|
+
const response = await import_vk_bridge.default.send("VKWebAppAllowNotifications");
|
|
1797
|
+
if (response && typeof response === "object" && "result" in response) {
|
|
1798
|
+
return Boolean(response.result);
|
|
1799
|
+
}
|
|
1800
|
+
return true;
|
|
1801
|
+
} catch (error) {
|
|
1802
|
+
console.warn("[tvm-app-adapter] VK allow notifications failed:", error);
|
|
1803
|
+
return false;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
async scanQRCode(options) {
|
|
1807
|
+
const supportsQrScanner = await this.supportsBridgeMethod("VKWebAppOpenCodeReader");
|
|
1808
|
+
if (!supportsQrScanner) {
|
|
1809
|
+
return super.scanQRCode(options);
|
|
1810
|
+
}
|
|
1811
|
+
let result = null;
|
|
1812
|
+
try {
|
|
1813
|
+
const data = await import_vk_bridge.default.send("VKWebAppOpenCodeReader");
|
|
1814
|
+
if (data.code_data) {
|
|
1815
|
+
result = data.code_data;
|
|
1816
|
+
}
|
|
1817
|
+
} catch (error) {
|
|
1818
|
+
console.log(error);
|
|
1819
|
+
}
|
|
1820
|
+
return result;
|
|
1821
|
+
}
|
|
1822
|
+
onViewHide(callback) {
|
|
1823
|
+
this.viewHideListeners.add(callback);
|
|
1824
|
+
return () => {
|
|
1825
|
+
this.viewHideListeners.delete(callback);
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
onViewRestore(callback) {
|
|
1829
|
+
this.viewRestoreListeners.add(callback);
|
|
1830
|
+
return () => {
|
|
1831
|
+
this.viewRestoreListeners.delete(callback);
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
async shareStory(mediaUrl, _options) {
|
|
1835
|
+
const bridgeOptions = {
|
|
1836
|
+
background_type: "image",
|
|
1837
|
+
url: mediaUrl
|
|
1838
|
+
};
|
|
1839
|
+
await import_vk_bridge.default.send("VKWebAppShowStoryBox", bridgeOptions);
|
|
1840
|
+
}
|
|
1841
|
+
async downloadFile(url, filename) {
|
|
1842
|
+
const supported = await this.supportsBridgeMethod("VKWebAppDownloadFile");
|
|
1843
|
+
if (!supported) {
|
|
1844
|
+
await super.downloadFile(url, filename);
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
try {
|
|
1848
|
+
const response = await import_vk_bridge.default.send("VKWebAppDownloadFile", { url, filename });
|
|
1849
|
+
const result = response?.result;
|
|
1850
|
+
if (result === false) {
|
|
1851
|
+
throw new Error("VKWebAppDownloadFile returned result=false");
|
|
1852
|
+
}
|
|
1853
|
+
} catch (error) {
|
|
1854
|
+
console.warn("[tvm-app-adapter] VK downloadFile failed:", error);
|
|
1855
|
+
await super.downloadFile(url, filename);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
trackConversionEvent(event, payload) {
|
|
1859
|
+
const normalizedEvent = this.normalizeAnalyticsEventName(event);
|
|
1860
|
+
if (!normalizedEvent) {
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
const envelope = {
|
|
1864
|
+
method: "VKWebAppConversionHit",
|
|
1865
|
+
params: {
|
|
1866
|
+
event: normalizedEvent,
|
|
1867
|
+
params: this.normalizeAnalyticsPayload(payload)
|
|
1868
|
+
}
|
|
1869
|
+
};
|
|
1870
|
+
this.dispatchAnalytics(envelope);
|
|
1871
|
+
}
|
|
1872
|
+
trackPixelEvent(event, payload) {
|
|
1873
|
+
const pixelCode2 = getVkPixelCode();
|
|
1874
|
+
if (!pixelCode2) {
|
|
1875
|
+
if (!this.pixelCodeWarningShown) {
|
|
1876
|
+
console.warn("[VKAnalytics] VK pixel code is not configured. Call configureVkPixel() before tracking.");
|
|
1877
|
+
this.pixelCodeWarningShown = true;
|
|
1878
|
+
}
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
const normalizedEvent = this.normalizeAnalyticsEventName(event);
|
|
1882
|
+
if (!normalizedEvent) {
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
const envelope = {
|
|
1886
|
+
method: "VKWebAppRetargetingPixel",
|
|
1887
|
+
params: {
|
|
1888
|
+
pixel_code: pixelCode2,
|
|
1889
|
+
type: normalizedEvent,
|
|
1890
|
+
data: this.normalizeAnalyticsPayload(payload)
|
|
1891
|
+
}
|
|
1892
|
+
};
|
|
1893
|
+
this.pixelCodeWarningShown = false;
|
|
1894
|
+
this.dispatchAnalytics(envelope);
|
|
1895
|
+
}
|
|
1896
|
+
dispatchAnalytics(envelope) {
|
|
1897
|
+
if (typeof import_vk_bridge.default.isWebView === "function") {
|
|
1898
|
+
try {
|
|
1899
|
+
if (!import_vk_bridge.default.isWebView()) {
|
|
1900
|
+
this.emitAnalyticsFallback(envelope);
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
} catch (error) {
|
|
1904
|
+
console.warn("[VKAnalytics] bridge.isWebView check failed:", error);
|
|
1905
|
+
this.emitAnalyticsFallback(envelope);
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
void this.safeBridgeSend(envelope.method, envelope.params);
|
|
1910
|
+
}
|
|
1911
|
+
emitAnalyticsFallback(envelope) {
|
|
1912
|
+
if (typeof window === "undefined") {
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
const detail = {
|
|
1916
|
+
...envelope,
|
|
1917
|
+
timestamp: Date.now()
|
|
1918
|
+
};
|
|
1919
|
+
if (typeof window.dispatchEvent === "function" && typeof window.CustomEvent === "function") {
|
|
1920
|
+
window.dispatchEvent(new CustomEvent(ANALYTICS_FALLBACK_EVENT, { detail }));
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1923
|
+
try {
|
|
1924
|
+
window.postMessage({ type: ANALYTICS_FALLBACK_EVENT, detail }, "*");
|
|
1925
|
+
} catch (error) {
|
|
1926
|
+
console.warn("[VKAnalytics] fallback dispatch failed", error);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
normalizeAnalyticsEventName(event) {
|
|
1930
|
+
if (typeof event !== "string") {
|
|
1931
|
+
return null;
|
|
1932
|
+
}
|
|
1933
|
+
const trimmed = event.trim();
|
|
1934
|
+
if (!trimmed || !ANALYTICS_EVENT_NAME_PATTERN.test(trimmed)) {
|
|
1935
|
+
console.warn(`[VKAnalytics] Invalid event name: "${event}"`);
|
|
1936
|
+
return null;
|
|
1937
|
+
}
|
|
1938
|
+
return trimmed;
|
|
1939
|
+
}
|
|
1940
|
+
normalizeAnalyticsPayload(payload) {
|
|
1941
|
+
if (!this.isPlainObject(payload)) {
|
|
1942
|
+
return {};
|
|
1943
|
+
}
|
|
1944
|
+
return { ...payload };
|
|
1945
|
+
}
|
|
1946
|
+
isPlainObject(value) {
|
|
1947
|
+
if (value === null || typeof value !== "object") {
|
|
1948
|
+
return false;
|
|
1949
|
+
}
|
|
1950
|
+
if (Array.isArray(value)) {
|
|
1951
|
+
return false;
|
|
1952
|
+
}
|
|
1953
|
+
const proto = Object.getPrototypeOf(value);
|
|
1954
|
+
return proto === Object.prototype || proto === null;
|
|
1955
|
+
}
|
|
1956
|
+
async safeBridgeSend(method, params) {
|
|
1957
|
+
try {
|
|
1958
|
+
const supported = await this.supportsBridgeMethod(method);
|
|
1959
|
+
if (!supported) {
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
await import_vk_bridge.default.send(method, params);
|
|
1963
|
+
} catch (error) {
|
|
1964
|
+
console.warn(`[VKAnalytics] ${method} failed`, error);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
composeEnvironment(launchParams, queryParams, config) {
|
|
1968
|
+
const language = queryParams.vk_language ?? launchParams.vk_language;
|
|
1969
|
+
const platform = queryParams.vk_platform ?? launchParams.vk_platform;
|
|
1970
|
+
const appId = queryParams.vk_app_id ?? launchParams.vk_app_id;
|
|
1971
|
+
const prefersDark = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-color-scheme: dark)").matches : false;
|
|
1972
|
+
const appearance = this.normalizeAppearance(config?.appearance, config?.scheme) ?? (prefersDark ? "dark" : "light");
|
|
1973
|
+
const configSafeArea = this.extractSafeAreaFromConfig(config);
|
|
1974
|
+
return {
|
|
1975
|
+
platform: "vk",
|
|
1976
|
+
sdkVersion: platform ? String(platform) : void 0,
|
|
1977
|
+
appVersion: typeof appId === "number" ? `vk-app-${appId}` : void 0,
|
|
1978
|
+
languageCode: language ? String(language) : void 0,
|
|
1979
|
+
appearance,
|
|
1980
|
+
isWebView: import_vk_bridge.default.isWebView(),
|
|
1981
|
+
safeArea: configSafeArea
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
handleBridgeEvent(event) {
|
|
1985
|
+
const { type, data } = event.detail ?? {};
|
|
1986
|
+
if (type === "VKWebAppViewHide") {
|
|
1987
|
+
this.notifyVisibilityListeners(this.viewHideListeners);
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
if (type === "VKWebAppViewRestore") {
|
|
1991
|
+
this.notifyVisibilityListeners(this.viewRestoreListeners);
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
if (type === "VKWebAppUpdateConfig" && data) {
|
|
1995
|
+
const config = data;
|
|
1996
|
+
this.updateEnvironmentFromConfig(config);
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
updateEnvironmentFromConfig(config) {
|
|
2000
|
+
if (!this.environment) {
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
const nextAppearance = this.normalizeAppearance(config.appearance, config.scheme);
|
|
2004
|
+
let changed = false;
|
|
2005
|
+
if (nextAppearance && nextAppearance !== this.environment.appearance) {
|
|
2006
|
+
this.environment.appearance = nextAppearance;
|
|
2007
|
+
changed = true;
|
|
2008
|
+
}
|
|
2009
|
+
const prevSafeArea = this.environment.safeArea;
|
|
2010
|
+
const nextSafeArea = this.extractSafeAreaFromConfig(config);
|
|
2011
|
+
if (nextSafeArea) {
|
|
2012
|
+
this.configSafeArea = nextSafeArea;
|
|
2013
|
+
this.environment.safeArea = nextSafeArea;
|
|
2014
|
+
} else {
|
|
2015
|
+
this.configSafeArea = void 0;
|
|
2016
|
+
this.environment.safeArea = void 0;
|
|
2017
|
+
}
|
|
2018
|
+
const combinedSafeArea = this.computeSafeArea() ?? {
|
|
2019
|
+
top: 0,
|
|
2020
|
+
right: 0,
|
|
2021
|
+
bottom: 0,
|
|
2022
|
+
left: 0
|
|
2023
|
+
};
|
|
2024
|
+
const prevForComparison = prevSafeArea;
|
|
2025
|
+
const safeAreaChanged = !prevForComparison || prevForComparison.top !== combinedSafeArea.top || prevForComparison.right !== combinedSafeArea.right || prevForComparison.bottom !== combinedSafeArea.bottom || prevForComparison.left !== combinedSafeArea.left;
|
|
2026
|
+
if (safeAreaChanged) {
|
|
2027
|
+
this.environment.safeArea = combinedSafeArea;
|
|
2028
|
+
changed = true;
|
|
2029
|
+
} else {
|
|
2030
|
+
this.environment.safeArea = prevSafeArea;
|
|
2031
|
+
}
|
|
2032
|
+
this.applyAppearance(this.environment.appearance, config.scheme);
|
|
2033
|
+
if (changed) {
|
|
2034
|
+
this.notifyEnvironmentChanged();
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
applyAppearance(appearance, scheme) {
|
|
2038
|
+
if (typeof document === "undefined") {
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
if (appearance) {
|
|
2042
|
+
document.documentElement.dataset.vkAppearance = appearance;
|
|
2043
|
+
document.documentElement.classList.toggle("dark", appearance === "dark");
|
|
2044
|
+
}
|
|
2045
|
+
if (scheme) {
|
|
2046
|
+
document.documentElement.dataset.vkScheme = scheme;
|
|
2047
|
+
if (!appearance) {
|
|
2048
|
+
const normalized = this.normalizeAppearance(void 0, scheme);
|
|
2049
|
+
document.documentElement.classList.toggle("dark", normalized === "dark");
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
resolveStatusBarStyle(color) {
|
|
2054
|
+
const hex = color.replace("#", "");
|
|
2055
|
+
const normalized = hex.length === 3 ? hex.split("").map((symbol) => symbol + symbol).join("") : hex.slice(0, 6);
|
|
2056
|
+
const r = parseInt(normalized.slice(0, 2), 16) / 255;
|
|
2057
|
+
const g = parseInt(normalized.slice(2, 4), 16) / 255;
|
|
2058
|
+
const b = parseInt(normalized.slice(4, 6), 16) / 255;
|
|
2059
|
+
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
2060
|
+
return luminance > 0.6 ? "dark" : "light";
|
|
2061
|
+
}
|
|
2062
|
+
normalizeAppearance(rawAppearance, scheme) {
|
|
2063
|
+
const normalized = rawAppearance?.toLowerCase();
|
|
2064
|
+
if (normalized === "dark" || normalized === "light") {
|
|
2065
|
+
return normalized;
|
|
2066
|
+
}
|
|
2067
|
+
const normalizedScheme = scheme?.toLowerCase();
|
|
2068
|
+
if (normalizedScheme) {
|
|
2069
|
+
if (normalizedScheme.includes("dark") || normalizedScheme.includes("space_gray")) {
|
|
2070
|
+
return "dark";
|
|
2071
|
+
}
|
|
2072
|
+
return "light";
|
|
2073
|
+
}
|
|
2074
|
+
return void 0;
|
|
2075
|
+
}
|
|
2076
|
+
extractSafeAreaFromConfig(config) {
|
|
2077
|
+
const rawInsets = config && "insets" in config ? config.insets : void 0;
|
|
2078
|
+
if (!rawInsets) {
|
|
2079
|
+
return void 0;
|
|
2080
|
+
}
|
|
2081
|
+
const { top = 0, right = 0, bottom = 0, left = 0 } = rawInsets;
|
|
2082
|
+
const values = [top, right, bottom, left].map((value) => typeof value === "number" ? value : Number(value) || 0);
|
|
2083
|
+
const hasInsets = values.some((value) => value !== 0);
|
|
2084
|
+
if (!hasInsets) {
|
|
2085
|
+
return void 0;
|
|
2086
|
+
}
|
|
2087
|
+
const [nTop, nRight, nBottom, nLeft] = values;
|
|
2088
|
+
return { top: nTop, right: nRight, bottom: nBottom, left: nLeft };
|
|
2089
|
+
}
|
|
2090
|
+
notifyVisibilityListeners(listeners) {
|
|
2091
|
+
for (const listener of listeners) {
|
|
2092
|
+
try {
|
|
2093
|
+
listener();
|
|
2094
|
+
} catch (error) {
|
|
2095
|
+
console.warn("[tvm-app-adapter] VK visibility listener failed:", error);
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
computeBaseSafeArea() {
|
|
2100
|
+
return computeCombinedSafeArea({
|
|
2101
|
+
environment: this.configSafeArea,
|
|
2102
|
+
viewport: this.getViewportInsets?.(),
|
|
2103
|
+
css: readCssSafeArea()
|
|
2104
|
+
});
|
|
2105
|
+
}
|
|
2106
|
+
startViewportTracking() {
|
|
2107
|
+
this.stopViewportTracking?.();
|
|
2108
|
+
const dispose = createSafeAreaWatcher({
|
|
2109
|
+
getSafeArea: () => this.computeSafeArea(),
|
|
2110
|
+
onChange: (next) => {
|
|
2111
|
+
this.environment.safeArea = next;
|
|
2112
|
+
this.notifyEnvironmentChanged();
|
|
2113
|
+
}
|
|
2114
|
+
});
|
|
2115
|
+
if (dispose) {
|
|
2116
|
+
this.stopViewportTracking = this.registerDisposable(dispose);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
resolveOverlayInsets() {
|
|
2120
|
+
if (typeof window === "undefined") {
|
|
2121
|
+
return void 0;
|
|
2122
|
+
}
|
|
2123
|
+
if (!import_vk_bridge.default.isWebView()) {
|
|
2124
|
+
return void 0;
|
|
2125
|
+
}
|
|
2126
|
+
if (!this.isLikelyMobilePlatform()) {
|
|
2127
|
+
return void 0;
|
|
2128
|
+
}
|
|
2129
|
+
const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || 0;
|
|
2130
|
+
if (!viewportWidth) {
|
|
2131
|
+
return void 0;
|
|
2132
|
+
}
|
|
2133
|
+
const overlayBreakpoint = 880;
|
|
2134
|
+
if (viewportWidth > overlayBreakpoint) {
|
|
2135
|
+
return void 0;
|
|
2136
|
+
}
|
|
2137
|
+
const orientationQuery = window.matchMedia?.("(orientation: landscape)");
|
|
2138
|
+
const isLandscape = Boolean(orientationQuery?.matches);
|
|
2139
|
+
const top = isLandscape ? 48 : 56;
|
|
2140
|
+
const right = isLandscape ? 72 : 88;
|
|
2141
|
+
return { top, right };
|
|
2142
|
+
}
|
|
2143
|
+
isLikelyMobilePlatform() {
|
|
2144
|
+
const platform = (this.resolveLaunchParam("vk_platform") ?? "").toLowerCase();
|
|
2145
|
+
const device = (this.resolveLaunchParam("vk_viewer_device") ?? "").toLowerCase();
|
|
2146
|
+
const isLayer = this.resolveLaunchParam("vk_is_layer") === "1";
|
|
2147
|
+
if (isLayer) {
|
|
2148
|
+
return false;
|
|
2149
|
+
}
|
|
2150
|
+
const mobilePattern = /(iphone|ipad|ios|android|mobile)/i;
|
|
2151
|
+
const desktopPattern = /(desktop|web|tablet)/i;
|
|
2152
|
+
const matchesMobilePlatform = mobilePattern.test(platform) || mobilePattern.test(device);
|
|
2153
|
+
const matchesDesktopPlatform = desktopPattern.test(platform) || desktopPattern.test(device);
|
|
2154
|
+
if (!matchesMobilePlatform) {
|
|
2155
|
+
return false;
|
|
2156
|
+
}
|
|
2157
|
+
return !matchesDesktopPlatform;
|
|
2158
|
+
}
|
|
2159
|
+
resolveLaunchParam(key) {
|
|
2160
|
+
const queryValue = this.queryParams?.[key];
|
|
2161
|
+
if (typeof queryValue === "string" && queryValue) {
|
|
2162
|
+
return queryValue;
|
|
2163
|
+
}
|
|
2164
|
+
const launchValue = this.launchParams ? this.launchParams[key] : void 0;
|
|
2165
|
+
if (typeof launchValue === "string" && launchValue) {
|
|
2166
|
+
return launchValue;
|
|
2167
|
+
}
|
|
2168
|
+
if (typeof launchValue === "number") {
|
|
2169
|
+
return String(launchValue);
|
|
2170
|
+
}
|
|
2171
|
+
if (typeof launchValue === "boolean") {
|
|
2172
|
+
return launchValue ? "1" : "0";
|
|
2173
|
+
}
|
|
2174
|
+
return void 0;
|
|
2175
|
+
}
|
|
2176
|
+
supportsBridgeMethod(method) {
|
|
2177
|
+
return isBridgeMethodSupported(method, this.supportsAsync);
|
|
2178
|
+
}
|
|
2179
|
+
onDestroy() {
|
|
2180
|
+
this.unsubscribe?.();
|
|
2181
|
+
this.unsubscribe = void 0;
|
|
2182
|
+
this.stopViewportTracking?.();
|
|
2183
|
+
this.stopViewportTracking = void 0;
|
|
2184
|
+
this.viewHideListeners.clear();
|
|
2185
|
+
this.viewRestoreListeners.clear();
|
|
2186
|
+
super.onDestroy();
|
|
2187
|
+
}
|
|
2188
|
+
};
|
|
2189
|
+
|
|
2190
|
+
// src/adapters/webAdapter.ts
|
|
2191
|
+
var import_html5_qrcode = require("html5-qrcode");
|
|
2192
|
+
var WebMiniAppAdapter = class extends BaseMiniAppAdapter {
|
|
2193
|
+
constructor() {
|
|
2194
|
+
super("web", {
|
|
2195
|
+
sdkVersion: navigator.userAgent,
|
|
2196
|
+
languageCode: navigator.language,
|
|
2197
|
+
isWebView: false
|
|
2198
|
+
});
|
|
2199
|
+
}
|
|
2200
|
+
async downloadFile(url, filename) {
|
|
2201
|
+
try {
|
|
2202
|
+
await triggerFileDownload(url, filename, { preferBlob: true });
|
|
2203
|
+
} catch (error) {
|
|
2204
|
+
console.warn("[mini-app-template] Web downloadFile fallback:", error);
|
|
2205
|
+
await super.downloadFile(url, filename);
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
scanQRCode(_options) {
|
|
2209
|
+
return new Promise(async (resolve) => {
|
|
2210
|
+
if (typeof document === "undefined") {
|
|
2211
|
+
resolve(null);
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
const prevOverflow = document.body.style.overflow;
|
|
2215
|
+
const prevPos = document.body.style.position;
|
|
2216
|
+
const prevTouch = document.body.style.touchAction;
|
|
2217
|
+
const prevWidth = document.body.style.width;
|
|
2218
|
+
const prevHtmlOverflow = document.documentElement.style.overflow;
|
|
2219
|
+
document.body.style.overflow = "hidden";
|
|
2220
|
+
document.body.style.position = "fixed";
|
|
2221
|
+
document.body.style.width = "100%";
|
|
2222
|
+
document.body.style.touchAction = "none";
|
|
2223
|
+
document.documentElement.style.overflow = "hidden";
|
|
2224
|
+
const overlay = document.createElement("div");
|
|
2225
|
+
overlay.id = "qr-overlay";
|
|
2226
|
+
overlay.style.position = "fixed";
|
|
2227
|
+
overlay.style.top = "0";
|
|
2228
|
+
overlay.style.left = "0";
|
|
2229
|
+
overlay.style.right = "0";
|
|
2230
|
+
overlay.style.bottom = "0";
|
|
2231
|
+
overlay.style.zIndex = "999999999";
|
|
2232
|
+
overlay.style.background = "rgba(0,0,0,0.92)";
|
|
2233
|
+
overlay.style.width = "100%";
|
|
2234
|
+
overlay.style.height = "100%";
|
|
2235
|
+
overlay.style.display = "flex";
|
|
2236
|
+
overlay.style.flexDirection = "column";
|
|
2237
|
+
overlay.style.alignItems = "center";
|
|
2238
|
+
overlay.style.justifyContent = "center";
|
|
2239
|
+
overlay.style.overflow = "hidden";
|
|
2240
|
+
overlay.style.backdropFilter = "blur(3px)";
|
|
2241
|
+
document.body.appendChild(overlay);
|
|
2242
|
+
const closeBtn = document.createElement("button");
|
|
2243
|
+
closeBtn.innerText = "\u2715";
|
|
2244
|
+
closeBtn.style.position = "absolute";
|
|
2245
|
+
closeBtn.style.top = "22px";
|
|
2246
|
+
closeBtn.style.right = "22px";
|
|
2247
|
+
closeBtn.style.fontSize = "32px";
|
|
2248
|
+
closeBtn.style.color = "white";
|
|
2249
|
+
closeBtn.style.background = "transparent";
|
|
2250
|
+
closeBtn.style.border = "none";
|
|
2251
|
+
closeBtn.style.cursor = "pointer";
|
|
2252
|
+
closeBtn.style.zIndex = "9999999999";
|
|
2253
|
+
overlay.appendChild(closeBtn);
|
|
2254
|
+
const scanArea = document.createElement("div");
|
|
2255
|
+
scanArea.id = "qr-reader";
|
|
2256
|
+
scanArea.style.width = "300px";
|
|
2257
|
+
scanArea.style.height = "300px";
|
|
2258
|
+
scanArea.style.borderRadius = "18px";
|
|
2259
|
+
scanArea.style.overflow = "hidden";
|
|
2260
|
+
scanArea.style.position = "relative";
|
|
2261
|
+
overlay.appendChild(scanArea);
|
|
2262
|
+
const frame = document.createElement("div");
|
|
2263
|
+
frame.style.position = "absolute";
|
|
2264
|
+
frame.style.top = "0";
|
|
2265
|
+
frame.style.left = "0";
|
|
2266
|
+
frame.style.right = "0";
|
|
2267
|
+
frame.style.bottom = "0";
|
|
2268
|
+
frame.style.border = "3px solid rgba(255,255,255,0.9)";
|
|
2269
|
+
frame.style.borderRadius = "18px";
|
|
2270
|
+
frame.style.pointerEvents = "none";
|
|
2271
|
+
frame.style.zIndex = "10";
|
|
2272
|
+
scanArea.appendChild(frame);
|
|
2273
|
+
const line = document.createElement("div");
|
|
2274
|
+
line.style.position = "absolute";
|
|
2275
|
+
line.style.left = "0";
|
|
2276
|
+
line.style.right = "0";
|
|
2277
|
+
line.style.height = "2px";
|
|
2278
|
+
line.style.background = "rgba(255,255,255,0.85)";
|
|
2279
|
+
line.style.borderRadius = "2px";
|
|
2280
|
+
line.style.animation = "qr-line 2s infinite";
|
|
2281
|
+
line.style.zIndex = "11";
|
|
2282
|
+
scanArea.appendChild(line);
|
|
2283
|
+
const styleTag = document.createElement("style");
|
|
2284
|
+
styleTag.innerHTML = `
|
|
2285
|
+
@keyframes qr-line {
|
|
2286
|
+
0% { top: 0; }
|
|
2287
|
+
50% { top: calc(100% - 2px); }
|
|
2288
|
+
100% { top: 0; }
|
|
2289
|
+
}
|
|
2290
|
+
`;
|
|
2291
|
+
document.head.appendChild(styleTag);
|
|
2292
|
+
const hint = document.createElement("div");
|
|
2293
|
+
hint.innerText = "\u041D\u0430\u0432\u0435\u0434\u0438\u0442\u0435 \u043A\u0430\u043C\u0435\u0440\u0443 \u043D\u0430 QR-\u043A\u043E\u0434";
|
|
2294
|
+
hint.style.color = "white";
|
|
2295
|
+
hint.style.marginTop = "30px";
|
|
2296
|
+
hint.style.fontSize = "17px";
|
|
2297
|
+
hint.style.opacity = "0.9";
|
|
2298
|
+
overlay.appendChild(hint);
|
|
2299
|
+
const scanner = new import_html5_qrcode.Html5Qrcode("qr-reader");
|
|
2300
|
+
let closed = false;
|
|
2301
|
+
const finalize = async (result) => {
|
|
2302
|
+
if (closed) {
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
2305
|
+
closed = true;
|
|
2306
|
+
try {
|
|
2307
|
+
await scanner.stop();
|
|
2308
|
+
} catch {
|
|
2309
|
+
}
|
|
2310
|
+
overlay.remove();
|
|
2311
|
+
styleTag.remove();
|
|
2312
|
+
document.body.style.overflow = prevOverflow;
|
|
2313
|
+
document.body.style.position = prevPos;
|
|
2314
|
+
document.body.style.width = prevWidth;
|
|
2315
|
+
document.body.style.touchAction = prevTouch;
|
|
2316
|
+
document.documentElement.style.overflow = prevHtmlOverflow;
|
|
2317
|
+
resolve(result);
|
|
2318
|
+
};
|
|
2319
|
+
const removeFromBag = this.registerDisposable(() => {
|
|
2320
|
+
void finalize(null);
|
|
2321
|
+
});
|
|
2322
|
+
const closeScanner = (result) => {
|
|
2323
|
+
void finalize(result);
|
|
2324
|
+
removeFromBag();
|
|
2325
|
+
};
|
|
2326
|
+
closeBtn.onclick = () => closeScanner(null);
|
|
2327
|
+
try {
|
|
2328
|
+
await scanner.start(
|
|
2329
|
+
{ facingMode: "environment" },
|
|
2330
|
+
{
|
|
2331
|
+
fps: 10,
|
|
2332
|
+
qrbox: { width: 250, height: 250 },
|
|
2333
|
+
aspectRatio: 1
|
|
2334
|
+
},
|
|
2335
|
+
(decodedText) => {
|
|
2336
|
+
closeScanner(decodedText);
|
|
2337
|
+
},
|
|
2338
|
+
() => {
|
|
2339
|
+
}
|
|
2340
|
+
);
|
|
2341
|
+
} catch (error) {
|
|
2342
|
+
console.error("QR Start error", error);
|
|
2343
|
+
closeScanner(null);
|
|
2344
|
+
}
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
};
|
|
2348
|
+
|
|
2349
|
+
// src/adapters/index.ts
|
|
2350
|
+
function detectPlatform() {
|
|
2351
|
+
if (typeof window === "undefined") {
|
|
2352
|
+
return "web";
|
|
2353
|
+
}
|
|
2354
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
2355
|
+
const hashParams = (() => {
|
|
2356
|
+
const hash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
|
|
2357
|
+
return new URLSearchParams(hash);
|
|
2358
|
+
})();
|
|
2359
|
+
const getParam = (name) => searchParams.get(name) ?? hashParams.get(name);
|
|
2360
|
+
const hasParam = (...names) => names.some((name) => getParam(name));
|
|
2361
|
+
const shellPlatform = readShellPlatform();
|
|
2362
|
+
if (shellPlatform) {
|
|
2363
|
+
return shellPlatform;
|
|
2364
|
+
}
|
|
2365
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
2366
|
+
const hasNativeBridge = typeof window.NativeBridge?.postMessage === "function";
|
|
2367
|
+
if (hasNativeBridge) {
|
|
2368
|
+
if (userAgent.includes("android")) {
|
|
2369
|
+
return "shell_android";
|
|
2370
|
+
}
|
|
2371
|
+
return "shell_ios";
|
|
2372
|
+
}
|
|
2373
|
+
if (window.Telegram?.WebApp || hasParam("tgWebAppPlatform", "tgWebAppVersion", "tgWebAppData", "tgWebAppLanguage") || userAgent.includes("telegram")) {
|
|
2374
|
+
return "telegram";
|
|
2375
|
+
}
|
|
2376
|
+
if (window.WebApp) {
|
|
2377
|
+
return "max";
|
|
2378
|
+
}
|
|
2379
|
+
if (window.MaxMiniApp) {
|
|
2380
|
+
return "max";
|
|
2381
|
+
}
|
|
2382
|
+
if (hasParam("vk_app_id", "vk_platform")) {
|
|
2383
|
+
return "vk";
|
|
2384
|
+
}
|
|
2385
|
+
return "web";
|
|
2386
|
+
}
|
|
2387
|
+
function createAdapter(input) {
|
|
2388
|
+
const options = normalizeCreateAdapterOptions(input);
|
|
2389
|
+
const platform = options.platform ?? detectPlatform();
|
|
2390
|
+
if (platform === "vk") {
|
|
2391
|
+
setVkPixelCode(options.vk?.pixelCode ?? null);
|
|
2392
|
+
}
|
|
2393
|
+
switch (platform) {
|
|
2394
|
+
case "shell_ios":
|
|
2395
|
+
case "shell_android":
|
|
2396
|
+
return new ShellMiniAppAdapter(platform);
|
|
2397
|
+
case "telegram":
|
|
2398
|
+
return new TelegramMiniAppAdapter();
|
|
2399
|
+
case "vk":
|
|
2400
|
+
return new VKMiniAppAdapter();
|
|
2401
|
+
case "max":
|
|
2402
|
+
return new MaxMiniAppAdapter();
|
|
2403
|
+
default:
|
|
2404
|
+
return new WebMiniAppAdapter();
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
function normalizeCreateAdapterOptions(input) {
|
|
2408
|
+
if (!input) {
|
|
2409
|
+
return {};
|
|
2410
|
+
}
|
|
2411
|
+
if (typeof input === "string") {
|
|
2412
|
+
return { platform: input };
|
|
2413
|
+
}
|
|
2414
|
+
return input;
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
// src/components/AdapterProvider.tsx
|
|
2418
|
+
var import_react = require("react");
|
|
2419
|
+
|
|
2420
|
+
// src/registry.ts
|
|
2421
|
+
var currentAdapter = null;
|
|
2422
|
+
function setActiveAdapter(adapter) {
|
|
2423
|
+
currentAdapter = adapter;
|
|
2424
|
+
}
|
|
2425
|
+
function getActiveAdapter() {
|
|
2426
|
+
return currentAdapter;
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
// src/components/AdapterProvider.tsx
|
|
2430
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
2431
|
+
var AdapterContext = (0, import_react.createContext)(null);
|
|
2432
|
+
function AdapterProvider({ adapter, children }) {
|
|
2433
|
+
const proxiedAdapter = (0, import_react.useMemo)(() => {
|
|
2434
|
+
return new Proxy(adapter, {
|
|
2435
|
+
get(target, prop, receiver) {
|
|
2436
|
+
const value = Reflect.get(target, prop, receiver);
|
|
2437
|
+
if (typeof value === "function") {
|
|
2438
|
+
return value.bind(target);
|
|
2439
|
+
}
|
|
2440
|
+
return value;
|
|
2441
|
+
}
|
|
2442
|
+
});
|
|
2443
|
+
}, [adapter]);
|
|
2444
|
+
(0, import_react.useEffect)(() => {
|
|
2445
|
+
setActiveAdapter(proxiedAdapter);
|
|
2446
|
+
return () => {
|
|
2447
|
+
setActiveAdapter(null);
|
|
2448
|
+
if (typeof proxiedAdapter.destroy === "function") {
|
|
2449
|
+
try {
|
|
2450
|
+
proxiedAdapter.destroy();
|
|
2451
|
+
} catch (error) {
|
|
2452
|
+
console.warn("[tvm-app-adapter] adapter destroy failed:", error);
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
};
|
|
2456
|
+
}, [proxiedAdapter]);
|
|
2457
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AdapterContext.Provider, { value: proxiedAdapter, children });
|
|
2458
|
+
}
|
|
2459
|
+
function useMiniAppAdapter() {
|
|
2460
|
+
const adapter = (0, import_react.useContext)(AdapterContext);
|
|
2461
|
+
if (!adapter) {
|
|
2462
|
+
throw new Error("useMiniAppAdapter must be used inside <AdapterProvider/>.");
|
|
2463
|
+
}
|
|
2464
|
+
return adapter;
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
// src/hooks/useAdapterTheme.ts
|
|
2468
|
+
var import_react2 = require("react");
|
|
2469
|
+
var STORAGE_KEY = "loyalka-theme-preference";
|
|
2470
|
+
function useAdapterTheme() {
|
|
2471
|
+
const adapter = useMiniAppAdapter();
|
|
2472
|
+
const [systemPrefersDark, setSystemPrefersDark] = (0, import_react2.useState)(() => {
|
|
2473
|
+
const { appearance: appearance2 } = adapter.getEnvironment();
|
|
2474
|
+
if (adapter.onAppearanceChange && appearance2) {
|
|
2475
|
+
return appearance2 === "dark";
|
|
2476
|
+
}
|
|
2477
|
+
if (typeof window !== "undefined" && window.matchMedia) {
|
|
2478
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
2479
|
+
}
|
|
2480
|
+
return false;
|
|
2481
|
+
});
|
|
2482
|
+
const [preference, setPreferenceState] = (0, import_react2.useState)(() => {
|
|
2483
|
+
try {
|
|
2484
|
+
const saved = localStorage.getItem(STORAGE_KEY);
|
|
2485
|
+
if (saved === "light" || saved === "dark" || saved === "system") return saved;
|
|
2486
|
+
} catch {
|
|
2487
|
+
}
|
|
2488
|
+
return "system";
|
|
2489
|
+
});
|
|
2490
|
+
(0, import_react2.useEffect)(() => {
|
|
2491
|
+
if (adapter.onAppearanceChange) {
|
|
2492
|
+
return adapter.onAppearanceChange((appearance2) => {
|
|
2493
|
+
if (appearance2) {
|
|
2494
|
+
setSystemPrefersDark(appearance2 === "dark");
|
|
2495
|
+
}
|
|
2496
|
+
});
|
|
2497
|
+
}
|
|
2498
|
+
if (typeof window !== "undefined" && window.matchMedia) {
|
|
2499
|
+
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
2500
|
+
const handleChange = (event) => setSystemPrefersDark(event.matches);
|
|
2501
|
+
media.addEventListener("change", handleChange);
|
|
2502
|
+
return () => media.removeEventListener("change", handleChange);
|
|
2503
|
+
}
|
|
2504
|
+
return void 0;
|
|
2505
|
+
}, [adapter]);
|
|
2506
|
+
const isDark = (0, import_react2.useMemo)(() => {
|
|
2507
|
+
if (preference === "dark" || preference === "system" && systemPrefersDark) {
|
|
2508
|
+
return true;
|
|
2509
|
+
}
|
|
2510
|
+
return false;
|
|
2511
|
+
}, [preference, systemPrefersDark]);
|
|
2512
|
+
const appearance = isDark ? "dark" : "light";
|
|
2513
|
+
(0, import_react2.useEffect)(() => {
|
|
2514
|
+
const root = document.documentElement;
|
|
2515
|
+
if (isDark) {
|
|
2516
|
+
root.classList.add("dark");
|
|
2517
|
+
} else {
|
|
2518
|
+
root.classList.remove("dark");
|
|
2519
|
+
}
|
|
2520
|
+
}, [isDark]);
|
|
2521
|
+
const setPreference = (0, import_react2.useCallback)((pref) => {
|
|
2522
|
+
setPreferenceState(pref);
|
|
2523
|
+
try {
|
|
2524
|
+
localStorage.setItem(STORAGE_KEY, pref);
|
|
2525
|
+
} catch {
|
|
2526
|
+
}
|
|
2527
|
+
}, []);
|
|
2528
|
+
const toggle = (0, import_react2.useCallback)(() => {
|
|
2529
|
+
document.documentElement.classList.add("theme-transition");
|
|
2530
|
+
setTimeout(() => {
|
|
2531
|
+
setTimeout(() => {
|
|
2532
|
+
document.documentElement.classList.remove("theme-transition");
|
|
2533
|
+
}, 400);
|
|
2534
|
+
}, 50);
|
|
2535
|
+
const root = document.documentElement;
|
|
2536
|
+
const styles = getComputedStyle(root);
|
|
2537
|
+
const primaryColor = styles.getPropertyValue("--primary").trim();
|
|
2538
|
+
const backgroundColor = styles.getPropertyValue("--background").trim();
|
|
2539
|
+
adapter.setColors({
|
|
2540
|
+
header: primaryColor,
|
|
2541
|
+
background: backgroundColor,
|
|
2542
|
+
footer: backgroundColor
|
|
2543
|
+
});
|
|
2544
|
+
setPreference(preference === "dark" ? "light" : "dark");
|
|
2545
|
+
}, [adapter, preference, setPreference]);
|
|
2546
|
+
return {
|
|
2547
|
+
isDark,
|
|
2548
|
+
appearance,
|
|
2549
|
+
preference,
|
|
2550
|
+
setPreference,
|
|
2551
|
+
toggle
|
|
2552
|
+
};
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
// src/hooks/useSafeArea.ts
|
|
2556
|
+
var import_react3 = require("react");
|
|
2557
|
+
var ZERO_SAFE_AREA = {
|
|
2558
|
+
top: 0,
|
|
2559
|
+
right: 0,
|
|
2560
|
+
bottom: 0,
|
|
2561
|
+
left: 0
|
|
2562
|
+
};
|
|
2563
|
+
function useSafeArea() {
|
|
2564
|
+
const adapter = useMiniAppAdapter();
|
|
2565
|
+
const computeSafeArea = (0, import_react3.useCallback)(
|
|
2566
|
+
() => adapter.computeSafeArea() ?? ZERO_SAFE_AREA,
|
|
2567
|
+
[adapter]
|
|
2568
|
+
);
|
|
2569
|
+
const [safeArea, setSafeArea] = (0, import_react3.useState)(computeSafeArea);
|
|
2570
|
+
(0, import_react3.useEffect)(() => {
|
|
2571
|
+
setSafeArea(computeSafeArea());
|
|
2572
|
+
const unsubscribe = adapter.subscribe?.(() => {
|
|
2573
|
+
setSafeArea(computeSafeArea());
|
|
2574
|
+
});
|
|
2575
|
+
return () => unsubscribe?.();
|
|
2576
|
+
}, [adapter, computeSafeArea]);
|
|
2577
|
+
return safeArea ?? ZERO_SAFE_AREA;
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
// src/platform.ts
|
|
2581
|
+
var cachedPlatform = null;
|
|
2582
|
+
function getPlatform() {
|
|
2583
|
+
const adapter = getActiveAdapter();
|
|
2584
|
+
if (adapter) {
|
|
2585
|
+
cachedPlatform = adapter.platform;
|
|
2586
|
+
return adapter.platform;
|
|
2587
|
+
}
|
|
2588
|
+
if (cachedPlatform) {
|
|
2589
|
+
return cachedPlatform;
|
|
2590
|
+
}
|
|
2591
|
+
const detectedPlatform = detectPlatform();
|
|
2592
|
+
cachedPlatform = detectedPlatform;
|
|
2593
|
+
return detectedPlatform;
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
// src/analytics.ts
|
|
2597
|
+
function configureVkPixel(pixelCode2) {
|
|
2598
|
+
setVkPixelCode(pixelCode2);
|
|
2599
|
+
}
|
|
2600
|
+
function trackConversionEvent(event, payload) {
|
|
2601
|
+
const adapter = resolveVkAdapter();
|
|
2602
|
+
adapter?.trackConversionEvent(event, payload);
|
|
2603
|
+
}
|
|
2604
|
+
function trackPixelEvent(event, payload) {
|
|
2605
|
+
const adapter = resolveVkAdapter();
|
|
2606
|
+
adapter?.trackPixelEvent(event, payload);
|
|
2607
|
+
}
|
|
2608
|
+
function resolveVkAdapter() {
|
|
2609
|
+
const adapter = getActiveAdapter();
|
|
2610
|
+
if (adapter) {
|
|
2611
|
+
return adapter.platform === "vk" ? adapter : null;
|
|
2612
|
+
}
|
|
2613
|
+
if (getPlatform() !== "vk") {
|
|
2614
|
+
return null;
|
|
2615
|
+
}
|
|
2616
|
+
return null;
|
|
2617
|
+
}
|
|
2618
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2619
|
+
0 && (module.exports = {
|
|
2620
|
+
AdapterProvider,
|
|
2621
|
+
BaseMiniAppAdapter,
|
|
2622
|
+
MaxMiniAppAdapter,
|
|
2623
|
+
ShellMiniAppAdapter,
|
|
2624
|
+
TelegramMiniAppAdapter,
|
|
2625
|
+
VKMiniAppAdapter,
|
|
2626
|
+
WebMiniAppAdapter,
|
|
2627
|
+
configureVkPixel,
|
|
2628
|
+
createAdapter,
|
|
2629
|
+
createShellAPI,
|
|
2630
|
+
detectPlatform,
|
|
2631
|
+
getActiveAdapter,
|
|
2632
|
+
getPlatform,
|
|
2633
|
+
isShell,
|
|
2634
|
+
isShellAndroid,
|
|
2635
|
+
isShellIOS,
|
|
2636
|
+
readShellPlatform,
|
|
2637
|
+
requestShellPushPermission,
|
|
2638
|
+
shell,
|
|
2639
|
+
storeShellToken,
|
|
2640
|
+
trackConversionEvent,
|
|
2641
|
+
trackPixelEvent,
|
|
2642
|
+
useAdapterTheme,
|
|
2643
|
+
useMiniAppAdapter,
|
|
2644
|
+
useSafeArea
|
|
2645
|
+
});
|
|
2646
|
+
//# sourceMappingURL=index.cjs.map
|