@@ -16,7 +16,6 @@ import 'package:dwds/src/events.dart';
1616import 'package:dwds/src/utilities/dart_uri.dart' ;
1717import 'package:dwds/src/utilities/shared.dart' ;
1818import 'package:logging/logging.dart' ;
19- // Ensure RPCError and RPCErrorKind are available for error handling
2019import 'package:pub_semver/pub_semver.dart' as semver;
2120import 'package:vm_service/vm_service.dart' as vm_service;
2221import 'package:vm_service/vm_service.dart' ;
@@ -85,8 +84,6 @@ class _ServiceExtensionTracker {
8584}
8685
8786/// WebSocket-based VM service proxy for web debugging.
88- ///
89- /// Provides hot reload and service extension support via WebSocket communication.
9087class WebSocketProxyService implements VmServiceInterface {
9188 final _logger = Logger ('WebSocketProxyService' );
9289
@@ -102,7 +99,7 @@ class WebSocketProxyService implements VmServiceInterface {
10299 final SendClientRequest sendClientRequest;
103100
104101 /// App connection for this service.
105- final AppConnection appConnection;
102+ AppConnection appConnection;
106103
107104 /// Active hot reload trackers by request ID.
108105 final Map <String , _HotReloadTracker > _pendingHotReloads = {};
@@ -118,6 +115,26 @@ class WebSocketProxyService implements VmServiceInterface {
118115 _pauseIsolatesOnStartFlag: false ,
119116 };
120117
118+ /// Stream controller for resume events after restart.
119+ final _resumeAfterRestartEventsController =
120+ StreamController <String >.broadcast ();
121+
122+ /// Stream of resume events after restart.
123+ Stream <String > get resumeAfterRestartEventsStream =>
124+ _resumeAfterRestartEventsController.stream;
125+
126+ /// Whether there's a pending restart.
127+ bool get hasPendingRestart => _resumeAfterRestartEventsController.hasListener;
128+
129+ /// Whether isolates should pause on start.
130+ bool get pauseIsolatesOnStart =>
131+ _currentVmServiceFlags[_pauseIsolatesOnStartFlag] ?? false ;
132+
133+ /// Counter for generating unique isolate IDs across page refreshes
134+ static int _globalIsolateIdCounter = 0 ;
135+
136+ bool get _isIsolateRunning => _isolateRunning;
137+
121138 /// Root VM instance.
122139 final vm_service.VM _vm;
123140
@@ -131,12 +148,19 @@ class WebSocketProxyService implements VmServiceInterface {
131148 vm_service.Event ? _currentPauseEvent;
132149 bool _hasResumed = false ;
133150
134- bool get _isIsolateRunning => _isolateRunning;
135-
136151 /// Creates a new isolate for WebSocket debugging.
137152 Future <void > createIsolate ([AppConnection ? appConnectionOverride]) async {
138153 final appConn = appConnectionOverride ?? appConnection;
139154
155+ // Update the app connection reference if a new one is provided
156+ if (appConnectionOverride != null ) {
157+ _logger.fine (
158+ 'Updating appConnection reference from '
159+ 'instanceId: ${appConnection .request .instanceId } to instanceId: ${appConnectionOverride .request .instanceId }' ,
160+ );
161+ appConnection = appConnectionOverride;
162+ }
163+
140164 // Clean up existing isolate
141165 if (_isIsolateRunning) {
142166 destroyIsolate ();
@@ -149,11 +173,12 @@ class WebSocketProxyService implements VmServiceInterface {
149173 destroyIsolate ();
150174 });
151175
152- // Create isolate reference
176+ // Create isolate reference with unique ID that changes on each page refresh
177+ final isolateId = '${++_globalIsolateIdCounter }' ;
153178 final isolateRef = vm_service.IsolateRef (
154- id: '1' ,
179+ id: isolateId ,
155180 name: 'main()' ,
156- number: '1' ,
181+ number: isolateId ,
157182 isSystemIsolate: false ,
158183 );
159184
@@ -766,21 +791,6 @@ class WebSocketProxyService implements VmServiceInterface {
766791 return UriList (uris: uris.map (DartUri .toResolvedUri).toList ());
767792 }
768793
769- /// Stream controller for resume events after restart.
770- final _resumeAfterRestartEventsController =
771- StreamController <String >.broadcast ();
772-
773- /// Stream of resume events after restart.
774- Stream <String > get resumeAfterRestartEventsStream =>
775- _resumeAfterRestartEventsController.stream;
776-
777- /// Whether there's a pending restart.
778- bool get hasPendingRestart => _resumeAfterRestartEventsController.hasListener;
779-
780- /// Whether isolates should pause on start.
781- bool get pauseIsolatesOnStart =>
782- _currentVmServiceFlags[_pauseIsolatesOnStartFlag] ?? false ;
783-
784794 /// Resumes execution of the isolate.
785795 @override
786796 Future <Success > resume (String isolateId, {String ? step, int ? frameIndex}) =>
@@ -794,31 +804,87 @@ class WebSocketProxyService implements VmServiceInterface {
794804 String ? step,
795805 int ? frameIndex,
796806 }) async {
797- // Trigger runMain if this is the first resume
798- if (! _hasResumed && _currentPauseEvent != null ) {
807+ if (hasPendingRestart && ! _resumeAfterRestartEventsController.isClosed) {
808+ _resumeAfterRestartEventsController.add (isolateId);
809+ } else {
799810 appConnection.runMain ();
800811 }
812+ return Success ();
813+ }
801814
802- // Prevent multiple resume calls
803- if (_hasResumed && _currentPauseEvent == null ) {
804- return Success ();
805- }
815+ @override
816+ Future <UriList > lookupPackageUris (String isolateId, List <String > uris) =>
817+ wrapInErrorHandlerAsync (
818+ 'lookupPackageUris' ,
819+ () => _lookupPackageUris (isolateId, uris),
820+ );
806821
807- _currentPauseEvent = null ;
808- _hasResumed = true ;
822+ Future <UriList > _lookupPackageUris (
823+ String isolateId,
824+ List <String > uris,
825+ ) async {
826+ await isInitialized;
827+ return UriList (uris: uris.map (DartUri .toPackageUri).toList ());
828+ }
809829
810- // Send resume event
811- if (_isolateRef != null ) {
812- final resumeEvent = vm_service.Event (
813- kind: vm_service.EventKind .kResume,
814- timestamp: DateTime .now ().millisecondsSinceEpoch,
815- isolate: _isolateRef! ,
816- );
830+ @override
831+ Future <Success > registerService (String service, String alias) {
832+ return _rpcNotSupportedFuture ('registerService' );
833+ }
817834
818- _streamNotify (vm_service.EventStreams .kDebug, resumeEvent);
835+ @override
836+ Future <FlagList > getFlagList () =>
837+ wrapInErrorHandlerAsync ('getFlagList' , _getFlagList);
838+
839+ Future <FlagList > _getFlagList () async {
840+ // Return basic flag list for WebSocket mode
841+ return FlagList (
842+ flags: [
843+ Flag (
844+ name: _pauseIsolatesOnStartFlag,
845+ comment: 'If enabled, isolates are paused on start' ,
846+ valueAsString: pauseIsolatesOnStart.toString (),
847+ ),
848+ ],
849+ );
850+ }
851+
852+ @override
853+ Future <vm_service.Stack > getStack (
854+ String isolateId, {
855+ String ? idZoneId,
856+ int ? limit,
857+ }) => wrapInErrorHandlerAsync (
858+ 'getStack' ,
859+ () => _getStack (isolateId, idZoneId: idZoneId, limit: limit),
860+ );
861+
862+ Future <vm_service.Stack > _getStack (
863+ String isolateId, {
864+ String ? idZoneId,
865+ int ? limit,
866+ }) async {
867+ if (! _isIsolateRunning || _isolateRef == null ) {
868+ throw vm_service.RPCError (
869+ 'getStack' ,
870+ vm_service.RPCErrorKind .kInvalidParams.code,
871+ 'No running isolate found for id: $isolateId ' ,
872+ );
873+ }
874+ if (_isolateRef! .id != isolateId) {
875+ throw vm_service.RPCError (
876+ 'getStack' ,
877+ vm_service.RPCErrorKind .kInvalidParams.code,
878+ 'Isolate with id $isolateId not found.' ,
879+ );
819880 }
820881
821- return Success ();
882+ // Return empty stack since we're in WebSocket mode without Chrome debugging
883+ return vm_service.Stack (
884+ frames: [],
885+ asyncCausalFrames: [],
886+ awaiterFrames: [],
887+ );
822888 }
823889
824890 @override
0 commit comments