nuxt-telegram-mini-app 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/dependabot.yml +6 -0
- package/README.md +45 -5
- package/app/composables/telegram.ts +370 -36
- package/app/pages/components.vue +283 -32
- package/app/types/telegram-webapp.ts +157 -3
- package/app/utils/color.ts +29 -5
- package/nuxt.config.ts +1 -1
- package/package.json +17 -17
- package/tests/pages.spec.ts +9 -12
- package/tests/telegram.spec.ts +1 -1
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ A comprehensive template for building Telegram Mini Apps using Nuxt 4, Vue 3, Ty
|
|
|
7
7
|

|
|
8
8
|

|
|
9
9
|

|
|
10
|
+

|
|
10
11
|
|
|
11
12
|
## ✨ Features
|
|
12
13
|
|
|
@@ -31,7 +32,9 @@ A comprehensive template for building Telegram Mini Apps using Nuxt 4, Vue 3, Ty
|
|
|
31
32
|
- **⬅️ Back Button** - Navigation back button control
|
|
32
33
|
- **👤 User Data** - Access to Telegram user information
|
|
33
34
|
- **🎨 Theme Integration** - Automatic Telegram theme colors
|
|
34
|
-
- **📐 Viewport Control** -
|
|
35
|
+
- **📐 Viewport Control** - Fullscreen, safe-area, and responsive viewport management
|
|
36
|
+
- **🏠 Home Screen Shortcuts** - Add/check Mini App shortcut support
|
|
37
|
+
- **📍 Device APIs** - Feature-detected storage, location, and motion access
|
|
35
38
|
- **🔗 Deep Linking** - External and Telegram link handling
|
|
36
39
|
- **📤 Sharing** - Built-in sharing functionality
|
|
37
40
|
|
|
@@ -159,7 +162,10 @@ import {
|
|
|
159
162
|
useBackButton,
|
|
160
163
|
useHapticFeedback,
|
|
161
164
|
useInitData,
|
|
162
|
-
useThemeParams
|
|
165
|
+
useThemeParams,
|
|
166
|
+
useViewport,
|
|
167
|
+
useHomeScreen,
|
|
168
|
+
useTelegramStorage
|
|
163
169
|
} from '~/composables/telegram'
|
|
164
170
|
|
|
165
171
|
const main = useMainButton()
|
|
@@ -167,6 +173,9 @@ const back = useBackButton()
|
|
|
167
173
|
const haptic = useHapticFeedback()
|
|
168
174
|
const init = useInitData()
|
|
169
175
|
const theme = useThemeParams()
|
|
176
|
+
const viewport = useViewport()
|
|
177
|
+
const homeScreen = useHomeScreen()
|
|
178
|
+
const storage = useTelegramStorage('device')
|
|
170
179
|
|
|
171
180
|
// Configure main button
|
|
172
181
|
onMounted(() => {
|
|
@@ -188,6 +197,17 @@ onMounted(() => {
|
|
|
188
197
|
// Access user data
|
|
189
198
|
const userName = computed(() => init.user.value?.first_name || 'Guest')
|
|
190
199
|
|
|
200
|
+
// Newer Telegram APIs are feature-detected
|
|
201
|
+
function openFullscreen() {
|
|
202
|
+
if (!viewport.fullscreen.value) {
|
|
203
|
+
viewport.requestFullscreen()
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function rememberPreference() {
|
|
208
|
+
storage.setItem('preferred_mode', theme.dark.value ? 'dark' : 'light')
|
|
209
|
+
}
|
|
210
|
+
|
|
191
211
|
// Use theme colors
|
|
192
212
|
const bgColor = computed(() => theme.backgroundColor.value)
|
|
193
213
|
</script>
|
|
@@ -399,6 +419,7 @@ export default {
|
|
|
399
419
|
- `setParams(params)` - Configure button
|
|
400
420
|
- `onClick(callback)` - Handle clicks
|
|
401
421
|
- `visible` - Visibility state
|
|
422
|
+
- Supports shine effect, custom emoji icon, and other current BottomButton params
|
|
402
423
|
|
|
403
424
|
#### `useBackButton()`
|
|
404
425
|
- `show()` / `hide()` - Control visibility
|
|
@@ -420,6 +441,26 @@ export default {
|
|
|
420
441
|
- `buttonColor` - Theme button color
|
|
421
442
|
- And more theme colors...
|
|
422
443
|
|
|
444
|
+
#### `useViewport()`
|
|
445
|
+
- `requestFullscreen()` / `exitFullscreen()` - Toggle Telegram fullscreen when supported
|
|
446
|
+
- `insets` - Device safe-area insets
|
|
447
|
+
- `contentInsets` - Content safe-area insets
|
|
448
|
+
|
|
449
|
+
#### `useMiniApp()`
|
|
450
|
+
- `hideKeyboard()` - Hide the native keyboard when supported
|
|
451
|
+
- `lockOrientation()` / `unlockOrientation()` - Control device orientation when supported
|
|
452
|
+
- `enableVerticalSwipes()` / `disableVerticalSwipes()` - Control vertical swipe gestures
|
|
453
|
+
|
|
454
|
+
#### New Telegram APIs
|
|
455
|
+
- `useSecondaryButton()` - Control Telegram's secondary bottom button
|
|
456
|
+
- `useSettingsButton()` - Control the Mini App settings menu button
|
|
457
|
+
- `useHomeScreen()` - Add/check home screen shortcuts
|
|
458
|
+
- `useEmojiStatus()` - Request emoji status access and open the set-status dialog
|
|
459
|
+
- `useTelegramStorage(type)` - Use `cloud`, `device`, or `secure` Telegram storage
|
|
460
|
+
- `useLocationManager()` - Initialize and request native location data
|
|
461
|
+
- `useDeviceMotion()` - Access accelerometer, orientation, and gyroscope objects
|
|
462
|
+
- `shareToStory()`, `shareMessage()`, `downloadFile()`, `requestChat()` - Wrappers for newer Telegram actions
|
|
463
|
+
|
|
423
464
|
### Components
|
|
424
465
|
|
|
425
466
|
#### `<TgButton>`
|
|
@@ -605,8 +646,7 @@ export default defineNuxtConfig({
|
|
|
605
646
|
app: {
|
|
606
647
|
head: {
|
|
607
648
|
script: [{
|
|
608
|
-
src: 'https://telegram.org/js/telegram-web-app.js?
|
|
609
|
-
defer: true
|
|
649
|
+
src: 'https://telegram.org/js/telegram-web-app.js?62'
|
|
610
650
|
}]
|
|
611
651
|
}
|
|
612
652
|
}
|
|
@@ -615,7 +655,7 @@ export default defineNuxtConfig({
|
|
|
615
655
|
|
|
616
656
|
### Telegram WebApp Script
|
|
617
657
|
|
|
618
|
-
The template automatically includes the Telegram WebApp script. The
|
|
658
|
+
The template automatically includes the Telegram WebApp script. The wrapper feature-detects newer APIs because the available API version depends on the user's Telegram client.
|
|
619
659
|
|
|
620
660
|
## 🤝 Contributing
|
|
621
661
|
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { ref, computed, onMounted } from 'vue'
|
|
2
2
|
import type {
|
|
3
3
|
TelegramWebApp,
|
|
4
|
-
|
|
4
|
+
TelegramDownloadFileParams,
|
|
5
|
+
TelegramEmojiStatusParams,
|
|
6
|
+
TelegramHomeScreenStatus,
|
|
7
|
+
TelegramLocationData,
|
|
8
|
+
TelegramStoryShareParams,
|
|
9
|
+
TelegramWebAppDeviceStorage,
|
|
10
|
+
TelegramWebAppInitData,
|
|
11
|
+
TelegramWebAppSecureStorage,
|
|
12
|
+
TelegramWebAppInsets
|
|
5
13
|
} from '~/types/telegram-webapp'
|
|
6
14
|
|
|
7
15
|
// Helper to get WebApp instance
|
|
@@ -26,6 +34,29 @@ function initWebApp() {
|
|
|
26
34
|
}
|
|
27
35
|
}
|
|
28
36
|
|
|
37
|
+
function ensureWebApp() {
|
|
38
|
+
initWebApp()
|
|
39
|
+
return globalWebApp || getWebApp()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const emptyInsets: TelegramWebAppInsets = {
|
|
43
|
+
top: 0,
|
|
44
|
+
bottom: 0,
|
|
45
|
+
left: 0,
|
|
46
|
+
right: 0
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function storageCallback<T>(resolve: (value: T) => void, reject: (reason?: unknown) => void) {
|
|
50
|
+
return (error: unknown, result?: T) => {
|
|
51
|
+
if (error) {
|
|
52
|
+
reject(error)
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
resolve(result as T)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
29
60
|
// Main composable to access Telegram WebApp
|
|
30
61
|
export function useTelegramWebApp() {
|
|
31
62
|
const webApp = ref<TelegramWebApp | null>(null)
|
|
@@ -47,31 +78,39 @@ export function useTelegramWebApp() {
|
|
|
47
78
|
// Back Button
|
|
48
79
|
export function useBackButton() {
|
|
49
80
|
const visible = ref(false)
|
|
81
|
+
const mounted = ref(false)
|
|
50
82
|
|
|
51
83
|
onMounted(() => {
|
|
52
84
|
initWebApp()
|
|
53
85
|
if (globalWebApp?.BackButton) {
|
|
54
|
-
visible.value = globalWebApp.BackButton.isVisible
|
|
86
|
+
visible.value = globalWebApp.BackButton.isVisible ?? false
|
|
55
87
|
}
|
|
56
88
|
})
|
|
57
89
|
|
|
58
90
|
return {
|
|
59
91
|
supported: computed(() => !!globalWebApp?.BackButton),
|
|
60
|
-
mounted: computed(() =>
|
|
92
|
+
mounted: computed(() => mounted.value),
|
|
61
93
|
visible: computed(() => visible.value),
|
|
62
|
-
mount: () =>
|
|
63
|
-
|
|
94
|
+
mount: () => {
|
|
95
|
+
mounted.value = true
|
|
96
|
+
return true
|
|
97
|
+
},
|
|
98
|
+
unmount: () => {
|
|
99
|
+
mounted.value = false
|
|
100
|
+
visible.value = false
|
|
101
|
+
return true
|
|
102
|
+
},
|
|
64
103
|
show: () => {
|
|
65
|
-
if (globalWebApp?.BackButton) {
|
|
104
|
+
if (globalWebApp?.BackButton?.show) {
|
|
66
105
|
globalWebApp.BackButton.show()
|
|
67
|
-
visible.value = true
|
|
68
106
|
}
|
|
107
|
+
visible.value = true
|
|
69
108
|
},
|
|
70
109
|
hide: () => {
|
|
71
|
-
if (globalWebApp?.BackButton) {
|
|
110
|
+
if (globalWebApp?.BackButton?.hide) {
|
|
72
111
|
globalWebApp.BackButton.hide()
|
|
73
|
-
visible.value = false
|
|
74
112
|
}
|
|
113
|
+
visible.value = false
|
|
75
114
|
},
|
|
76
115
|
onClick: (fn: () => void) => {
|
|
77
116
|
if (globalWebApp?.BackButton) {
|
|
@@ -96,7 +135,7 @@ export function useHapticFeedback() {
|
|
|
96
135
|
|
|
97
136
|
return {
|
|
98
137
|
supported: computed(() => !!globalWebApp?.HapticFeedback),
|
|
99
|
-
impactOccurred: (style: 'light' | 'medium' | 'heavy' = 'medium') => {
|
|
138
|
+
impactOccurred: (style: 'light' | 'medium' | 'heavy' | 'rigid' | 'soft' = 'medium') => {
|
|
100
139
|
if (globalWebApp?.HapticFeedback) {
|
|
101
140
|
globalWebApp.HapticFeedback.impactOccurred(style)
|
|
102
141
|
}
|
|
@@ -137,6 +176,7 @@ export function useInitData() {
|
|
|
137
176
|
authDate: computed(() => state.value?.auth_date),
|
|
138
177
|
queryId: computed(() => state.value?.query_id),
|
|
139
178
|
startParam: computed(() => state.value?.start_param),
|
|
179
|
+
signature: computed(() => state.value?.signature),
|
|
140
180
|
restore: () => {
|
|
141
181
|
// Official SDK handles this automatically
|
|
142
182
|
return true
|
|
@@ -146,27 +186,50 @@ export function useInitData() {
|
|
|
146
186
|
|
|
147
187
|
// Main Button
|
|
148
188
|
export function useMainButton() {
|
|
189
|
+
const mounted = ref(false)
|
|
190
|
+
const state = ref({
|
|
191
|
+
icon_custom_emoji_id: '',
|
|
192
|
+
text: '',
|
|
193
|
+
color: '',
|
|
194
|
+
text_color: '',
|
|
195
|
+
has_shine_effect: false,
|
|
196
|
+
is_active: false,
|
|
197
|
+
is_visible: false
|
|
198
|
+
})
|
|
199
|
+
|
|
149
200
|
onMounted(() => {
|
|
150
201
|
initWebApp()
|
|
202
|
+
if (globalWebApp?.MainButton) {
|
|
203
|
+
state.value = {
|
|
204
|
+
icon_custom_emoji_id: globalWebApp.MainButton.iconCustomEmojiId || '',
|
|
205
|
+
text: globalWebApp.MainButton.text || '',
|
|
206
|
+
color: globalWebApp.MainButton.color || '',
|
|
207
|
+
text_color: globalWebApp.MainButton.textColor || '',
|
|
208
|
+
has_shine_effect: globalWebApp.MainButton.hasShineEffect || false,
|
|
209
|
+
is_active: globalWebApp.MainButton.isActive || false,
|
|
210
|
+
is_visible: globalWebApp.MainButton.isVisible || false
|
|
211
|
+
}
|
|
212
|
+
}
|
|
151
213
|
})
|
|
152
214
|
|
|
153
215
|
return {
|
|
154
|
-
mounted: computed(() =>
|
|
155
|
-
enabled: computed(() =>
|
|
216
|
+
mounted: computed(() => mounted.value),
|
|
217
|
+
enabled: computed(() => state.value.is_active),
|
|
156
218
|
loaderVisible: computed(() => globalWebApp?.MainButton?.isProgressVisible || false),
|
|
157
|
-
visible: computed(() =>
|
|
158
|
-
state: computed(() =>
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
219
|
+
visible: computed(() => state.value.is_visible),
|
|
220
|
+
state: computed(() => state.value),
|
|
221
|
+
text: computed(() => state.value.text),
|
|
222
|
+
textColor: computed(() => state.value.text_color || undefined),
|
|
223
|
+
backgroundColor: computed(() => state.value.color || undefined),
|
|
224
|
+
mount: () => {
|
|
225
|
+
mounted.value = true
|
|
226
|
+
return true
|
|
227
|
+
},
|
|
228
|
+
unmount: () => {
|
|
229
|
+
mounted.value = false
|
|
230
|
+
state.value.is_visible = false
|
|
231
|
+
return true
|
|
232
|
+
},
|
|
170
233
|
onClick: (fn: () => void) => {
|
|
171
234
|
if (globalWebApp?.MainButton) {
|
|
172
235
|
globalWebApp.MainButton.onClick(fn)
|
|
@@ -180,19 +243,88 @@ export function useMainButton() {
|
|
|
180
243
|
}
|
|
181
244
|
},
|
|
182
245
|
setParams: (updates: {
|
|
246
|
+
icon_custom_emoji_id?: string
|
|
183
247
|
text?: string
|
|
184
248
|
color?: string
|
|
185
249
|
text_color?: string
|
|
250
|
+
has_shine_effect?: boolean
|
|
251
|
+
position?: 'left' | 'right' | 'top' | 'bottom'
|
|
186
252
|
is_active?: boolean
|
|
187
253
|
is_visible?: boolean
|
|
188
254
|
}) => {
|
|
189
|
-
|
|
255
|
+
state.value = {
|
|
256
|
+
...state.value,
|
|
257
|
+
...updates
|
|
258
|
+
}
|
|
259
|
+
if (globalWebApp?.MainButton?.setParams) {
|
|
190
260
|
globalWebApp.MainButton.setParams(updates)
|
|
191
261
|
}
|
|
192
262
|
}
|
|
193
263
|
}
|
|
194
264
|
}
|
|
195
265
|
|
|
266
|
+
export function useSecondaryButton() {
|
|
267
|
+
onMounted(() => {
|
|
268
|
+
initWebApp()
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
mounted: computed(() => !!globalWebApp?.SecondaryButton),
|
|
273
|
+
enabled: computed(() => globalWebApp?.SecondaryButton?.isActive || false),
|
|
274
|
+
loaderVisible: computed(() => globalWebApp?.SecondaryButton?.isProgressVisible || false),
|
|
275
|
+
visible: computed(() => globalWebApp?.SecondaryButton?.isVisible || false),
|
|
276
|
+
text: computed(() => globalWebApp?.SecondaryButton?.text || ''),
|
|
277
|
+
textColor: computed(() => globalWebApp?.SecondaryButton?.textColor),
|
|
278
|
+
backgroundColor: computed(() => globalWebApp?.SecondaryButton?.color),
|
|
279
|
+
position: computed(() => globalWebApp?.SecondaryButton?.position),
|
|
280
|
+
onClick: (fn: () => void) => {
|
|
281
|
+
if (globalWebApp?.SecondaryButton) {
|
|
282
|
+
globalWebApp.SecondaryButton.onClick(fn)
|
|
283
|
+
return fn
|
|
284
|
+
}
|
|
285
|
+
return () => {}
|
|
286
|
+
},
|
|
287
|
+
offClick: (fn: () => void) => {
|
|
288
|
+
globalWebApp?.SecondaryButton?.offClick(fn)
|
|
289
|
+
},
|
|
290
|
+
setParams: (updates: {
|
|
291
|
+
icon_custom_emoji_id?: string
|
|
292
|
+
text?: string
|
|
293
|
+
color?: string
|
|
294
|
+
text_color?: string
|
|
295
|
+
has_shine_effect?: boolean
|
|
296
|
+
position?: 'left' | 'right' | 'top' | 'bottom'
|
|
297
|
+
is_active?: boolean
|
|
298
|
+
is_visible?: boolean
|
|
299
|
+
}) => {
|
|
300
|
+
globalWebApp?.SecondaryButton?.setParams(updates)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function useSettingsButton() {
|
|
306
|
+
onMounted(() => {
|
|
307
|
+
initWebApp()
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
supported: computed(() => !!globalWebApp?.SettingsButton),
|
|
312
|
+
visible: computed(() => globalWebApp?.SettingsButton?.isVisible || false),
|
|
313
|
+
show: () => globalWebApp?.SettingsButton?.show(),
|
|
314
|
+
hide: () => globalWebApp?.SettingsButton?.hide(),
|
|
315
|
+
onClick: (fn: () => void) => {
|
|
316
|
+
if (globalWebApp?.SettingsButton) {
|
|
317
|
+
globalWebApp.SettingsButton.onClick(fn)
|
|
318
|
+
return fn
|
|
319
|
+
}
|
|
320
|
+
return () => {}
|
|
321
|
+
},
|
|
322
|
+
offClick: (fn: () => void) => {
|
|
323
|
+
globalWebApp?.SettingsButton?.offClick(fn)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
196
328
|
// Mini App / Theme management
|
|
197
329
|
export function useMiniApp() {
|
|
198
330
|
onMounted(() => {
|
|
@@ -203,6 +335,9 @@ export function useMiniApp() {
|
|
|
203
335
|
supported: computed(() => !!globalWebApp),
|
|
204
336
|
mounted: computed(() => !!globalWebApp),
|
|
205
337
|
active: computed(() => true), // Always active when available
|
|
338
|
+
foreground: computed(() => globalWebApp?.isActive ?? true),
|
|
339
|
+
fullscreen: computed(() => globalWebApp?.isFullscreen || false),
|
|
340
|
+
orientationLocked: computed(() => globalWebApp?.isOrientationLocked || false),
|
|
206
341
|
dark: computed(() => globalWebApp?.colorScheme === 'dark'),
|
|
207
342
|
state: computed(() => globalWebApp || null),
|
|
208
343
|
backgroundColor: computed(() => globalWebApp?.backgroundColor),
|
|
@@ -222,7 +357,14 @@ export function useMiniApp() {
|
|
|
222
357
|
if (globalWebApp) {
|
|
223
358
|
globalWebApp.setBottomBarColor(color)
|
|
224
359
|
}
|
|
225
|
-
}
|
|
360
|
+
},
|
|
361
|
+
enableClosingConfirmation: () => globalWebApp?.enableClosingConfirmation(),
|
|
362
|
+
disableClosingConfirmation: () => globalWebApp?.disableClosingConfirmation(),
|
|
363
|
+
enableVerticalSwipes: () => globalWebApp?.enableVerticalSwipes(),
|
|
364
|
+
disableVerticalSwipes: () => globalWebApp?.disableVerticalSwipes(),
|
|
365
|
+
lockOrientation: () => globalWebApp?.lockOrientation?.(),
|
|
366
|
+
unlockOrientation: () => globalWebApp?.unlockOrientation?.(),
|
|
367
|
+
hideKeyboard: () => globalWebApp?.hideKeyboard?.()
|
|
226
368
|
}
|
|
227
369
|
}
|
|
228
370
|
|
|
@@ -277,16 +419,17 @@ export function useViewport() {
|
|
|
277
419
|
return vh === svh
|
|
278
420
|
}),
|
|
279
421
|
insets: computed(() => ({
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
422
|
+
...(globalWebApp?.safeAreaInset || emptyInsets)
|
|
423
|
+
})),
|
|
424
|
+
contentInsets: computed(() => ({
|
|
425
|
+
...(globalWebApp?.contentSafeAreaInset || emptyInsets)
|
|
284
426
|
})),
|
|
427
|
+
fullscreen: computed(() => globalWebApp?.isFullscreen || false),
|
|
285
428
|
requestFullscreen: () => {
|
|
286
|
-
|
|
429
|
+
globalWebApp?.requestFullscreen?.()
|
|
287
430
|
},
|
|
288
431
|
exitFullscreen: () => {
|
|
289
|
-
|
|
432
|
+
globalWebApp?.exitFullscreen?.()
|
|
290
433
|
},
|
|
291
434
|
expand: () => {
|
|
292
435
|
if (globalWebApp) {
|
|
@@ -296,9 +439,155 @@ export function useViewport() {
|
|
|
296
439
|
}
|
|
297
440
|
}
|
|
298
441
|
|
|
442
|
+
export function useHomeScreen() {
|
|
443
|
+
onMounted(() => {
|
|
444
|
+
initWebApp()
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
const status = ref<TelegramHomeScreenStatus | undefined>()
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
supported: computed(() => !!globalWebApp?.addToHomeScreen || !!globalWebApp?.checkHomeScreenStatus),
|
|
451
|
+
status: computed(() => status.value),
|
|
452
|
+
add: () => globalWebApp?.addToHomeScreen?.(),
|
|
453
|
+
check: () => {
|
|
454
|
+
globalWebApp?.checkHomeScreenStatus?.((nextStatus) => {
|
|
455
|
+
status.value = nextStatus
|
|
456
|
+
})
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export function useEmojiStatus() {
|
|
462
|
+
onMounted(() => {
|
|
463
|
+
initWebApp()
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
supported: computed(() => !!globalWebApp?.setEmojiStatus || !!globalWebApp?.requestEmojiStatusAccess),
|
|
468
|
+
set: (customEmojiId: string, params?: TelegramEmojiStatusParams) => {
|
|
469
|
+
return new Promise<boolean>((resolve) => {
|
|
470
|
+
if (!globalWebApp?.setEmojiStatus) {
|
|
471
|
+
resolve(false)
|
|
472
|
+
return
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
globalWebApp.setEmojiStatus(customEmojiId, params, resolve)
|
|
476
|
+
})
|
|
477
|
+
},
|
|
478
|
+
requestAccess: () => {
|
|
479
|
+
return new Promise<boolean>((resolve) => {
|
|
480
|
+
if (!globalWebApp?.requestEmojiStatusAccess) {
|
|
481
|
+
resolve(false)
|
|
482
|
+
return
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
globalWebApp.requestEmojiStatusAccess(resolve)
|
|
486
|
+
})
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export function useTelegramStorage(type: 'cloud' | 'device' | 'secure' = 'cloud') {
|
|
492
|
+
onMounted(() => {
|
|
493
|
+
initWebApp()
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
const storage = computed(() => {
|
|
497
|
+
if (type === 'device') return globalWebApp?.DeviceStorage
|
|
498
|
+
if (type === 'secure') return globalWebApp?.SecureStorage
|
|
499
|
+
return globalWebApp?.CloudStorage
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
return {
|
|
503
|
+
supported: computed(() => !!storage.value),
|
|
504
|
+
setItem: (key: string, value: string) => {
|
|
505
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
506
|
+
if (!storage.value) {
|
|
507
|
+
resolve(false)
|
|
508
|
+
return
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
storage.value.setItem(key, value, storageCallback(resolve, reject))
|
|
512
|
+
})
|
|
513
|
+
},
|
|
514
|
+
getItem: (key: string) => {
|
|
515
|
+
return new Promise<string | null>((resolve, reject) => {
|
|
516
|
+
if (!storage.value) {
|
|
517
|
+
resolve(null)
|
|
518
|
+
return
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
storage.value.getItem(key, storageCallback(resolve, reject))
|
|
522
|
+
})
|
|
523
|
+
},
|
|
524
|
+
removeItem: (key: string) => {
|
|
525
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
526
|
+
if (!storage.value) {
|
|
527
|
+
resolve(false)
|
|
528
|
+
return
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
storage.value.removeItem(key, storageCallback(resolve, reject))
|
|
532
|
+
})
|
|
533
|
+
},
|
|
534
|
+
clear: () => {
|
|
535
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
536
|
+
const currentStorage = storage.value as TelegramWebAppDeviceStorage | TelegramWebAppSecureStorage | undefined
|
|
537
|
+
if (!currentStorage?.clear) {
|
|
538
|
+
resolve(false)
|
|
539
|
+
return
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
currentStorage.clear(storageCallback(resolve, reject))
|
|
543
|
+
})
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export function useLocationManager() {
|
|
549
|
+
onMounted(() => {
|
|
550
|
+
initWebApp()
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
return {
|
|
554
|
+
supported: computed(() => !!globalWebApp?.LocationManager),
|
|
555
|
+
inited: computed(() => globalWebApp?.LocationManager?.isInited || false),
|
|
556
|
+
available: computed(() => globalWebApp?.LocationManager?.isLocationAvailable || false),
|
|
557
|
+
accessRequested: computed(() => globalWebApp?.LocationManager?.isAccessRequested || false),
|
|
558
|
+
accessGranted: computed(() => globalWebApp?.LocationManager?.isAccessGranted || false),
|
|
559
|
+
init: () => globalWebApp?.LocationManager?.init(),
|
|
560
|
+
getLocation: () => {
|
|
561
|
+
return new Promise<TelegramLocationData | null>((resolve) => {
|
|
562
|
+
if (!globalWebApp?.LocationManager) {
|
|
563
|
+
resolve(null)
|
|
564
|
+
return
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
globalWebApp.LocationManager.getLocation(resolve)
|
|
568
|
+
})
|
|
569
|
+
},
|
|
570
|
+
openSettings: () => globalWebApp?.LocationManager?.openSettings()
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
export function useDeviceMotion() {
|
|
575
|
+
onMounted(() => {
|
|
576
|
+
initWebApp()
|
|
577
|
+
})
|
|
578
|
+
|
|
579
|
+
return {
|
|
580
|
+
accelerometer: computed(() => globalWebApp?.Accelerometer),
|
|
581
|
+
deviceOrientation: computed(() => globalWebApp?.DeviceOrientation),
|
|
582
|
+
gyroscope: computed(() => globalWebApp?.Gyroscope),
|
|
583
|
+
lockOrientation: () => globalWebApp?.lockOrientation?.(),
|
|
584
|
+
unlockOrientation: () => globalWebApp?.unlockOrientation?.()
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
299
588
|
// Links and sharing
|
|
300
589
|
export function openLink(url: string, options?: { try_instant_view?: boolean }) {
|
|
301
|
-
const webApp =
|
|
590
|
+
const webApp = ensureWebApp()
|
|
302
591
|
if (webApp) {
|
|
303
592
|
webApp.openLink(url, options)
|
|
304
593
|
} else {
|
|
@@ -308,7 +597,7 @@ export function openLink(url: string, options?: { try_instant_view?: boolean })
|
|
|
308
597
|
}
|
|
309
598
|
|
|
310
599
|
export function openTelegramLink(url: string) {
|
|
311
|
-
const webApp =
|
|
600
|
+
const webApp = ensureWebApp()
|
|
312
601
|
if (webApp) {
|
|
313
602
|
webApp.openTelegramLink(url)
|
|
314
603
|
} else {
|
|
@@ -318,7 +607,7 @@ export function openTelegramLink(url: string) {
|
|
|
318
607
|
}
|
|
319
608
|
|
|
320
609
|
export function shareURL(url: string) {
|
|
321
|
-
const webApp =
|
|
610
|
+
const webApp = ensureWebApp()
|
|
322
611
|
if (webApp) {
|
|
323
612
|
// Use Web Share API if available, otherwise copy to clipboard
|
|
324
613
|
if (navigator.share) {
|
|
@@ -340,3 +629,48 @@ export function shareURL(url: string) {
|
|
|
340
629
|
}
|
|
341
630
|
}
|
|
342
631
|
}
|
|
632
|
+
|
|
633
|
+
export function shareToStory(mediaUrl: string, params?: TelegramStoryShareParams) {
|
|
634
|
+
const webApp = ensureWebApp()
|
|
635
|
+
if (webApp?.shareToStory) {
|
|
636
|
+
webApp.shareToStory(mediaUrl, params)
|
|
637
|
+
} else if (navigator.share) {
|
|
638
|
+
navigator.share({ url: mediaUrl }).catch(() => {})
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
export function shareMessage(messageId: string) {
|
|
643
|
+
const webApp = ensureWebApp()
|
|
644
|
+
return new Promise<boolean>((resolve) => {
|
|
645
|
+
if (!webApp?.shareMessage) {
|
|
646
|
+
resolve(false)
|
|
647
|
+
return
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
webApp.shareMessage(messageId, resolve)
|
|
651
|
+
})
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export function downloadFile(params: TelegramDownloadFileParams) {
|
|
655
|
+
const webApp = ensureWebApp()
|
|
656
|
+
return new Promise<boolean>((resolve) => {
|
|
657
|
+
if (!webApp?.downloadFile) {
|
|
658
|
+
resolve(false)
|
|
659
|
+
return
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
webApp.downloadFile(params, resolve)
|
|
663
|
+
})
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
export function requestChat(reqId: string) {
|
|
667
|
+
const webApp = ensureWebApp()
|
|
668
|
+
return new Promise<boolean>((resolve) => {
|
|
669
|
+
if (!webApp?.requestChat) {
|
|
670
|
+
resolve(false)
|
|
671
|
+
return
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
webApp.requestChat(reqId, resolve)
|
|
675
|
+
})
|
|
676
|
+
}
|