Skip to content

Commit

Permalink
Telegram mini app support (#216)
Browse files Browse the repository at this point in the history
Co-authored-by: upalinski <[email protected]>
  • Loading branch information
upalinski and upalinski authored Dec 2, 2024
1 parent a53d7bf commit 532f77c
Show file tree
Hide file tree
Showing 19 changed files with 101 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v18.17.1
v18.17.1
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/communication/src/wallet/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Context, NetworkConfig } from '@cere/embed-wallet';
import type { PermissionRequest, BiconomyOptions } from '@cere-wallet/wallet-engine';

import { createChannel, CreateChannelOptions } from './createChannel';
import { AuthMethod } from '@cere/torus-embed';

export type UserInfo = {
email: string;
Expand Down Expand Up @@ -42,6 +43,7 @@ export type InitChannelIn = {
torusWidgetVisibility: boolean;
network: NetworkInterface;
biconomy?: BiconomyOptions;
authMethod?: AuthMethod;
};
};

Expand Down
2 changes: 1 addition & 1 deletion packages/embed-wallet-inject/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cere/embed-wallet-inject",
"version": "0.21.0",
"version": "0.22.1",
"sideEffects": false,
"type": "module",
"types": "./dist/types/index.d.ts",
Expand Down
4 changes: 2 additions & 2 deletions packages/embed-wallet/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cere/embed-wallet",
"version": "0.21.0",
"version": "0.22.1",
"description": "Cere Wallet SDK to integrate the wallet into a web application.",
"sideEffects": false,
"main": "dist/bundle.umd.js",
Expand All @@ -23,7 +23,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"@cere/torus-embed": "0.2.8",
"@cere/torus-embed": "0.2.9",
"@types/bn.js": "^5.1.1",
"@types/readable-stream": "^2.3.15",
"bn.js": "^5.2.1"
Expand Down
2 changes: 2 additions & 0 deletions packages/embed-wallet/src/EmbedWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export class EmbedWallet {
network,
context,
biconomy,
authMethod,
popupMode = 'modal',
connectOptions = {},
appId = this.options.appId,
Expand All @@ -175,6 +176,7 @@ export class EmbedWallet {
sessionId,
popupMode,
biconomy,
authMethod,
sessionNamespace,
context: this.defaultContext,
buildEnv: buildEnvMap[env],
Expand Down
3 changes: 2 additions & 1 deletion packages/embed-wallet/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { NetworkInterface } from '@cere/torus-embed';
import type {AuthMethod, NetworkInterface} from '@cere/torus-embed';
import BN from 'bn.js';

// App context
Expand Down Expand Up @@ -102,6 +102,7 @@ export type WalletInitOptions = WalletOptions & {
popupMode?: 'popup' | 'modal';
connectOptions?: Partial<WalletConnectOptions>;
biconomy?: BiconomyOptions;
authMethod?: AuthMethod;
};

export type WalletTransferOptions = {
Expand Down
2 changes: 1 addition & 1 deletion packages/wallet-engine/src/engine/polkadot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export const createPolkadotEngine = ({ getPrivateKey, polkadotRpc }: PolkadotEng
const [from, to, value] = req.params as [string, string, string];
const pair = getPair(from);

const hash = await api.tx.balances.transfer(to, value).signAndSend(pair);
const hash = await api.tx.balances.transferAllowDeath(to, value).signAndSend(pair);

res.result = hash.toHex();
}),
Expand Down
21 changes: 21 additions & 0 deletions src/api/auth-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,25 @@ export class AuthApiService {

return result?.data?.data || null;
}

public static async getTokenByTelegramMiniAppInitDataLink(botId: string, initData: string): Promise<string | null> {
let result: AxiosResponse<ApiResponse<TokenData>> | null = null;
try {
result = await api.post<{ code: 'SUCCESS' | 'ERROR'; data: { token: string } }>(
'/auth/token-by-telegram-mini-app-init-data',
{
botId,
initData,
},
);
} catch (error) {
const isUserError = error instanceof AxiosError && error.code === 'ERR_BAD_REQUEST';

if (!isUserError) {
reportError(error);
}
}

return result?.data.code === 'SUCCESS' ? result?.data.data.token : null;
}
}
2 changes: 1 addition & 1 deletion src/components/AccountDropdown/AccountDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const AccountDropdown = (props: AccountDropdownProps) => {
<Dropdown
open={open}
onToggle={setOpen}
label={<Truncate text={user.email} variant="email" maxLength={20} />}
label={<Truncate text={user.email} variant="text" maxLength={20} />}
leftElement={<Avatar src={user.avatar} />}
>
<Stack width={350} spacing={2}>
Expand Down
2 changes: 1 addition & 1 deletion src/components/WalletWidget/WalletWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ const WalletWidget = () => {
<Widget>
<Card>
<Header
title={<Truncate variant="email" text={user.email} maxLength={maxLength} />}
title={<Truncate variant="text" text={user.email} maxLength={maxLength} />}
subheader={<Address variant="text" address={selectedAccount.address} maxLength={maxLength} />}
avatar={<Avatar src={user.avatar} />}
action={<CopyButton value={selectedAccount.address} successMessage="Address copied" />}
Expand Down
3 changes: 3 additions & 0 deletions src/routes/AuthorizationRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
AuthorizePermissions,
AuthorizeComplete,
AuthorizeLink,
AuthorizeTelegramMiniApp,
} from './Authorize';

export const AuthorizationRouter = () => {
Expand All @@ -26,6 +27,8 @@ export const AuthorizationRouter = () => {
<Route element={<Authorize />}>
{email ? (
<Route index element={<AuthorizeOtp sendOtp />} />
) : store.isTelegramMiniApp ? (
<Route index element={<AuthorizeTelegramMiniApp />} />
) : (
<>
<Route index element={isGame || skipLoginIntro ? <AuthorizeLogin /> : <AuthorizeIntro />} />
Expand Down
30 changes: 30 additions & 0 deletions src/routes/Authorize/AuthorizeTelegramMiniApp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { observer } from 'mobx-react-lite';
import { useEffect } from 'react';

import { useOutletContext } from 'react-router-dom';

import { AuthorizePopupStore } from '~/stores';
import { useAppContextStore } from '~/hooks';
import { AuthApiService } from '~/api/auth-api.service';

const AuthorizeTelegramMiniApp = () => {
const authStore = useOutletContext<AuthorizePopupStore>();
const appContext = useAppContextStore();

useEffect(() => {
if (!appContext.authMethod) {
throw new Error('telegram-mini-app requires authMethod to be passed');
}

const [botId, initData] = appContext.authMethod.token.split(':');
AuthApiService.getTokenByTelegramMiniAppInitDataLink(botId, initData).then((idToken) => {
authStore.login(idToken!!).then(() => {
authStore.acceptSession();
});
});
}, [authStore, appContext]);

return <div></div>;
};

export default observer(AuthorizeTelegramMiniApp);
1 change: 1 addition & 0 deletions src/routes/Authorize/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { default as AuthorizeLogin } from './AuthorizeLogin';
export { default as AuthorizeOtp } from './AuthorizeOtp';
export { default as AuthorizeComplete } from './AuthorizeComplete';
export { default as AuthorizeLink } from './AuthorizeLink';
export { default as AuthorizeTelegramMiniApp } from './AuthorizeTelegramMiniApp';
7 changes: 5 additions & 2 deletions src/routes/EmbeddedWallet/EmbeddedModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect } from 'react';
import { Dialog, DialogContent } from '@cere-wallet/ui';
import { observer } from 'mobx-react-lite';

import { useFullScreen, usePopupManagerStore } from '~/hooks';
import { useAppContextStore, useFullScreen, usePopupManagerStore } from '~/hooks';
import { PopupManagerModal } from '~/stores';
import { RouteElement } from '../RouteElement';

Expand All @@ -14,14 +14,17 @@ type EmbeddedModalProps = {
const EmbeddedModal = ({ modal, showClose }: EmbeddedModalProps) => {
const popupStore = usePopupManagerStore();
const [isFullscreen, setFullscreen] = useFullScreen();
const appContextStore = useAppContextStore();

useEffect(() => {
setFullscreen(true);

return () => setFullscreen(false);
}, [modal, setFullscreen]);

return (
return appContextStore.isTelegramMiniApp ? (
<RouteElement path={modal.path} context={{ preopenInstanceId: modal.instanceId }} />
) : (
<Dialog
showClose={showClose}
origin="right"
Expand Down
2 changes: 1 addition & 1 deletion src/routes/EmbeddedWallet/EmbeddedWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const EmbeddedWallet = () => {
return (
<UIProvider transparentBody isGame={isGame} whiteLabel={toJS(whiteLabel)}>
<WalletContext.Provider value={store}>
<WalletWidget />
{store.appContextStore.isTelegramMiniApp ? <></> : <WalletWidget />}
{modal && <EmbeddedModal showClose={!isGame} modal={modal} />}
</WalletContext.Provider>
</UIProvider>
Expand Down
6 changes: 3 additions & 3 deletions src/routes/FramePopup/FramePopup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { styled, Loading, Logo } from '@cere-wallet/ui';
import { useCallback, useEffect, useState } from 'react';
import { usePopupStore } from '~/hooks';
import { useAppContextStore, usePopupStore } from '~/hooks';
import { RedirectPopupStore } from '~/stores';

// @ts-ignore
Expand All @@ -26,7 +26,7 @@ export const FramePopup = () => {
const [url, setUrl] = useState<string>();
const [loaded, setLoaded] = useState(false);
const hideLoader = useCallback(() => setLoaded(true), []);

const appContextStore = useAppContextStore();
const store = usePopupStore((popupId) => new RedirectPopupStore(popupId, true));

useEffect(() => {
Expand All @@ -39,7 +39,7 @@ export const FramePopup = () => {

return (
<>
{!loaded && (
{!loaded && !appContextStore.isTelegramMiniApp && (
<Loading sx={{ position: 'absolute' }} fullScreen>
<Logo />
</Loading>
Expand Down
14 changes: 14 additions & 0 deletions src/stores/AppContextStore/AppContextStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { makeAutoObservable } from 'mobx';

import { Wallet } from '../types';
import { createSharedState } from '../sharedState';
import { AuthMethod } from '@cere/torus-embed';

export type App = Omit<NonNullable<AppContext['app']>, 'name'> & {
name: string;
Expand All @@ -14,6 +15,7 @@ export type ContextBanner = AppContext['banner'] & {

type SharedState = {
context?: AppContext;
authMethod?: AuthMethod;
};

export class AppContextStore {
Expand All @@ -35,6 +37,14 @@ export class AppContextStore {
this.shared.state.context = context;
}

set authMethod(authMethod: AuthMethod | undefined) {
this.shared.state.authMethod = authMethod;
}

get authMethod(): AuthMethod | undefined {
return this.shared.state.authMethod;
}

get banner(): ContextBanner | undefined {
if (this.context?.banner) {
return { variant: 'banner', ...this.context.banner };
Expand Down Expand Up @@ -74,6 +84,10 @@ export class AppContextStore {
return { ...this.context.app, name };
}

get isTelegramMiniApp(): boolean {
return (this.app?.appId as string) === 'telegram-mini-app';
}

async disconnect() {
this.context = undefined;
}
Expand Down
8 changes: 5 additions & 3 deletions src/stores/EmbededWalletStore/EmbeddedWalletStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,15 @@ export class EmbeddedWalletStore implements Wallet {
this.walletConnection = createWalletConnection({
logger: console,

onInit: async ({ chainConfig, context, biconomy }) => {
onInit: async ({ chainConfig, context, biconomy, authMethod }) => {
this.networkStore.network = chainConfig;
this.appContextStore.context = context;
if (authMethod) {
this.appContextStore.authMethod = authMethod;
}

/**
* Configure the wallet with the init optionss
* Configure the wallet with the init options
*/
if (biconomy) {
this.options.biconomy = biconomy;
Expand All @@ -185,7 +188,6 @@ export class EmbeddedWalletStore implements Wallet {
if (loginOptions.uxMode === 'modal') {
return this.authenticationStore.loginInModal(preopenInstanceId, loginOptions);
}

return this.authenticationStore.loginInPopup(preopenInstanceId, loginOptions);
},

Expand Down

0 comments on commit 532f77c

Please sign in to comment.