Skip to content

Commit

Permalink
Improve telegram mini app (#1472)
Browse files Browse the repository at this point in the history
* Replace TG library with new one

* Add haptic feedback support

* rename service

* Update AppHeaderMenu.vue

* Add haptic global listener

* Update App.vue

* Migrate to native Telegram API

* Fix haptic issue with event

* Update package.json

---------

Co-authored-by: RustemYuzlibaev <rustem.yuzlibaev@yandex.ru>
  • Loading branch information
stefashkaa and RustemYuzlibaev authored Jul 29, 2024
1 parent 38aeeb0 commit 93f9787
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 78 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
"@cedelabs/widgets-universal": "^1.3.1",
"@metamask/detect-provider": "^2.0.0",
"@soramitsu/soraneo-wallet-web": "1.39.6",
"@tma.js/sdk": "^2.7.0",
"@walletconnect/ethereum-provider": "^2.13.3",
"@walletconnect/modal": "^2.6.2",
"core-js": "^3.37.1",
Expand Down Expand Up @@ -58,6 +57,7 @@
"@types/jsdom": "^21.1.7",
"@types/lodash": "^4.17.5",
"@types/node": "^20.14.5",
"@types/telegram-web-app": "^7.3.1",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vue/cli-plugin-babel": "5.0.8",
Expand Down
1 change: 1 addition & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<meta property="og:image:secure_url" content="https://polkaswap.io/<%= BASE_URL %>polkaswap-share-image.jpg" />
<meta property="og:image:width" content="1280" />
<meta property="og:image:height" content="630" />
<script src="https://telegram.org/js/telegram-web-app.js"></script>
</head>
<body>
<noscript>
Expand Down
14 changes: 3 additions & 11 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ import {
initWallet,
waitForCore,
} from '@soramitsu/soraneo-wallet-web';
import { isTMA } from '@tma.js/sdk';
import debounce from 'lodash/debounce';
import { Component, Mixins, Watch } from 'vue-property-decorator';
Expand All @@ -81,7 +80,7 @@ import { action, getter, mutation, state } from '@/store/decorators';
import { getMobileCssClasses, preloadFontFace, updateDocumentTitle } from '@/utils';
import type { NodesConnection } from '@/utils/connection';
import { calculateStorageUsagePercentage, clearLocalStorage } from '@/utils/storage';
import { TmaSdk } from '@/utils/telegram';
import { tmaSdkService } from '@/utils/telegram';
import type { FeatureFlags } from './store/settings/types';
import type { EthBridgeSettings, SubNetworkApps } from './store/web3/types';
Expand Down Expand Up @@ -170,10 +169,6 @@ export default class App extends Mixins(mixins.TransactionMixin, NodeErrorMixin)
@state.wallet.transactions.isSignTxDialogVisible public isSignTxDialogVisible!: boolean;
@mutation.wallet.transactions.setSignTxDialogVisibility public setSignTxDialogVisibility!: (flag: boolean) => void;
// [DESKTOP] To Enable Desktop
@mutation.wallet.account.setIsDesktop private setIsDesktop!: (v: boolean) => void;
// [TMA] To Enable TMA
@mutation.settings.enableTMA private enableTMA!: FnWithoutArgs;
@Watch('assetsToNotifyQueue')
private handleNotifyOnDeposit(whitelistAssetArray: WhitelistArrayItem[]): void {
Expand Down Expand Up @@ -261,11 +256,7 @@ export default class App extends Mixins(mixins.TransactionMixin, NodeErrorMixin)
}
// To start running as Telegram Web App (desktop capabilities)
if (await isTMA()) {
this.enableTMA();
this.setIsDesktop(true);
await TmaSdk.init(data?.TG_BOT_URL, data.NETWORK_TYPE === WALLET_CONSTS.SoraNetwork.Dev);
}
tmaSdkService.init(data?.TG_BOT_URL);
await this.setApiKeys(data?.API_KEYS);
await this.setEthBridgeSettings(data.ETH_BRIDGE);
Expand Down Expand Up @@ -388,6 +379,7 @@ export default class App extends Mixins(mixins.TransactionMixin, NodeErrorMixin)
async beforeDestroy(): Promise<void> {
window.removeEventListener('localStorageUpdated', this.handleLocalStorageChange);
window.removeEventListener('resize', this.setResponsiveClassDebounced);
tmaSdkService.destroy();
await this.resetInternalSubscriptions();
await this.resetNetworkSubscriptions();
this.resetBlockNumberSubscription();
Expand Down
4 changes: 2 additions & 2 deletions src/components/App/Header/AppHeaderMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import { Component, Mixins } from 'vue-property-decorator';
import TranslationMixin from '@/components/mixins/TranslationMixin';
import { getter, mutation, state } from '@/store/decorators';
import { TmaSdk } from '@/utils/telegram';
import { tmaSdkService } from '@/utils/telegram';
import type { Currency } from '@soramitsu/soraneo-wallet-web/lib/types/currency';
Expand Down Expand Up @@ -186,7 +186,7 @@ export default class AppHeaderMenu extends Mixins(TranslationMixin) {
case HeaderMenuType.Theme:
await switchTheme();
await this.$nextTick();
TmaSdk.updateTheme();
tmaSdkService.updateTheme();
break;
case HeaderMenuType.Language:
this.setLanguageDialogVisibility(true);
Expand Down
3 changes: 3 additions & 0 deletions src/store/settings/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ const mutations = defineMutations<SettingsState>()({
enableTMA(state): void {
state.isTMA = true;
},
disableTMA(state): void {
state.isTMA = false;
},
setTelegramBotUrl(state, url: Nullable<string>): void {
state.telegramBotUrl = url;
},
Expand Down
141 changes: 87 additions & 54 deletions src/utils/telegram.ts
Original file line number Diff line number Diff line change
@@ -1,100 +1,133 @@
import { api } from '@soramitsu/soraneo-wallet-web';
import {
setDebug,
initViewport,
initMiniApp,
initWeb,
isIframe,
retrieveLaunchParams,
initUtils,
type MiniApp,
type LaunchParams,
type Utils,
type Viewport,
} from '@tma.js/sdk';

import store from '@/store';

export class TmaSdk {
public static miniApp: MiniApp;
public static launchParams: LaunchParams;
public static viewport: Nullable<Viewport>;
private static utils: Utils;
enum HapticStatusValue {
success = 'success',
warning = 'warning',
error = 'error',
}

type HapticFeedbackStatus = keyof typeof HapticStatusValue;

export type HapticFeedbackBinding = 'light' | 'medium' | 'heavy' | 'rigid' | 'soft' | HapticFeedbackStatus;

const HapticNotificationTypes: string[] = Object.values(HapticStatusValue);

public static async init(botUrl?: string, isDebug = false): Promise<void> {
function isNotification(value: HapticFeedbackBinding): value is HapticFeedbackStatus {
return HapticNotificationTypes.includes(value);
}

function useHaptic(type: HapticFeedbackBinding): void {
const HapticFeedback = Telegram?.WebApp?.HapticFeedback;
if (!HapticFeedback) {
return;
}
try {
if (isNotification(type)) {
HapticFeedback?.notificationOccurred(type);
return;
}
HapticFeedback?.impactOccurred(type);
} catch (error) {
console.warn('[TMA]: useHapticFeedback', error);
}
}

class TmaSdk {
public init(botUrl?: string): void {
try {
// Initialize the app in the web version of Telegram
if (isIframe()) {
initWeb();
console.info('[TMA]: initTMA: Web version of Telegram');
// Check if the current platform is Telegram Mini App
const WebApp = Telegram?.WebApp;
if (!WebApp?.initData) {
console.info('[TMA]: Not a Telegram Mini App, skipping initialization');
return;
}
// Init viewport
await this.initViewport();
// Init mini app
const [miniApp] = initMiniApp();
this.miniApp = miniApp;
// Expand viewport to the full screen
WebApp?.expand?.();
// Disable vertical swipe if possible
WebApp?.disableVerticalSwipes?.();
store.commit.settings.enableTMA();
store.commit.wallet.account.setIsDesktop(true);
console.info('[TMA]: Mini app was initialized');
// Set theme
this.updateTheme();
// Enable debugging
setDebug(isDebug);
// Retrieve launch params
this.launchParams = retrieveLaunchParams();
console.info('[TMA]: Launch params were retrieved');
// Check the referrer
this.setReferrer(this.launchParams.startParam);
this.setReferrer(WebApp?.initDataUnsafe?.start_param);
// Set the Telegram bot URL
if (botUrl) {
store.commit.settings.setTelegramBotUrl(botUrl);
}
// Init utils
this.utils = initUtils();
console.info('[TMA]: Utils were initialized');
// Init haptic feedback
this.addHapticListener();
} catch (error) {
console.warn('[TMA]: init', error);
console.warn('[TMA]: disabling TMA mode because of the error:', error);
store.commit.settings.disableTMA();
store.commit.wallet.account.setIsDesktop(false);
}
}

/**
* **Should be used after `miniApp` init!**
*
* Update the theme of the Telegram Mini App using `var(--s-color-utility-body)`
*/
public static updateTheme(): void {
public updateTheme(): void {
try {
const colorUtilityBody =
(getComputedStyle(document.documentElement).getPropertyValue('--s-color-utility-body') as `#${string}`) ||
'#f7f3f4'; // Default color

this.miniApp.setHeaderColor(colorUtilityBody);
this.miniApp.setBgColor(colorUtilityBody);
const WebApp = Telegram?.WebApp;
WebApp?.setHeaderColor(colorUtilityBody);
WebApp?.setBackgroundColor(colorUtilityBody);
} catch (error) {
console.warn('[TMA]: updateTheme', error);
}
}

public static shareLink(url: string, text?: string): void {
public shareLink(url: string, text?: string): void {
try {
this.utils.shareURL(url, text ? encodeURIComponent(text) : text);
const desc = text ? encodeURIComponent(text) : text;
Telegram?.WebApp?.openLink(`https://t.me/share/url?url=${url}&text=${desc}`);
} catch (error) {
console.warn('[TMA]: shareLink', error);
}
}

private static async initViewport(): Promise<void> {
try {
const [viewport] = initViewport();
this.viewport = await viewport;
console.info('[TMA]: Viewport was initialized');
} catch (error) {
console.warn('[TMA]: initViewport', error);
public useHaptic(type: HapticFeedbackBinding): void {
useHaptic(type);
}

private onTouchEnd(event: TouchEvent): void {
const clickableSelectors = 'button, a, [data-clickable], .el-button, .clickable, [role="button"]';
const clickedElement = event.target as Nullable<HTMLElement>;

// Check if the clicked element matches any of the clickable selectors
if (clickedElement?.matches(clickableSelectors)) {
// Trigger Telegram WebApp HapticFeedback
useHaptic('soft');
}
}

private static setReferrer(referrerAddress?: string): void {
private addHapticListener(): void {
console.info('[TMA]: Haptic listener was added');
document.addEventListener('touchend', this.onTouchEnd);
}

private removeHapticListener(): void {
document.removeEventListener('touchend', this.onTouchEnd);
}

private setReferrer(referrerAddress?: string): void {
if (referrerAddress && api.validateAddress(referrerAddress)) {
store.commit.referrals.setStorageReferrer(referrerAddress);
console.info('[TMA]: Referrer was set', referrerAddress);
}
}

public destroy(): void {
this.removeHapticListener();
}
}

const tmaSdkService = new TmaSdk();

export { tmaSdkService };
4 changes: 2 additions & 2 deletions src/views/ReferralProgram.vue
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ import type { ReferrerRewards } from '@/indexer/queries/referrals';
import router, { lazyView } from '@/router';
import { action, getter, mutation, state } from '@/store/decorators';
import { formatAddress } from '@/utils';
import { TmaSdk } from '@/utils/telegram';
import { tmaSdkService } from '@/utils/telegram';
import type { CodecString } from '@sora-substrate/util';
import type { AccountAsset } from '@sora-substrate/util/build/assets/types';
Expand Down Expand Up @@ -410,7 +410,7 @@ export default class ReferralProgram extends Mixins(
}
const botUrl = `${this.telegramBotUrl}/app?startapp=${this.account.address}`;
TmaSdk.shareLink(botUrl, this.t('referralProgram.welcomeMessage'));
tmaSdkService.shareLink(botUrl, this.t('referralProgram.welcomeMessage'));
}
destroyed(): void {
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"webpack-env",
"jest",
"node",
"element-ui"
"element-ui",
"telegram-web-app"
],
"paths": {
"@/*": [
Expand Down
36 changes: 29 additions & 7 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1970,8 +1970,7 @@
"@polkadot/util-crypto" "^10.4.2"
rxjs "^7.8.0"

"@polkadot/api@11.2.1", "polkadotApi@npm:@polkadot/api@11.2.1":
name polkadotApi
"@polkadot/api@11.2.1":
version "11.2.1"
resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-11.2.1.tgz#b19a8e22367703333e71f3613095f76f0dbbca70"
integrity sha512-NwcWadMt+mrJ3T7RuwpnaIYtH4x0eix+GiKRtLMtIO32uAfhwVyMnqvLtxDxa4XDJ/es2rtSMYG+t0b1BTM+xQ==
Expand Down Expand Up @@ -3081,11 +3080,6 @@
dependencies:
defer-to-connect "^2.0.0"

"@tma.js/sdk@^2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@tma.js/sdk/-/sdk-2.7.0.tgz#1ae1fa6e5b21f6a5d959328ba84096bf5e6cc910"
integrity sha512-LRuJdoEbAqKmSYccvXx5emSnye2je8oIbBVPtV+BmcNoa/dCZ0gFVkaeuyBqzroGtfGDyGO/WebU54ie5MKmCQ==

"@tootallnate/once@1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
Expand Down Expand Up @@ -3510,6 +3504,11 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==

"@types/telegram-web-app@^7.3.1":
version "7.3.1"
resolved "https://registry.yarnpkg.com/@types/telegram-web-app/-/telegram-web-app-7.3.1.tgz#9edf1c18674b29c5abcf8f699fb8f367f6dae8ae"
integrity sha512-88VN+61MM6R3G0hhaHGJR+hMAazmNcfg568cn2WcGWZU4iAjzmogWZTgmZmwfaexrCdZ6PLVwoiQRoOX5/Djcg==

"@types/tough-cookie@*":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397"
Expand Down Expand Up @@ -12133,6 +12132,29 @@ pngjs@^5.0.0:
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==

"polkadotApi@npm:@polkadot/api@11.2.1":
version "11.2.1"
resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-11.2.1.tgz#b19a8e22367703333e71f3613095f76f0dbbca70"
integrity sha512-NwcWadMt+mrJ3T7RuwpnaIYtH4x0eix+GiKRtLMtIO32uAfhwVyMnqvLtxDxa4XDJ/es2rtSMYG+t0b1BTM+xQ==
dependencies:
"@polkadot/api-augment" "11.2.1"
"@polkadot/api-base" "11.2.1"
"@polkadot/api-derive" "11.2.1"
"@polkadot/keyring" "^12.6.2"
"@polkadot/rpc-augment" "11.2.1"
"@polkadot/rpc-core" "11.2.1"
"@polkadot/rpc-provider" "11.2.1"
"@polkadot/types" "11.2.1"
"@polkadot/types-augment" "11.2.1"
"@polkadot/types-codec" "11.2.1"
"@polkadot/types-create" "11.2.1"
"@polkadot/types-known" "11.2.1"
"@polkadot/util" "^12.6.2"
"@polkadot/util-crypto" "^12.6.2"
eventemitter3 "^5.0.1"
rxjs "^7.8.1"
tslib "^2.6.2"

portfinder@^1.0.26:
version "1.0.32"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.32.tgz#2fe1b9e58389712429dc2bea5beb2146146c7f81"
Expand Down

0 comments on commit 93f9787

Please sign in to comment.