diff --git a/webdev/CHANGELOG.md b/webdev/CHANGELOG.md index 448a206af..49ef1f1b9 100644 --- a/webdev/CHANGELOG.md +++ b/webdev/CHANGELOG.md @@ -1,7 +1,9 @@ -## 3.7.2-wip +## 3.7.2 +- Fixed duplicate app logs on page refresh by preventing multiple stdout listeners for the same appId. - Adds `--offline` flag [#2483](https://github.com/dart-lang/webdev/pull/2483). - Support the `--hostname` flag when the `--tls-cert-key` and `--tls-cert-chain` flags are present [#2588](https://github.com/dart-lang/webdev/pull/2588). +- Update `dwds` constraint to `24.3.11`. ## 3.7.1 diff --git a/webdev/lib/src/daemon/app_domain.dart b/webdev/lib/src/daemon/app_domain.dart index 66853c19d..8d750e50e 100644 --- a/webdev/lib/src/daemon/app_domain.dart +++ b/webdev/lib/src/daemon/app_domain.dart @@ -63,46 +63,33 @@ class AppDomain extends Domain { Future _handleAppConnections(WebDevServer server) async { final dwds = server.dwds!; + // The connection is established right before `main()` is called. await for (final appConnection in dwds.connectedApps) { + final appId = appConnection.request.appId; + + // Check if we already have an active app state for this appId + if (_appStates.containsKey(appId)) { + // Reuse existing connection, just run main again + appConnection.runMain(); + continue; + } + final debugConnection = await dwds.debugConnection(appConnection); final debugUri = debugConnection.ddsUri ?? debugConnection.uri; final vmService = await vmServiceConnectUri(debugUri); - final appId = appConnection.request.appId; - unawaited(debugConnection.onDone.then((_) { - sendEvent('app.log', { - 'appId': appId, - 'log': 'Lost connection to device.', - }); - sendEvent('app.stop', { - 'appId': appId, - }); - daemon.shutdown(); - })); + sendEvent('app.start', { 'appId': appId, 'directory': Directory.current.path, 'deviceId': 'chrome', 'launchMode': 'run' }); - // TODO(grouma) - limit the catch to the appropriate error. - try { - await vmService.streamCancel('Stdout'); - } catch (_) {} - try { - await vmService.streamListen('Stdout'); - } catch (_) {} - try { - vmService.onServiceEvent.listen(_onServiceEvent); - await vmService.streamListen('Service'); - } catch (_) {} + + // Set up VM service listeners for this appId // ignore: cancel_subscriptions - final stdOutSub = vmService.onStdoutEvent.listen((log) { - sendEvent('app.log', { - 'appId': appId, - 'log': utf8.decode(base64.decode(log.bytes!)), - }); - }); + final stdOutSub = await _setupVmServiceListeners(appId, vmService); + sendEvent('app.debugPort', { 'appId': appId, 'port': debugConnection.port, @@ -120,9 +107,19 @@ class AppDomain extends Domain { appConnection.runMain(); + // Handle connection termination - send events first, then cleanup unawaited(debugConnection.onDone.whenComplete(() { - appState.dispose(); - _appStates.remove(appId); + sendEvent('app.log', { + 'appId': appId, + 'log': 'Lost connection to device.', + }); + sendEvent('app.stop', { + 'appId': appId, + }); + daemon.shutdown(); + + // Clean up app resources + _cleanupAppConnection(appId, appState); })); } @@ -223,6 +220,36 @@ class AppDomain extends Domain { return true; } + /// Sets up VM service listeners for the given appId. + /// Returns the stdout subscription. + Future> _setupVmServiceListeners( + String appId, VmService vmService) async { + try { + vmService.onServiceEvent.listen(_onServiceEvent); + await vmService.streamListen(EventStreams.kService); + } catch (_) {} + + // ignore: cancel_subscriptions + final stdoutSubscription = vmService.onStdoutEvent.listen((log) { + sendEvent('app.log', { + 'appId': appId, + 'log': utf8.decode(base64.decode(log.bytes!)), + }); + }); + + try { + await vmService.streamListen(EventStreams.kStdout); + } catch (_) {} + + return stdoutSubscription; + } + + /// Cleans up an app connection and its associated listeners. + void _cleanupAppConnection(String appId, _AppState appState) { + appState.dispose(); + _appStates.remove(appId); + } + @override void dispose() { _isShutdown = true; diff --git a/webdev/lib/src/version.dart b/webdev/lib/src/version.dart index 7d40e1d7b..2cda305ca 100644 --- a/webdev/lib/src/version.dart +++ b/webdev/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '3.7.2-wip'; +const packageVersion = '3.7.2'; diff --git a/webdev/pubspec.yaml b/webdev/pubspec.yaml index 5e7ea31f0..d1706c7b4 100644 --- a/webdev/pubspec.yaml +++ b/webdev/pubspec.yaml @@ -1,6 +1,6 @@ name: webdev # Every time this changes you need to run `dart run build_runner build`. -version: 3.7.2-wip +version: 3.7.2 # We should not depend on a dev SDK before publishing. # publish_to: none description: >- @@ -19,7 +19,7 @@ dependencies: crypto: ^3.0.2 dds: ^4.1.0 # Pin DWDS to avoid dependency conflicts with vm_service: - dwds: 24.3.5 + dwds: 24.3.11 http: ^1.0.0 http_multi_server: ^3.2.0 io: ^1.0.3