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 5, 2024
1 parent b777d51 commit e64811b
Show file tree
Hide file tree
Showing 20 changed files with 483 additions and 643 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
73 changes: 37 additions & 36 deletions dist/js/components/DisplayData.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,41 @@
class DisplayData {
constructor({ rows }) {
this.el = $('<div/>');
this.setRows(rows);
}
function isRGB(value) {
return /^#[a-f0-9]{3,6}$/i.test(value);
}

/**
* @returns {HTMLDivElement}
*/
element() {
return this.el[0];
}
function DisplayData(options) {
var rows = options.rows;
this.el = $('<div/>');
this.setRows(rows);
}

setRows(rows) {
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)) {
lineValue.append(new RGB({ color: row.value }).element());
} else if (row.value === false) {
lineValue.text('❌');
} else if (row.value === true) {
lineValue.text('✔️');
} else if (row.value === undefined) {
lineValue.html('<i>empty</i>');
} else if (row.value instanceof HTMLElement) {
lineValue.append(row.value);
} else {
lineValue.append(row.value.toString());
}
/**
* @returns {HTMLDivElement}
*/
DisplayData.prototype.element = function() {
return this.el[0];
};

return $('<div class="display-data__line"/>').append(
$('<span class="display-data__line-title"/>').text(row.title),
lineValue,
);
}),
DisplayData.prototype.setRows = function(rows) {
this.el.empty().append.apply(this.el, rows.map(function(row) {
var lineValue = $('<span class="display-data__line-value"/>');
if (typeof row.value === 'string' && isRGB(row.value)) {
lineValue.append(new RGB({ color: row.value }).element());
} else if (row.value === false) {
lineValue.text('❌');
} else if (row.value === true) {
lineValue.text('✔️');
} else if (row.value === undefined) {
lineValue.html('<i>empty</i>');
} else if (row.value instanceof HTMLElement) {
lineValue.append(row.value);
} else {
lineValue.append(row.value.toString());
}

return $('<div class="display-data__line"/>').append(
$('<span class="display-data__line-title"/>').text(row.title),
lineValue
);
return this;
}
}
}));
return this;
};
55 changes: 28 additions & 27 deletions dist/js/components/Link.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
class Link {
constructor({ href, class: className }, context) {
const targetUrl = new URL(href, window.location.toString());
const currentUrl = new URL(window.location.toString());
const isExternal = targetUrl.protocol !== currentUrl.protocol
|| targetUrl.host !== currentUrl.host;
function Link(options, context) {
var href = options.href;
var className = options.class;

var targetUrl = new URL(href, window.location.toString());
var currentUrl = new URL(window.location.toString());
var isExternal = targetUrl.protocol !== currentUrl.protocol || targetUrl.host !== currentUrl.host;

this.el = $('<a/>')
.attr('class', 'link')
.addClass(className ?? '')
.attr('href', isExternal ? href : context.navigator.renderPath(href));
this.el = $('<a/>')
.attr('class', 'link')
.addClass(className || '')
.attr('href', isExternal ? href : '#' + href);

if (isExternal) {
this.el.on('click', (e) => {
e.preventDefault();
context.utils.openLink(targetUrl.toString());
});
}
if (isExternal) {
this.el.on('click', function(e) {
e.preventDefault();
context.getWebApp().openLink(targetUrl.toString());
});
}
}

appendChild(...children) {
this.el.append(...filterChildren(children));
return this;
}
Link.prototype.appendChild = function() {
var children = Array.prototype.slice.call(arguments);
this.el.append.apply(this.el, filterChildren(children));
return this;
};

/**
* @returns {HTMLAnchorElement}
*/
element() {
return this.el[0];
}
}
/**
* @returns {HTMLAnchorElement}
*/
Link.prototype.element = function() {
return this.el[0];
};
49 changes: 25 additions & 24 deletions dist/js/components/Page.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
class Page {
constructor({ title }) {
this.el = $('<div class="page"/>').append($('<h1/>').text(title));
}
function Page(options) {
var title = options.title;
this.el = $('<div class="page"/>').append($('<h1/>').text(title));
}

appendChild(...children) {
this.el.append(...filterChildren(children));
return this;
}
Page.prototype.appendChild = function() {
var children = Array.prototype.slice.call(arguments);
this.el.append.apply(this.el, filterChildren(children));
return this;
};

/**
* @returns {HTMLDivElement}
*/
element() {
return this.el[0];
}
/**
* @returns {HTMLDivElement}
*/
Page.prototype.element = function() {
return this.el[0];
};

setDisclaimer(disclaimer) {
if (this.disclaimer) {
this.disclaimer.empty().append(...toArray(disclaimer));
} else {
this.disclaimer = $('<div class="page__disclaimer"/>')
.append(...toArray(disclaimer))
.insertAfter(this.el.children('h1'));
}
return this;
Page.prototype.setDisclaimer = function(disclaimer) {
if (this.disclaimer) {
this.disclaimer.empty().append.apply(this.disclaimer, toArray(disclaimer));
} else {
var disclaimerEl = $('<div class="page__disclaimer"/>');
this.disclaimer = disclaimerEl
.append.apply(disclaimerEl, toArray(disclaimer))
.insertAfter(this.el.children('h1'));
}
}
return this;
};
22 changes: 10 additions & 12 deletions dist/js/components/PageComponent.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
class PageComponent {
constructor(page) {
this.page = page;
}
function PageComponent(page) {
this.page = page;
}

/**
* @param {HTMLElement} root
* @returns {void}
*/
render(root) {
$(root).empty().append(this.page.element());
}
}
/**
* @param {HTMLElement} root
* @returns {void}
*/
PageComponent.prototype.render = function(root) {
$(root).empty().append(this.page.element());
};
41 changes: 21 additions & 20 deletions dist/js/components/RGB.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
class RGB {
/**
* @param {{ color: string; class?: string }}
*/
constructor({ color, class: className }) {
this.el = $('<span/>')
.attr('class', 'rgb')
.addClass(className ?? '')
.append(
$(`<i class="rgb__icon" style="background-color: ${color}"/>`),
color,
);
}
/**
* @param {{ color: string; class?: string }} options
*/
function RGB(options) {
var color = options.color;
var className = options.class;

this.el = $('<span/>')
.attr('class', 'rgb')
.addClass(className || '')
.append(
$('<i class="rgb__icon"/>').css('background-color', color),
color
);
}

/**
* @returns {HTMLSpanElement}
*/
element() {
return this.el[0];
}
}
/**
* @returns {HTMLSpanElement}
*/
RGB.prototype.element = function() {
return this.el[0];
};
Loading

0 comments on commit e64811b

Please sign in to comment.