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.
@@ -0,0 +1,6 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "npm"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
package/README.md CHANGED
@@ -7,6 +7,7 @@ A comprehensive template for building Telegram Mini Apps using Nuxt 4, Vue 3, Ty
7
7
  ![Vue](https://img.shields.io/badge/Vue-3.x-4FC08D)
8
8
  ![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6)
9
9
  ![Tailwind](https://img.shields.io/badge/Tailwind-3.x-38B2AC)
10
+ ![Telegram API](https://img.shields.io/badge/Telegram%20API-6.2-26A5E4)
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** - Responsive viewport management
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?58',
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 version can be updated in the Nuxt config.
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
- TelegramWebAppInitData
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(() => !!globalWebApp?.BackButton),
92
+ mounted: computed(() => mounted.value),
61
93
  visible: computed(() => visible.value),
62
- mount: () => true, // Always available with official SDK
63
- unmount: () => true,
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(() => !!globalWebApp?.MainButton),
155
- enabled: computed(() => globalWebApp?.MainButton?.isActive || false),
216
+ mounted: computed(() => mounted.value),
217
+ enabled: computed(() => state.value.is_active),
156
218
  loaderVisible: computed(() => globalWebApp?.MainButton?.isProgressVisible || false),
157
- visible: computed(() => globalWebApp?.MainButton?.isVisible || false),
158
- state: computed(() => ({
159
- text: globalWebApp?.MainButton?.text || '',
160
- color: globalWebApp?.MainButton?.color || '',
161
- text_color: globalWebApp?.MainButton?.textColor || '',
162
- is_active: globalWebApp?.MainButton?.isActive || false,
163
- is_visible: globalWebApp?.MainButton?.isVisible || false
164
- })),
165
- text: computed(() => globalWebApp?.MainButton?.text || ''),
166
- textColor: computed(() => globalWebApp?.MainButton?.textColor),
167
- backgroundColor: computed(() => globalWebApp?.MainButton?.color),
168
- mount: () => true, // Always available
169
- unmount: () => true,
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
- if (globalWebApp?.MainButton) {
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
- top: 0,
281
- bottom: 0,
282
- left: 0,
283
- right: 0
422
+ ...(globalWebApp?.safeAreaInset || emptyInsets)
423
+ })),
424
+ contentInsets: computed(() => ({
425
+ ...(globalWebApp?.contentSafeAreaInset || emptyInsets)
284
426
  })),
427
+ fullscreen: computed(() => globalWebApp?.isFullscreen || false),
285
428
  requestFullscreen: () => {
286
- // Not available in Telegram WebApp
429
+ globalWebApp?.requestFullscreen?.()
287
430
  },
288
431
  exitFullscreen: () => {
289
- // Not available in Telegram WebApp
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 = getWebApp()
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 = getWebApp()
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 = getWebApp()
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
+ }