Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 56 additions & 43 deletions packages/walletkit/src/core/BridgeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type {
import { uuidv7 } from '../utils/uuid';
import { WalletKitError, ERROR_CODES } from '../errors';
import type { Analytics, AnalyticsManager } from '../analytics';
import type { TonConnectEventsHandler } from './TonConnectEventsHandler';
import type { TonWalletKitOptions } from '../types/config';
import { TONCONNECT_BRIDGE_RESPONSE } from '../bridge/JSBridgeInjector';
import type { BridgeEvent, TONConnectSession } from '../api/models';
Expand All @@ -48,7 +49,7 @@ export class BridgeManager {

// Event processing queue and concurrency control
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private eventQueue: any[] = [];
private eventQueue: Array<{ event: any; eventsHandler?: TonConnectEventsHandler }> = [];
private isProcessing = false;

// Durable events support
Expand Down Expand Up @@ -479,7 +480,7 @@ export class BridgeManager {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private queueBridgeEvent(event: any): void {
log.debug('Bridge event queued', { eventId: event?.id, event });
this.eventQueue.push(event);
this.eventQueue.push({ event });

// Trigger processing (don't wait for it to complete)
this.processBridgeEvents().catch((error) => {
Expand All @@ -490,6 +491,7 @@ export class BridgeManager {
public queueJsBridgeEvent(
messageInfo: BridgeEventMessageInfo,
event: InjectedToExtensionBridgeRequestPayload,
eventsHandler?: TonConnectEventsHandler,
): void {
log.debug('JS Bridge event queued', { eventId: messageInfo?.messageId });

Expand All @@ -504,12 +506,15 @@ export class BridgeManager {

if (event.method == 'connect') {
this.eventQueue.push({
...event,
isJsBridge: true,
tabId: messageInfo.tabId,
domain: messageInfo.domain,
messageId: messageInfo.messageId,
walletId: messageInfo.walletId,
event: {
...event,
isJsBridge: true,
tabId: messageInfo.tabId,
domain: messageInfo.domain,
messageId: messageInfo.messageId,
walletId: messageInfo.walletId,
},
eventsHandler,
});
} else if (event.method == 'restoreConnection') {
this.eventEmitter?.emit('restoreConnection', {
Expand All @@ -521,14 +526,17 @@ export class BridgeManager {
});
} else if (event.method == 'send' && event?.params?.length === 1) {
this.eventQueue.push({
...event,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...(event as any).params[0],
isJsBridge: true,
tabId: messageInfo.tabId,
domain: messageInfo.domain,
messageId: messageInfo.messageId,
walletId: messageInfo.walletId,
event: {
...event,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...(event as any).params[0],
isJsBridge: true,
tabId: messageInfo.tabId,
domain: messageInfo.domain,
messageId: messageInfo.messageId,
walletId: messageInfo.walletId,
},
eventsHandler,
});
}

Expand Down Expand Up @@ -557,11 +565,11 @@ export class BridgeManager {
try {
// Process all events in FIFO order
while (this.eventQueue.length > 0) {
const event = this.eventQueue.shift();
if (event) {
const item = this.eventQueue.shift();
if (item) {
// Important: set isLocal to false for all events from bridge
event.isLocal = false;
await this.handleBridgeEvent(event);
item.event.isLocal = false;
await this.handleBridgeEvent(item.event, item.eventsHandler);
}
}
} catch (error) {
Expand All @@ -578,7 +586,7 @@ export class BridgeManager {
* Handle individual bridge event (original processing logic)
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private async handleBridgeEvent(event: any): Promise<void> {
private async handleBridgeEvent(event: any, eventsHandler?: TonConnectEventsHandler): Promise<void> {
try {
log.info('Bridge event received', { event });
// Convert bridge event to our internal format
Expand Down Expand Up @@ -655,31 +663,36 @@ export class BridgeManager {
}
}

// Store event durably if enabled
if (!this.eventStore) {
throw new WalletKitError(ERROR_CODES.EVENT_STORE_NOT_INITIALIZED, 'Event store is not initialized');
}
try {
await this.eventStore.storeEvent(rawEvent);

// Notify that bridge storage was updated
if (this.eventEmitter) {
this.eventEmitter.emit('bridge-storage-updated');
if (eventsHandler) {
await this.eventRouter.routeEvent(rawEvent, eventsHandler);
log.info('Event routed directly to custom handler', { eventId: rawEvent.id, method: rawEvent.method });
} else {
// Store event durably if enabled
if (!this.eventStore) {
throw new WalletKitError(ERROR_CODES.EVENT_STORE_NOT_INITIALIZED, 'Event store is not initialized');
}
try {
await this.eventStore.storeEvent(rawEvent);

log.info('Event stored durably', { eventId: rawEvent.id, method: rawEvent.method });
} catch (error) {
log.error('Failed to store event durably', {
eventId: rawEvent.id,
error: (error as Error).message,
});
// Notify that bridge storage was updated
if (this.eventEmitter) {
this.eventEmitter.emit('bridge-storage-updated');
}

throw WalletKitError.fromError(
ERROR_CODES.EVENT_STORE_OPERATION_FAILED,
'Failed to store event durably',
error,
{ eventId: rawEvent.id, method: rawEvent.method },
);
log.info('Event stored durably', { eventId: rawEvent.id, method: rawEvent.method });
} catch (error) {
log.error('Failed to store event durably', {
eventId: rawEvent.id,
error: (error as Error).message,
});

throw WalletKitError.fromError(
ERROR_CODES.EVENT_STORE_OPERATION_FAILED,
'Failed to store event durably',
error,
{ eventId: rawEvent.id, method: rawEvent.method },
);
}
}

log.info('Bridge event processed', { rawEvent });
Expand Down
96 changes: 87 additions & 9 deletions packages/walletkit/src/core/EventRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@ import type {
ConnectionRequestEvent,
} from '../api/models';
import type { TonWalletKitOptions } from '../types/config';
import type { TonConnectEventsHandler } from './TonConnectEventsHandler';
import type { TonConnectEventsRouter } from './TonConnectEventsRouter';

const log = globalLogger.createChild('EventRouter');

export class EventRouter {
export class EventRouter implements TonConnectEventsRouter {
private handlers: EventHandler[] = [];
private bridgeManager!: BridgeManager;
private eventsHandlers: Set<TonConnectEventsHandler> = new Set();

// Event callbacks
private connectRequestCallback: EventCallback<ConnectionRequestEvent> | undefined = undefined;
Expand All @@ -57,10 +60,20 @@ export class EventRouter {
this.bridgeManager = bridgeManager;
}

// TonConnectEventsRouter

add(eventsHandler: TonConnectEventsHandler): void {
this.eventsHandlers.add(eventsHandler);
}

remove(eventsHandler: TonConnectEventsHandler): void {
this.eventsHandlers.delete(eventsHandler);
}

/**
* Route incoming bridge event to appropriate handler
*/
async routeEvent(event: RawBridgeEvent): Promise<void> {
async routeEvent(event: RawBridgeEvent, eventsHandler?: TonConnectEventsHandler): Promise<void> {
// Validate event structure
const validation = validateBridgeEvent(event);
if (!validation.isValid) {
Expand All @@ -74,15 +87,24 @@ export class EventRouter {
if (handler.canHandle(event)) {
const result = await handler.handle(event);
if ('error' in result) {
this.notifyErrorCallback({ id: result.id, data: { ...event }, error: result.error });
const errorEvent = { id: result.id, data: { ...event }, error: result.error };
if (eventsHandler) {
eventsHandler.handleRequestError(errorEvent);
} else {
this.notifyErrorCallback(errorEvent);
}
try {
await this.bridgeManager.sendResponse(event, result);
} catch (error) {
log.error('Error sending response for error event', { error, event, result });
}
return;
}
await handler.notify(result as BridgeEvent);
if (eventsHandler) {
this.dispatchToEventsHandler(eventsHandler, event.method, result as BridgeEvent);
} else {
await handler.notify(result as BridgeEvent);
}
break;
}
}
Expand All @@ -92,6 +114,23 @@ export class EventRouter {
}
}

private dispatchToEventsHandler(eventsHandler: TonConnectEventsHandler, method: string, event: BridgeEvent): void {
switch (method) {
case 'connect':
eventsHandler.handleConnectRequest(event as ConnectionRequestEvent);
break;
case 'sendTransaction':
eventsHandler.handleSendTransactionRequest(event as SendTransactionRequestEvent);
break;
case 'signData':
eventsHandler.handleSignDataRequest(event as SignDataRequestEvent);
break;
case 'disconnect':
eventsHandler.handleDisconnection(event as DisconnectionEvent);
break;
}
}

/**
* Register event callbacks
*/
Expand Down Expand Up @@ -177,41 +216,45 @@ export class EventRouter {
* Notify connect request callbacks
*/
private async notifyConnectRequestCallbacks(event: ConnectionRequestEvent): Promise<void> {
return await this.connectRequestCallback?.(event);
return await this.handleConnectRequest(event);
}

/**
* Notify transaction request callbacks
*/
private async notifyTransactionRequestCallbacks(event: SendTransactionRequestEvent): Promise<void> {
return await this.transactionRequestCallback?.(event);
return await this.handleSendTransactionRequest(event);
}

/**
* Notify sign data request callbacks
*/
private async notifySignDataRequestCallbacks(event: SignDataRequestEvent): Promise<void> {
return await this.signDataRequestCallback?.(event);
return await this.handleSignDataRequest(event);
}

/**
* Notify disconnect callbacks
*/
private async notifyDisconnectCallbacks(event: DisconnectionEvent): Promise<void> {
return await this.disconnectCallback?.(event);
return await this.handleDisconnection(event);
}

/**
* Notify error callbacks
*/
private async notifyErrorCallback(event: RequestErrorEvent): Promise<void> {
return await this.errorCallback?.(event);
return await this.handleRequestError(event);
}

/**
* Get enabled event types based on registered callbacks
*/
getEnabledEventTypes(): EventType[] {
if (this.eventsHandlers.size > 0) {
return ['connect', 'sendTransaction', 'signData', 'disconnect'];
}

const enabledTypes: EventType[] = [];

if (this.connectRequestCallback) {
Expand All @@ -229,4 +272,39 @@ export class EventRouter {

return enabledTypes;
}

handleConnectRequest(event: ConnectionRequestEvent): void | Promise<void> {
this.connectRequestCallback?.(event);
for (const handler of this.eventsHandlers) {
handler.handleConnectRequest(event);
}
}

handleSendTransactionRequest(event: SendTransactionRequestEvent): void | Promise<void> {
this.transactionRequestCallback?.(event);
for (const handler of this.eventsHandlers) {
handler.handleSendTransactionRequest(event);
}
}

handleSignDataRequest(event: SignDataRequestEvent): void | Promise<void> {
this.signDataRequestCallback?.(event);
for (const handler of this.eventsHandlers) {
handler.handleSignDataRequest(event);
}
}

handleDisconnection(event: DisconnectionEvent): void | Promise<void> {
this.disconnectCallback?.(event);
for (const handler of this.eventsHandlers) {
handler.handleDisconnection(event);
}
}

handleRequestError(event: RequestErrorEvent): void | Promise<void> {
this.errorCallback?.(event);
for (const handler of this.eventsHandlers) {
handler.handleRequestError(event);
}
}
}
23 changes: 23 additions & 0 deletions packages/walletkit/src/core/TonConnectEventsHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Copyright (c) TonTech.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import type {
SendTransactionRequestEvent,
RequestErrorEvent,
DisconnectionEvent,
SignDataRequestEvent,
ConnectionRequestEvent,
} from '../api/models';

export interface TonConnectEventsHandler {
handleConnectRequest(event: ConnectionRequestEvent): void | Promise<void>;
handleSendTransactionRequest(event: SendTransactionRequestEvent): void | Promise<void>;
handleSignDataRequest(event: SignDataRequestEvent): void | Promise<void>;
handleDisconnection(event: DisconnectionEvent): void | Promise<void>;
handleRequestError(event: RequestErrorEvent): void | Promise<void>;
}
14 changes: 14 additions & 0 deletions packages/walletkit/src/core/TonConnectEventsRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Copyright (c) TonTech.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import type { TonConnectEventsHandler } from './TonConnectEventsHandler';

export interface TonConnectEventsRouter {
add(eventsHandler: TonConnectEventsHandler): void;
remove(eventsHandler: TonConnectEventsHandler): void;
}
Loading
Loading