telegix 1.1.1 → 1.1.2

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/lib/webapp.js CHANGED
@@ -1,5 +1,13 @@
1
1
  /**
2
- * Telegix - Telegram Web App InitData Validator
2
+ * Telegix - Telegram Mini Apps (TMA) & Web App Suite
3
+ * Comprehensive support for Mini Apps 8.0/9.0/10.x:
4
+ * - InitData Validation & Parsing
5
+ * - Full-Screen Mode
6
+ * - Device Motion Tracking (Accelerometer, Orientation, Gyroscope)
7
+ * - Loading Screen Customization (Theme-adaptive splash & skeleton)
8
+ * - Prepared Inline Messages & Launch URLs
9
+ * - Client-side WebApp Bridge Utilities
10
+ * @module telegix/webapp
3
11
  */
4
12
 
5
13
  import crypto from 'crypto';
@@ -59,7 +67,508 @@ export function validateWebAppInitData(initDataStr, botToken, options = {}) {
59
67
  }
60
68
 
61
69
  return result;
62
- } catch (err) {
70
+ } catch {
63
71
  return null;
64
72
  }
65
73
  }
74
+
75
+ /**
76
+ * Parses raw initData string into a structured JavaScript object without cryptographic validation
77
+ * @param {string} initDataStr
78
+ * @returns {object}
79
+ */
80
+ export function parseWebAppInitData(initDataStr) {
81
+ if (!initDataStr || typeof initDataStr !== 'string') return {};
82
+
83
+ const params = new URLSearchParams(initDataStr);
84
+ const result = {};
85
+
86
+ for (const [key, value] of params.entries()) {
87
+ try {
88
+ result[key] = JSON.parse(value);
89
+ } catch {
90
+ result[key] = value;
91
+ }
92
+ }
93
+
94
+ return result;
95
+ }
96
+
97
+ /**
98
+ * Generate a Telegram Mini App direct launch URL
99
+ * @param {string} botUsername - Bot username (without @)
100
+ * @param {string} appShortName - Mini App short name registered in @BotFather
101
+ * @param {string} [startParam] - Optional startapp parameter
102
+ * @returns {string}
103
+ */
104
+ export function createMiniAppLaunchUrl(botUsername, appShortName, startParam = '') {
105
+ const cleanUsername = String(botUsername).replace(/^@/, '');
106
+ const url = `https://t.me/${cleanUsername}/${appShortName}`;
107
+ return startParam ? `${url}?startapp=${encodeURIComponent(startParam)}` : url;
108
+ }
109
+
110
+ /**
111
+ * Builder and Generator for Telegram Mini App Loading Screen (Mini Apps 8.0)
112
+ * Generates lightweight, responsive splash & skeleton screens adapting to dark/light Telegram theme.
113
+ */
114
+ export class MiniAppLoadingScreen {
115
+ /**
116
+ * @param {object} [options]
117
+ * @param {string} [options.title='Loading Mini App...']
118
+ * @param {string} [options.icon] - Image URL or SVG string for brand icon
119
+ * @param {string} [options.lightColor='#2481cc'] - Primary brand color in light theme
120
+ * @param {string} [options.darkColor='#64b5f6'] - Primary brand color in dark theme
121
+ * @param {string} [options.lightBg='#ffffff'] - Background in light theme
122
+ * @param {string} [options.darkBg='#17212b'] - Background in dark theme
123
+ * @param {boolean} [options.skeleton=true] - Render shimmer skeleton bars below icon
124
+ */
125
+ constructor(options = {}) {
126
+ this.title = options.title ?? 'Loading...';
127
+ this.icon = options.icon ?? '';
128
+ this.lightColor = options.lightColor ?? '#2481cc';
129
+ this.darkColor = options.darkColor ?? '#64b5f6';
130
+ this.lightBg = options.lightBg ?? '#ffffff';
131
+ this.darkBg = options.darkBg ?? '#17212b';
132
+ this.skeleton = options.skeleton ?? true;
133
+ }
134
+
135
+ /**
136
+ * Set brand icon (URL or SVG)
137
+ * @param {string} icon
138
+ * @returns {this}
139
+ */
140
+ setIcon(icon) {
141
+ this.icon = String(icon);
142
+ return this;
143
+ }
144
+
145
+ /**
146
+ * Set loading title
147
+ * @param {string} title
148
+ * @returns {this}
149
+ */
150
+ setTitle(title) {
151
+ this.title = String(title);
152
+ return this;
153
+ }
154
+
155
+ /**
156
+ * Set light and dark theme colors
157
+ * @param {string} lightColor
158
+ * @param {string} darkColor
159
+ * @returns {this}
160
+ */
161
+ setColors(lightColor, darkColor) {
162
+ this.lightColor = lightColor;
163
+ this.darkColor = darkColor;
164
+ return this;
165
+ }
166
+
167
+ /**
168
+ * Toggle skeleton placeholder display
169
+ * @param {boolean} [enabled=true]
170
+ * @returns {this}
171
+ */
172
+ setSkeleton(enabled = true) {
173
+ this.skeleton = Boolean(enabled);
174
+ return this;
175
+ }
176
+
177
+ /**
178
+ * Generate CSS styles for the customized loading screen
179
+ * @returns {string}
180
+ */
181
+ toCSS() {
182
+ return `
183
+ :root {
184
+ --tg-loading-bg: ${this.lightBg};
185
+ --tg-loading-color: ${this.lightColor};
186
+ --tg-skeleton-base: #e0e0e0;
187
+ --tg-skeleton-shimmer: #f5f5f5;
188
+ }
189
+ @media (prefers-color-scheme: dark) {
190
+ :root {
191
+ --tg-loading-bg: ${this.darkBg};
192
+ --tg-loading-color: ${this.darkColor};
193
+ --tg-skeleton-base: #242f3d;
194
+ --tg-skeleton-shimmer: #313d4f;
195
+ }
196
+ }
197
+ #tg-loading-screen {
198
+ position: fixed;
199
+ inset: 0;
200
+ z-index: 99999;
201
+ display: flex;
202
+ flex-direction: column;
203
+ align-items: center;
204
+ justify-content: center;
205
+ background-color: var(--tg-theme-bg-color, var(--tg-loading-bg));
206
+ color: var(--tg-theme-text-color, #222222);
207
+ transition: opacity 0.35s ease, visibility 0.35s ease;
208
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
209
+ user-select: none;
210
+ }
211
+ #tg-loading-screen.hidden {
212
+ opacity: 0;
213
+ visibility: hidden;
214
+ pointer-events: none;
215
+ }
216
+ .tg-loading-icon {
217
+ width: 72px;
218
+ height: 72px;
219
+ margin-bottom: 16px;
220
+ animation: tg-bounce 1.6s infinite ease-in-out;
221
+ }
222
+ .tg-loading-title {
223
+ font-size: 17px;
224
+ font-weight: 600;
225
+ letter-spacing: -0.2px;
226
+ margin-bottom: 24px;
227
+ color: var(--tg-theme-text-color, #222222);
228
+ }
229
+ .tg-loading-spinner {
230
+ width: 32px;
231
+ height: 32px;
232
+ border: 3px solid rgba(127, 127, 127, 0.2);
233
+ border-top-color: var(--tg-theme-button-color, var(--tg-loading-color));
234
+ border-radius: 50%;
235
+ animation: tg-spin 0.8s linear infinite;
236
+ }
237
+ .tg-skeleton-container {
238
+ width: 80%;
239
+ max-width: 280px;
240
+ display: flex;
241
+ flex-direction: column;
242
+ gap: 10px;
243
+ margin-top: 16px;
244
+ }
245
+ .tg-skeleton-bar {
246
+ height: 14px;
247
+ border-radius: 6px;
248
+ background: linear-gradient(90deg, var(--tg-skeleton-base) 25%, var(--tg-skeleton-shimmer) 50%, var(--tg-skeleton-base) 75%);
249
+ background-size: 200% 100%;
250
+ animation: tg-shimmer 1.5s infinite;
251
+ }
252
+ @keyframes tg-spin { to { transform: rotate(360deg); } }
253
+ @keyframes tg-bounce {
254
+ 0%, 100% { transform: scale(1); }
255
+ 50% { transform: scale(1.08); }
256
+ }
257
+ @keyframes tg-shimmer {
258
+ 0% { background-position: 200% 0; }
259
+ 100% { background-position: -200% 0; }
260
+ }
261
+ `.trim();
262
+ }
263
+
264
+ /**
265
+ * Generate HTML element string for the customized loading screen
266
+ * @returns {string}
267
+ */
268
+ toHTML() {
269
+ const iconHtml = this.icon
270
+ ? (this.icon.startsWith('<svg')
271
+ ? `<div class="tg-loading-icon">${this.icon}</div>`
272
+ : `<img src="${this.icon}" alt="Loading" class="tg-loading-icon" />`)
273
+ : `<div class="tg-loading-spinner"></div>`;
274
+
275
+ const skeletonHtml = this.skeleton
276
+ ? `<div class="tg-skeleton-container">
277
+ <div class="tg-skeleton-bar" style="width: 100%;"></div>
278
+ <div class="tg-skeleton-bar" style="width: 75%;"></div>
279
+ <div class="tg-skeleton-bar" style="width: 88%;"></div>
280
+ </div>`
281
+ : '';
282
+
283
+ return `
284
+ <style>${this.toCSS()}</style>
285
+ <div id="tg-loading-screen">
286
+ ${iconHtml}
287
+ <div class="tg-loading-title">${this.title}</div>
288
+ ${skeletonHtml}
289
+ </div>
290
+ <script>
291
+ window.addEventListener('load', function() {
292
+ if (window.Telegram && window.Telegram.WebApp) {
293
+ window.Telegram.WebApp.ready();
294
+ }
295
+ setTimeout(function() {
296
+ var el = document.getElementById('tg-loading-screen');
297
+ if (el) el.classList.add('hidden');
298
+ }, 200);
299
+ });
300
+ </script>
301
+ `.trim();
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Helper to generate loading screen HTML
307
+ * @param {object} options
308
+ * @returns {string}
309
+ */
310
+ export function generateMiniAppLoadingScreen(options = {}) {
311
+ return new MiniAppLoadingScreen(options).toHTML();
312
+ }
313
+
314
+ /**
315
+ * Telegram Mini Apps 8.0/9.0/10.x Full-Screen and Device Motion Controller
316
+ * Lightweight client bridge helper for Telegram WebApp environment.
317
+ */
318
+ export const MiniApp = {
319
+ /**
320
+ * Check if running inside Telegram Mini App environment
321
+ * @returns {boolean}
322
+ */
323
+ isInsideTelegram() {
324
+ return typeof window !== 'undefined' && Boolean(window.Telegram?.WebApp);
325
+ },
326
+
327
+ /**
328
+ * Returns window.Telegram.WebApp object safely
329
+ * @returns {object|null}
330
+ */
331
+ get webApp() {
332
+ return (typeof window !== 'undefined' && window.Telegram?.WebApp) || null;
333
+ },
334
+
335
+ /**
336
+ * Full-Screen Mode Methods (Mini Apps 8.0)
337
+ */
338
+ fullscreen: {
339
+ /**
340
+ * Request full-screen mode for the Mini App
341
+ */
342
+ request() {
343
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.requestFullscreen) {
344
+ window.Telegram.WebApp.requestFullscreen();
345
+ return true;
346
+ }
347
+ return false;
348
+ },
349
+
350
+ /**
351
+ * Exit full-screen mode
352
+ */
353
+ exit() {
354
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.exitFullscreen) {
355
+ window.Telegram.WebApp.exitFullscreen();
356
+ return true;
357
+ }
358
+ return false;
359
+ },
360
+
361
+ /**
362
+ * Check if currently in full-screen mode
363
+ * @returns {boolean}
364
+ */
365
+ isActive() {
366
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp) {
367
+ return Boolean(window.Telegram.WebApp.isFullscreen);
368
+ }
369
+ return false;
370
+ },
371
+
372
+ /**
373
+ * Listen for full-screen state changes
374
+ * @param {function(boolean): void} callback
375
+ */
376
+ onChange(callback) {
377
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.onEvent) {
378
+ window.Telegram.WebApp.onEvent('fullscreenChanged', () => {
379
+ callback(Boolean(window.Telegram.WebApp.isFullscreen));
380
+ });
381
+ }
382
+ },
383
+
384
+ /**
385
+ * Listen for full-screen request failures
386
+ * @param {function(object): void} callback
387
+ */
388
+ onFailed(callback) {
389
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.onEvent) {
390
+ window.Telegram.WebApp.onEvent('fullscreenFailed', callback);
391
+ }
392
+ },
393
+ },
394
+
395
+ /**
396
+ * Device Motion Tracking Methods (Mini Apps 8.0)
397
+ * Tracks Accelerometer, Device Orientation, and Gyroscope
398
+ */
399
+ motion: {
400
+ /**
401
+ * Start tracking accelerometer
402
+ * @param {object} [options]
403
+ * @param {number} [options.refresh_rate=100] - Refresh rate in ms (min: 20ms, default: 100ms)
404
+ * @returns {boolean}
405
+ */
406
+ startAccelerometer(options = { refresh_rate: 100 }) {
407
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.startAccelerometer) {
408
+ window.Telegram.WebApp.startAccelerometer(options);
409
+ return true;
410
+ }
411
+ return false;
412
+ },
413
+
414
+ /**
415
+ * Stop tracking accelerometer
416
+ * @returns {boolean}
417
+ */
418
+ stopAccelerometer() {
419
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.stopAccelerometer) {
420
+ window.Telegram.WebApp.stopAccelerometer();
421
+ return true;
422
+ }
423
+ return false;
424
+ },
425
+
426
+ /**
427
+ * Listen to accelerometer changes
428
+ * @param {function({ x: number, y: number, z: number }): void} callback
429
+ */
430
+ onAccelerometer(callback) {
431
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.onEvent) {
432
+ window.Telegram.WebApp.onEvent('accelerometerChanged', callback);
433
+ }
434
+ },
435
+
436
+ /**
437
+ * Start tracking device orientation
438
+ * @param {object} [options]
439
+ * @param {number} [options.refresh_rate=100]
440
+ * @param {boolean} [options.need_absolute=false]
441
+ * @returns {boolean}
442
+ */
443
+ startDeviceOrientation(options = { refresh_rate: 100, need_absolute: false }) {
444
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.startDeviceOrientation) {
445
+ window.Telegram.WebApp.startDeviceOrientation(options);
446
+ return true;
447
+ }
448
+ return false;
449
+ },
450
+
451
+ /**
452
+ * Stop tracking device orientation
453
+ * @returns {boolean}
454
+ */
455
+ stopDeviceOrientation() {
456
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.stopDeviceOrientation) {
457
+ window.Telegram.WebApp.stopDeviceOrientation();
458
+ return true;
459
+ }
460
+ return false;
461
+ },
462
+
463
+ /**
464
+ * Listen to device orientation changes (alpha, beta, gamma, absolute)
465
+ * @param {function({ alpha: number, beta: number, gamma: number, absolute: boolean }): void} callback
466
+ */
467
+ onOrientation(callback) {
468
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.onEvent) {
469
+ window.Telegram.WebApp.onEvent('deviceOrientationChanged', callback);
470
+ }
471
+ },
472
+
473
+ /**
474
+ * Start tracking gyroscope
475
+ * @param {object} [options]
476
+ * @param {number} [options.refresh_rate=100]
477
+ * @returns {boolean}
478
+ */
479
+ startGyroscope(options = { refresh_rate: 100 }) {
480
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.startGyroscope) {
481
+ window.Telegram.WebApp.startGyroscope(options);
482
+ return true;
483
+ }
484
+ return false;
485
+ },
486
+
487
+ /**
488
+ * Stop tracking gyroscope
489
+ * @returns {boolean}
490
+ */
491
+ stopGyroscope() {
492
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.stopGyroscope) {
493
+ window.Telegram.WebApp.stopGyroscope();
494
+ return true;
495
+ }
496
+ return false;
497
+ },
498
+
499
+ /**
500
+ * Listen to gyroscope changes
501
+ * @param {function({ x: number, y: number, z: number }): void} callback
502
+ */
503
+ onGyroscope(callback) {
504
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.onEvent) {
505
+ window.Telegram.WebApp.onEvent('gyroscopeChanged', callback);
506
+ }
507
+ },
508
+ },
509
+
510
+ /**
511
+ * Home Screen Shortcut Helpers (Mini Apps 8.0)
512
+ */
513
+ homeScreen: {
514
+ /**
515
+ * Prompts user to add Mini App shortcut to homescreen
516
+ */
517
+ addToHomeScreen() {
518
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.addToHomeScreen) {
519
+ window.Telegram.WebApp.addToHomeScreen();
520
+ return true;
521
+ }
522
+ return false;
523
+ },
524
+
525
+ /**
526
+ * Check if shortcut was already added or unsupported
527
+ * @param {function('unsupported'|'unknown'|'added'|'missed'): void} callback
528
+ */
529
+ checkStatus(callback) {
530
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.checkHomeScreenStatus) {
531
+ window.Telegram.WebApp.checkHomeScreenStatus(callback);
532
+ }
533
+ },
534
+ },
535
+
536
+ /**
537
+ * Share Prepared Message directly from Mini App (Mini Apps 8.0)
538
+ * @param {string} preparedMessageId - ID obtained via bot.telegram.savePreparedInlineMessage()
539
+ */
540
+ sharePreparedMessage(preparedMessageId) {
541
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.shareMessage) {
542
+ window.Telegram.WebApp.shareMessage(preparedMessageId);
543
+ return true;
544
+ }
545
+ return false;
546
+ },
547
+
548
+ /**
549
+ * Prompt user to download a file (Mini Apps 8.0)
550
+ * @param {object} params - { url: string, file_name: string }
551
+ */
552
+ downloadFile(params) {
553
+ if (typeof window !== 'undefined' && window.Telegram?.WebApp?.downloadFile) {
554
+ window.Telegram.WebApp.downloadFile(params);
555
+ return true;
556
+ }
557
+ return false;
558
+ },
559
+
560
+ /**
561
+ * Haptic Feedback Shortcuts
562
+ */
563
+ haptics: {
564
+ impact(style = 'medium') {
565
+ window.Telegram?.WebApp?.HapticFeedback?.impactOccurred?.(style);
566
+ },
567
+ notification(type = 'success') {
568
+ window.Telegram?.WebApp?.HapticFeedback?.notificationOccurred?.(type);
569
+ },
570
+ selection() {
571
+ window.Telegram?.WebApp?.HapticFeedback?.selectionChanged?.();
572
+ },
573
+ },
574
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "telegix",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Lightweight Telegram Bot API framework for Node.js.",
5
5
  "type": "module",
6
6
  "main": "./index.cjs",