Skip to content

Commit

Permalink
Migrate to Telegram SDK
Browse files Browse the repository at this point in the history
  • Loading branch information
dnischeta committed Aug 1, 2024
1 parent b777d51 commit 29b7792
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 268 deletions.
75 changes: 3 additions & 72 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Vanilla JS example

> ⚠️ Please, avoid using vanilla JavaScript if possible on Telegram Mini Apps
> platform. It is better to use ES modules at least. [Learn more](#about-iife).
> platform. It is better to use ES modules at least.
This example shows how developer could use Vanilla JavaScript to start developing at
Telegram Mini Apps platform.
Expand All @@ -10,7 +10,7 @@ This template demonstrates how developers can implement an application on the Te
Mini Apps platform using the following technologies and libraries

- [TON Connect](https://docs.ton.org/develop/dapps/ton-connect/overview)
- [@telegram-apps SDK](https://docs.telegram-mini-apps.com/packages/telegram-apps-sdk)
- [Telegram SDK](https://core.telegram.org/bots/webapps#initializing-mini-apps)

> This boilerplate was created using [pnpm](https://pnpm.io/). Therefore, it is required to use
> it for this project as well.
Expand Down Expand Up @@ -73,15 +73,9 @@ devices in the same network with the current device.
To view the application, you need to open the `Local`
link (`http://localhost:3000` in this example) in your browser.

It is important to note that some libraries in this template, such as `@telegram-apps/sdk`, are not
It is important to note that some libraries in this template, such as Telegram SDK, are not
intended for use outside of Telegram.

Nevertheless, they appear to function properly. This is because the `dist/js/mockEnv.ts` file, which is
imported in the application's entry point (`dist/index.html`), employs the `mockTelegramEnv` function
to simulate the Telegram environment. This trick convinces the application that it is running in a
Telegram-based environment. Therefore, be cautious not to use this function in production mode
unless you fully understand its implications.

### Run Inside Telegram

Although it is possible to run the application outside of Telegram, it is recommended to develop it
Expand All @@ -108,69 +102,6 @@ to [@BotFather](https://t.me/botfather). Then, navigate
to [https://web.telegram.org/k/](https://web.telegram.org/k/), find your bot, and launch the
Telegram Mini App. This approach provides the full development experience.

## About IIFE

### Dependencies

Some of the packages use other `@tma.js` packages as dependencies. In this case there are 2
ways of importing them:

1. **By inserting another `script` tag which loads the dependency**.
This way makes usage of package with a lot of dependencies almost unreal.
2. **By inlining these packages**.
This way leads to code duplication between several packages using the same package as dependency.

As you can see, there is no optimal solution between both of them. As the additional problem
developer gets here, is bundler is unable to
use [tree shaking](https://stackoverflow.com/questions/45884414/what-is-tree-shaking-and-why-would-i-need-it),
making browser to load the code not used in the application. Imagine using the only 1 function from
some library like `lodash`, but fully load it.

### Unknown target

The other problem developer can face is IIFE packages are built for the specific browser of specific
version. So, the package author does not know which target he should choose as long as he doesn't
know it when creating such package. That's why the the package target should be lowered to support
most part of browsers, but this also make final bunlde bigger.

### Conclusion

Unfortunately, developer is unable to avoid these problems when using IIFE format. This is the
reason why it is recommended to use modern technologies along with ESM format.

### When there is no other choice

First of all, it is required to load the package. Developer could use [JSDelivr](https://www.jsdelivr.com/)
to do it:

```html

<head>
<script src="https://cdn.jsdelivr.net/npm/@telegram-apps/sdk/dist/index.iife.js"></script>
</head>
```

Loaded packages of `@telegram-apps` in IIFE format are accessible by path `window.telegramApps.*`:

```html

<head>
<script src="https://cdn.jsdelivr.net/npm/@telegram-apps/sdk/dist/index.iife.js"></script>
</head>
<body>
<script>
var sdk = window.telegramApps.sdk;
console.log(sdk.retrieveLaunchData());
</script>
</body>
```

> ⚠️ In this example we did not specify the exact version of required package. In this case,
> JSDelivr CDN will return the latest version of the package which in some cases may lead to
> unexpected behavior. To prevent such case, specify the exact version.


## Deploy

This boilerplate uses GitHub Pages as the way to host the application externally. GitHub Pages
Expand Down
6 changes: 1 addition & 5 deletions dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,14 @@
<link href="css/link.css" rel="stylesheet">
<link href="css/displayData.css" rel="stylesheet">

<script src="https://cdn.jsdelivr.net/npm/@telegram-apps/[email protected]/dist/index.iife.js"></script>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<script src="https://unpkg.com/@tonconnect/[email protected]/dist/tonconnect-ui.min.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="js/utils.js"></script>
<!-- Uncomment next line for local development outside Telegram Mini App -->
<!-- <script src="js/mockEnv.js"></script> -->
<script src="js/initNavigrator.js"></script>
<script src="js/initComponents.js"></script>
<script src="js/initTonConnect.js"></script>

<script src="js/components/DisplayData.js"></script>
Expand Down
6 changes: 5 additions & 1 deletion dist/js/components/DisplayData.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
function isRGB(value) {
return /^#[a-f0-9]{3,6}$/i.test(value);
}

class DisplayData {
constructor({ rows }) {
this.el = $('<div/>');
Expand All @@ -15,7 +19,7 @@ class DisplayData {
this.el.empty().append(
...rows.map(row => {
const lineValue = $('<span class="display-data__line-value"/>');
if (typeof row.value === 'string' && window.telegramApps.sdk.isRGB(row.value)) {
if (typeof row.value === 'string' && isRGB(row.value)) {
lineValue.append(new RGB({ color: row.value }).element());
} else if (row.value === false) {
lineValue.text('❌');
Expand Down
4 changes: 2 additions & 2 deletions dist/js/components/Link.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ class Link {
this.el = $('<a/>')
.attr('class', 'link')
.addClass(className ?? '')
.attr('href', isExternal ? href : context.navigator.renderPath(href));
.attr('href', isExternal ? href : `#${href}`);

if (isExternal) {
this.el.on('click', (e) => {
e.preventDefault();
context.utils.openLink(targetUrl.toString());
context.getWebApp().openLink(targetUrl.toString());
});
}
}
Expand Down
125 changes: 57 additions & 68 deletions dist/js/index.js
Original file line number Diff line number Diff line change
@@ -1,70 +1,59 @@
(async () => {
// Uncomment next line for local development outside Telegram Mini App
// mockEnv();

const launchParams = window.telegramApps.sdk.retrieveLaunchParams();

// Launch eruda and enable SDK debug mode, if debug mode was requested outside.
const debug = launchParams.startParam === 'debug';
if (debug) {
window.telegramApps.sdk.setDebug(debug);
const tonConnectUI = initTonConnectUI();

const routes = [
{ pathname: '/', Page: HomePage },
{ pathname: '/init-data', Page: InitDataPage, title: 'Init Data' },
{ pathname: '/theme-params', Page: ThemeParamsPage, title: 'Theme Params' },
{ pathname: '/launch-params', Page: LaunchParamsPage, title: 'Launch Params' },
{
pathname: '/ton-connect',
Page: TonConnectPage,
title: 'TON Connect',
icon: `${window.location.origin}${window.location.pathname}ton.svg`,
},
];

const root = document.getElementById('root');
const appContext = {
getWebApp() {
return window.Telegram.WebApp;
},
tonConnectUI,
routes,
};
let prevPage;


appContext.getWebApp().BackButton.onClick(goBack);

window.addEventListener('hashchange', () => {
const path = window.location.hash.slice(1);
renderCurrentRoute(path);
updateBackButton(path)
})

renderCurrentRoute(window.location.hash.slice(1));

function renderCurrentRoute(path) {
const route = routes.find(r => r.pathname === path);
if (!route) {
window.location.hash = '#/';
return;
}

// The web version of Telegram is capable of sending some specific CSS styles we would
// like to catch.
if (window.telegramApps.sdk.isIframe()) {
window.telegramApps.sdk.initWeb(true);
prevPage && prevPage.destroy && prevPage.destroy();
prevPage = new route.Page(appContext);
prevPage.init && prevPage.init();
prevPage.render(root);
}

function goBack() {
window.history.go(-1);
}

function updateBackButton(path) {
if (path === '/') {
appContext.getWebApp().BackButton.isVisible && appContext.getWebApp().BackButton.hide();
} else {
!appContext.getWebApp().BackButton.isVisible && appContext.getWebApp().BackButton.show();
}

const {
miniApp,
viewport,
utils,
themeParams,
initData,
} = await initComponents();
const navigator = await initNavigator();
const tonConnectUI = initTonConnectUI();

const routes = [
{ pathname: '/', Page: HomePage },
{ pathname: '/init-data', Page: InitDataPage, title: 'Init Data' },
{ pathname: '/theme-params', Page: ThemeParamsPage, title: 'Theme Params' },
{ pathname: '/launch-params', Page: LaunchParamsPage, title: 'Launch Params' },
{
pathname: '/ton-connect',
Page: TonConnectPage,
title: 'TON Connect',
icon: `${window.location.origin}${window.location.pathname}ton.svg`,
},
];

const root = document.getElementById('root');
const appContext = {
initData,
launchParams,
miniApp,
navigator,
themeParams,
utils,
viewport,
tonConnectUI,
routes,
};
let prevPage;

function renderCurrentRoute() {
const route = routes.find(r => r.pathname === navigator.pathname);
if (!route) {
navigator.replace('/');
return;
}
prevPage && prevPage.destroy && prevPage.destroy();
prevPage = new route.Page(appContext);
prevPage.init && prevPage.init();
prevPage.render(root);
}

navigator.on('change', renderCurrentRoute);
renderCurrentRoute();
})();
}
22 changes: 0 additions & 22 deletions dist/js/initComponents.js

This file was deleted.

8 changes: 0 additions & 8 deletions dist/js/initNavigrator.js

This file was deleted.

63 changes: 0 additions & 63 deletions dist/js/mockEnv.js

This file was deleted.

Loading

0 comments on commit 29b7792

Please sign in to comment.