diff --git a/packages/reown_appkit/lib/modal/appkit_modal_impl.dart b/packages/reown_appkit/lib/modal/appkit_modal_impl.dart index 8be249d7..7b77049b 100644 --- a/packages/reown_appkit/lib/modal/appkit_modal_impl.dart +++ b/packages/reown_appkit/lib/modal/appkit_modal_impl.dart @@ -1607,18 +1607,18 @@ class ReownAppKitModal if (_currentSession == null) { throw ReownAppKitModalException('Session is null'); } - if (!NamespaceUtils.isValidChainId(chainId)) { - throw Errors.getSdkError( - Errors.UNSUPPORTED_CHAINS, - context: 'chainId should conform to "CAIP-2" format', - ).toSignError(); - } // _appKit.core.logger.d( '[$runtimeType] request, chainId: $chainId, ' '${jsonEncode(request.toJson())}', ); try { + if (!NamespaceUtils.isValidChainId(chainId)) { + throw Errors.getSdkError( + Errors.UNSUPPORTED_CHAINS, + context: 'chainId should conform to "CAIP-2" format', + ).toSignError(); + } if (_currentSession!.sessionService.isMagic) { return await _magicService.request(chainId: chainId, request: request); } @@ -1655,12 +1655,13 @@ class ReownAppKitModal } catch (e) { if (_isUserRejectedError(e)) { onModalError.broadcast(UserRejectedRequest()); - } else { - if (e is CoinbaseServiceException) { - // If the error is due to no session on Coinbase Wallet we disconnnect the session on Modal. - // This is the only way to detect a missing session since Coinbase Wallet is not sending any event. - throw ReownAppKitModalException('Coinbase Wallet Error'); - } + } else if (e is CoinbaseServiceException) { + // If the error is due to no session on Coinbase Wallet we disconnect the session on Modal. + // This is the only way to detect a missing session since Coinbase Wallet is not sending any event. + throw ReownAppKitModalException('Coinbase Wallet Error'); + } else if (e is ReownSignError) { + onModalError.broadcast(ModalError(e.message)); + return; } rethrow; } diff --git a/packages/reown_appkit/lib/modal/pages/activity_page.dart b/packages/reown_appkit/lib/modal/pages/activity_page.dart index 24e5601d..d4911270 100644 --- a/packages/reown_appkit/lib/modal/pages/activity_page.dart +++ b/packages/reown_appkit/lib/modal/pages/activity_page.dart @@ -114,16 +114,20 @@ class _ActivityListViewBuilderState extends State { widget.appKitModal.onModalError.broadcast( ModalError('Error fetching activity'), ); - final namespace = NamespaceUtils.getNamespaceFromChain( - widget.appKitModal.selectedChain?.chainId ?? '', - ); - GetIt.I().sendEvent( - ErrorFetchTransactionsEvent( - address: widget.appKitModal.session!.getAddress(namespace), - projectId: widget.appKitModal.appKit!.core.projectId, - cursor: _currentCursor, - ), - ); + final session = widget.appKitModal.session; + final appKit = widget.appKitModal.appKit; + if (session != null && appKit != null) { + final namespace = NamespaceUtils.getNamespaceFromChain( + widget.appKitModal.selectedChain?.chainId ?? '', + ); + GetIt.I().sendEvent( + ErrorFetchTransactionsEvent( + address: session.getAddress(namespace), + projectId: appKit.core.projectId, + cursor: _currentCursor, + ), + ); + } } setState(() {}); } @@ -131,15 +135,19 @@ class _ActivityListViewBuilderState extends State { Future _loadMoreActivities() async { if (_isLoadingActivities || !_hasMoreActivities) return; - final chainId = widget.appKitModal.selectedChain?.chainId ?? ''; - final namespace = NamespaceUtils.getNamespaceFromChain(chainId); - GetIt.I().sendEvent( - LoadMoreTransactionsEvent( - address: widget.appKitModal.session!.getAddress(namespace), - projectId: widget.appKitModal.appKit!.core.projectId, - cursor: _currentCursor, - ), - ); + final session = widget.appKitModal.session; + final appKit = widget.appKitModal.appKit; + if (session != null && appKit != null) { + final chainId = widget.appKitModal.selectedChain?.chainId ?? ''; + final namespace = NamespaceUtils.getNamespaceFromChain(chainId); + GetIt.I().sendEvent( + LoadMoreTransactionsEvent( + address: session.getAddress(namespace), + projectId: appKit.core.projectId, + cursor: _currentCursor, + ), + ); + } await _fetchActivities(); } diff --git a/packages/reown_core/lib/relay_client/websocket/websocket_handler.dart b/packages/reown_core/lib/relay_client/websocket/websocket_handler.dart index d2d0c8b5..6e231eb3 100644 --- a/packages/reown_core/lib/relay_client/websocket/websocket_handler.dart +++ b/packages/reown_core/lib/relay_client/websocket/websocket_handler.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:reown_core/models/basic_models.dart'; import 'package:reown_core/relay_client/websocket/i_websocket_handler.dart'; import 'package:stream_channel/stream_channel.dart'; @@ -23,6 +24,8 @@ class WebSocketHandler implements IWebSocketHandler { StreamController? _inputController; StreamController? _outputController; + StreamSubscription? _inputSubscription; + StreamSubscription? _outputSubscription; @override Future setup({required String url}) async { @@ -51,17 +54,47 @@ class WebSocketHandler implements IWebSocketHandler { _outputController = StreamController.broadcast(sync: true); // Split the incoming stream to support multiple listeners - _socket!.stream.cast().listen( + _inputSubscription = _socket!.stream.cast().listen( (data) => _inputController?.add(data), - onError: (error) => _inputController?.addError(error), - onDone: () => _inputController?.close(), + onError: (error) { + try { + _inputController?.addError(error); + } catch (e) { + debugPrint('[WebSocketHandler] inputController.addError failed: $e'); + } + }, + onDone: () { + try { + _inputController?.close(); + } catch (e) { + debugPrint('[WebSocketHandler] inputController.close failed: $e'); + } + }, ); // Route outgoing messages through the output controller - _outputController!.stream.listen( - (data) => _socket?.sink.add(data), - onError: (error) => _socket?.sink.addError(error), - onDone: () => _socket?.sink.close(), + _outputSubscription = _outputController!.stream.listen( + (data) { + try { + _socket?.sink.add(data); + } catch (e) { + debugPrint('[WebSocketHandler] sink.add failed: $e'); + } + }, + onError: (error) { + try { + _socket?.sink.addError(error); + } catch (e) { + debugPrint('[WebSocketHandler] sink.addError failed: $e'); + } + }, + onDone: () { + try { + _socket?.sink.close(); + } catch (e) { + debugPrint('[WebSocketHandler] sink.close failed: $e'); + } + }, ); _channel = StreamChannel(_inputController!.stream, _outputController!.sink); @@ -75,30 +108,50 @@ class WebSocketHandler implements IWebSocketHandler { } } - await _socket?.ready; - - // Check if the request was successful (status code 200) - // try {} catch (e) { - // throw ReownCoreError( - // code: 400, - // message: 'WebSocket connection failed, missing or invalid project id.', - // ); - // } + try { + await _socket?.ready; + } catch (e) { + await close(); + throw ReownCoreError( + code: -1, + message: 'WebSocket connection failed: ${e.toString()}', + ); + } } @override Future close() async { + // Cancel subscriptions first to prevent writes to closed sinks + try { + await _inputSubscription?.cancel(); + } catch (e) { + debugPrint('[WebSocketHandler] inputSubscription.cancel failed: $e'); + } + try { + await _outputSubscription?.cancel(); + } catch (e) { + debugPrint('[WebSocketHandler] outputSubscription.cancel failed: $e'); + } + _inputSubscription = null; + _outputSubscription = null; + try { await _socket?.sink.close(); - } catch (_) {} + } catch (e) { + debugPrint('[WebSocketHandler] socket.sink.close failed: $e'); + } // Close the controllers to prevent further messages and race conditions try { await _inputController?.close(); - } catch (_) {} + } catch (e) { + debugPrint('[WebSocketHandler] inputController.close failed: $e'); + } try { await _outputController?.close(); - } catch (_) {} + } catch (e) { + debugPrint('[WebSocketHandler] outputController.close failed: $e'); + } _inputController = null; _outputController = null; diff --git a/packages/reown_walletkit/example/lib/main.dart b/packages/reown_walletkit/example/lib/main.dart index 08978e51..c50b0aa5 100644 --- a/packages/reown_walletkit/example/lib/main.dart +++ b/packages/reown_walletkit/example/lib/main.dart @@ -148,11 +148,13 @@ class _MyHomePageState extends State { return keyService; }); GetIt.I.registerSingleton(WalletKitService()); - await GetIt.I.allReady(timeout: Duration(seconds: 1)); + await GetIt.I.allReady(timeout: Duration(seconds: 5)); final walletKitService = GetIt.I(); await walletKitService.create(); + await Future.delayed(Duration.zero); // Yield to prevent UI hang await walletKitService.setUpAccounts(); + await Future.delayed(Duration.zero); // Yield to prevent UI hang await walletKitService.init(); walletKitService.walletKit.core.relayClient.onRelayClientConnect