Skip to content

Commit

Permalink
feat(ui): added user event tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
thekiba committed May 9, 2024
1 parent f43592a commit a9d3461
Show file tree
Hide file tree
Showing 4 changed files with 649 additions and 0 deletions.
15 changes: 15 additions & 0 deletions packages/ui/src/library.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
export { TonConnectUI as default } from './ton-connect-ui';
export { TonConnectUI } from './ton-connect-ui';
export type {
UserActionEvent,
ConnectionEvent,
ConnectionStartedEvent,
ConnectionCompletedEvent,
ConnectionErrorEvent,
ConnectionRestoringStartedEvent,
ConnectionRestoringCompletedEvent,
ConnectionRestoringErrorEvent,
DisconnectionEvent,
TransactionSigningEvent,
TransactionSentForSignatureEvent,
TransactionSignedEvent,
TransactionSigningFailedEvent
} from './tracker/types';
export * from './models';
export * from './errors';
28 changes: 28 additions & 0 deletions packages/ui/src/ton-connect-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { isInTMA, sendExpand } from 'src/app/utils/tma-api';
import { redirectToTelegram, redirectToWallet } from 'src/app/utils/url-strategy-helpers';
import { SingleWalletModalManager } from 'src/managers/single-wallet-modal-manager';
import { SingleWalletModal, SingleWalletModalState } from 'src/models/single-wallet-modal';
import { TonConnectTracker } from 'src/tracker/ton-connect-tracker';

export class TonConnectUI {
public static getWallets(): Promise<WalletInfo[]> {
Expand All @@ -47,6 +48,8 @@ export class TonConnectUI {

private readonly preferredWalletStorage = new PreferredWalletStorage();

private readonly tracker = new TonConnectTracker();

private walletInfo: WalletInfoWithOpenMethod | null = null;

private systemThemeChangeUnsubscribe: (() => void) | null = null;
Expand Down Expand Up @@ -218,11 +221,15 @@ export class TonConnectUI {
this.subscribeToWalletChange();

if (options?.restoreConnection !== false) {
this.tracker.trackConnectionRestoringStarted();
this.connectionRestored = new Promise(async resolve => {
await this.connector.restoreConnection();

if (!this.connector.connected) {
this.tracker.trackConnectionRestoringError('Connection was not restored');
this.walletInfoStorage.removeWalletInfo();
} else {
this.tracker.trackConnectionRestoringCompleted(this.wallet);
}

resolve(this.connector.connected);
Expand Down Expand Up @@ -287,13 +294,15 @@ export class TonConnectUI {
* Opens the modal window, returns a promise that resolves after the modal window is opened.
*/
public async openModal(): Promise<void> {
this.tracker.trackConnectionStarted();
return this.modal.open();
}

/**
* Closes the modal window.
*/
public closeModal(): void {
this.tracker.trackConnectionError('Connection was cancelled');
this.modal.close();
}

Expand All @@ -316,6 +325,7 @@ export class TonConnectUI {
* @experimental
*/
public async openSingleWalletModal(wallet: string): Promise<void> {
this.tracker.trackConnectionStarted();
return this.singleWalletModal.open(wallet);
}

Expand All @@ -324,6 +334,7 @@ export class TonConnectUI {
* @experimental
*/
public closeSingleWalletModal(): void {
this.tracker.trackConnectionError('Connection was cancelled');
this.singleWalletModal.close();
}

Expand Down Expand Up @@ -366,6 +377,8 @@ export class TonConnectUI {
* Disconnect wallet and clean localstorage.
*/
public disconnect(): Promise<void> {
this.tracker.trackDisconnection(this.wallet, 'dapp');

widgetController.clearAction();
widgetController.removeSelectedWalletInfo();
this.walletInfoStorage.removeWalletInfo();
Expand All @@ -381,7 +394,10 @@ export class TonConnectUI {
tx: SendTransactionRequest,
options?: ActionConfiguration
): Promise<SendTransactionResponse> {
this.tracker.trackTransactionSentForSignature(this.wallet, tx);

if (!this.connected) {
this.tracker.trackTransactionSigningFailed(this.wallet, tx, 'Wallet was not connected');
throw new TonConnectUIError('Connect wallet to send a transaction.');
}

Expand Down Expand Up @@ -459,6 +475,8 @@ export class TonConnectUI {
onRequestSent
);

this.tracker.trackTransactionSigned(this.wallet, tx, result);

widgetController.setAction({
name: 'transaction-sent',
showNotification: notifications.includes('success'),
Expand All @@ -467,6 +485,8 @@ export class TonConnectUI {

return result;
} catch (e) {
this.tracker.trackTransactionSigningFailed(this.wallet, tx, e.message);

widgetController.setAction({
name: 'transaction-canceled',
showNotification: notifications.includes('error'),
Expand Down Expand Up @@ -554,14 +574,18 @@ export class TonConnectUI {
options: WaitWalletConnectionOptions
): Promise<ConnectedWallet> {
return new Promise((resolve, reject) => {
this.tracker.trackConnectionStarted();
const { ignoreErrors = false, signal = null } = options;

if (signal && signal.aborted) {
this.tracker.trackConnectionError('Connection was cancelled');
return reject(new TonConnectUIError('Wallet was not connected'));
}

const onStatusChangeHandler = async (wallet: ConnectedWallet | null): Promise<void> => {
if (!wallet) {
this.tracker.trackConnectionError('Connection was cancelled');

if (ignoreErrors) {
// skip empty wallet status changes to avoid aborting the process
return;
Expand All @@ -570,12 +594,16 @@ export class TonConnectUI {
unsubscribe();
reject(new TonConnectUIError('Wallet was not connected'));
} else {
this.tracker.trackConnectionCompleted(wallet);

unsubscribe();
resolve(wallet);
}
};

const onErrorsHandler = (reason: TonConnectError): void => {
this.tracker.trackConnectionError(reason.message);

if (ignoreErrors) {
// skip errors to avoid aborting the process
return;
Expand Down
184 changes: 184 additions & 0 deletions packages/ui/src/tracker/ton-connect-tracker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import {
createConnectionCompletedEvent,
createConnectionErrorEvent,
createConnectionRestoringCompletedEvent,
createConnectionRestoringErrorEvent,
createConnectionRestoringStartedEvent,
createConnectionStartedEvent,
createDisconnectionEvent,
createTransactionSentForSignatureEvent,
createTransactionSignedEvent,
createTransactionSigningFailedEvent,
UserActionEvent
} from './types';
import { getWindow } from 'src/app/utils/web-api';

/**
* Tracker for TonConnectUI user actions, such as transaction signing, connection, etc.
*
* List of events:
* — `connection-started`: when a user starts connecting a wallet.
* — `connection-completed`: when a user successfully connected a wallet.
* — `connection-error`: when a user cancels a connection or there is an error during the connection process.
* — `disconnection`: when a user starts disconnecting a wallet.
* — `transaction-sent-for-signature`: when a user sends a transaction for signature.
* — `transaction-signed`: when a user successfully signs a transaction.
* — `transaction-signing-failed`: when a user cancels transaction signing or there is an error during the signing process.
*
* If you want to track user actions, you can subscribe to the window events with prefix `ton-connect-ui-`:
* ```typescript
* window.addEventListener('ton-connect-ui-transaction-sent-for-signature', (event) => {
* console.log('Transaction init', event.detail);
* });
* ```
*
* @internal
*/
export class TonConnectTracker {
/**
* Event prefix for user actions.
* @private
*/
private readonly eventPrefix = 'ton-connect-ui-';

/**
* Window object, possibly undefined in the server environment.
* @private
*/
private readonly window: Window | undefined = getWindow();

/**
* Emit user action event to the window.
* @param eventDetails
* @private
*/
private dispatchUserActionEvent(eventDetails: UserActionEvent): void {
try {
const eventName = `${this.eventPrefix}${eventDetails.type}`;
const event = new CustomEvent<UserActionEvent>(eventName, { detail: eventDetails });
this.window?.dispatchEvent(event);
} catch (e) {}
}

/**
* Track connection init event.
* @param args
*/
public trackConnectionStarted(...args: Parameters<typeof createConnectionStartedEvent>): void {
try {
const event = createConnectionStartedEvent(...args);
this.dispatchUserActionEvent(event);
} catch (e) {}
}

/**
* Track connection success event.
* @param args
*/
public trackConnectionCompleted(
...args: Parameters<typeof createConnectionCompletedEvent>
): void {
try {
const event = createConnectionCompletedEvent(...args);
this.dispatchUserActionEvent(event);
} catch (e) {}
}

/**
* Track connection error event.
* @param args
*/
public trackConnectionError(...args: Parameters<typeof createConnectionErrorEvent>): void {
try {
const event = createConnectionErrorEvent(...args);
this.dispatchUserActionEvent(event);
} catch (e) {}
}

/**
* Track connection restoring init event.
* @param args
*/
public trackConnectionRestoringStarted(
...args: Parameters<typeof createConnectionRestoringStartedEvent>
): void {
try {
const event = createConnectionRestoringStartedEvent(...args);
this.dispatchUserActionEvent(event);
} catch (e) {}
}

/**
* Track connection restoring success event.
* @param args
*/
public trackConnectionRestoringCompleted(
...args: Parameters<typeof createConnectionRestoringCompletedEvent>
): void {
try {
const event = createConnectionRestoringCompletedEvent(...args);
this.dispatchUserActionEvent(event);
} catch (e) {}
}

/**
* Track connection restoring error event.
* @param args
*/
public trackConnectionRestoringError(
...args: Parameters<typeof createConnectionRestoringErrorEvent>
): void {
try {
const event = createConnectionRestoringErrorEvent(...args);
this.dispatchUserActionEvent(event);
} catch (e) {}
}

/**
* Track disconnect event.
* @param args
*/
public trackDisconnection(...args: Parameters<typeof createDisconnectionEvent>): void {
try {
const event = createDisconnectionEvent(...args);
this.dispatchUserActionEvent(event);
} catch (e) {}
}

/**
* Track transaction init event.
* @param args
*/
public trackTransactionSentForSignature(
...args: Parameters<typeof createTransactionSentForSignatureEvent>
): void {
try {
const event = createTransactionSentForSignatureEvent(...args);
this.dispatchUserActionEvent(event);
} catch (e) {}
}

/**
* Track transaction signed event.
* @param args
*/
public trackTransactionSigned(...args: Parameters<typeof createTransactionSignedEvent>): void {
try {
const event = createTransactionSignedEvent(...args);
this.dispatchUserActionEvent(event);
} catch (e) {}
}

/**
* Track transaction error event.
* @param args
*/
public trackTransactionSigningFailed(
...args: Parameters<typeof createTransactionSigningFailedEvent>
): void {
try {
const event = createTransactionSigningFailedEvent(...args);
this.dispatchUserActionEvent(event);
} catch (e) {}
}
}
Loading

0 comments on commit a9d3461

Please sign in to comment.