1010#include < flutter/plugin_registrar_windows.h>
1111#include < flutter/standard_method_codec.h>
1212
13+ #include < algorithm>
1314#include < functional>
15+ #include < map>
1416#include < memory>
1517
1618namespace {
@@ -56,10 +58,24 @@ class ConnectivityStreamHandler : public FlStreamHandler {
5658 OnCancelInternal (const flutter::EncodableValue *arguments) override ;
5759
5860private:
61+ bool TryStartListen ();
62+ void ScheduleStartListenRetry ();
63+ void CancelStartListenRetry ();
64+ static void CALLBACK RetryTimerProc (HWND hwnd, UINT message, UINT_PTR id,
65+ DWORD time);
66+
67+ // Thread-affine (platform thread), like the stream handler itself.
68+ static std::map<UINT_PTR , ConnectivityStreamHandler *> retry_handlers_;
69+
5970 std::shared_ptr<NetworkManager> manager;
6071 std::unique_ptr<FlEventSink> sink;
72+ UINT_PTR retry_timer_id_ = 0 ;
73+ int retry_attempts_ = 0 ;
6174};
6275
76+ std::map<UINT_PTR , ConnectivityStreamHandler *>
77+ ConnectivityStreamHandler::retry_handlers_;
78+
6379ConnectivityPlusWindowsPlugin::ConnectivityPlusWindowsPlugin () {
6480 manager = std::make_shared<NetworkManager>();
6581 manager->Init ();
@@ -146,25 +162,92 @@ ConnectivityStreamHandler::ConnectivityStreamHandler(
146162 std::shared_ptr<NetworkManager> manager)
147163 : manager(manager) {}
148164
149- ConnectivityStreamHandler::~ConnectivityStreamHandler () {}
165+ ConnectivityStreamHandler::~ConnectivityStreamHandler () {
166+ CancelStartListenRetry ();
167+ }
150168
151169void ConnectivityStreamHandler::AddConnectivityEvent () {
152170 sink->Success (EncodeConnectivityTypes (manager->GetConnectivityTypes ()));
153171}
154172
173+ bool ConnectivityStreamHandler::TryStartListen () {
174+ auto callback =
175+ std::bind (&ConnectivityStreamHandler::AddConnectivityEvent, this );
176+ return manager->StartListen (callback);
177+ }
178+
179+ void ConnectivityStreamHandler::ScheduleStartListenRetry () {
180+ // 100ms, 200ms, 400ms, ... — enough to escape whatever input-synchronous
181+ // window the platform thread was inside.
182+ UINT delay = 100u << (std::min)(retry_attempts_, 4 );
183+ retry_timer_id_ = SetTimer (nullptr , 0 , delay, RetryTimerProc);
184+ if (retry_timer_id_ != 0 ) {
185+ retry_handlers_[retry_timer_id_] = this ;
186+ }
187+ }
188+
189+ void ConnectivityStreamHandler::CancelStartListenRetry () {
190+ if (retry_timer_id_ != 0 ) {
191+ KillTimer (nullptr , retry_timer_id_);
192+ retry_handlers_.erase (retry_timer_id_);
193+ retry_timer_id_ = 0 ;
194+ }
195+ }
196+
197+ void CALLBACK ConnectivityStreamHandler::RetryTimerProc (HWND hwnd,
198+ UINT message,
199+ UINT_PTR id,
200+ DWORD time) {
201+ KillTimer (hwnd, id);
202+ auto it = retry_handlers_.find (id);
203+ if (it == retry_handlers_.end ()) {
204+ return ;
205+ }
206+ ConnectivityStreamHandler *self = it->second ;
207+ retry_handlers_.erase (it);
208+ self->retry_timer_id_ = 0 ;
209+
210+ if (!self->sink ) {
211+ return ; // Cancelled while the retry was pending.
212+ }
213+
214+ if (self->TryStartListen ()) {
215+ self->AddConnectivityEvent ();
216+ return ;
217+ }
218+
219+ if (self->manager ->GetError () == RPC_E_CANTCALLOUT_ININPUTSYNCCALL &&
220+ ++self->retry_attempts_ < 5 ) {
221+ self->ScheduleStartListenRetry ();
222+ return ;
223+ }
224+
225+ // Persistent failure: report through the sink, where Dart-side stream
226+ // onError handlers can observe it (unlike an OnListen error, which the
227+ // framework can only surface as an uncatchable FlutterError).
228+ self->sink ->Error (std::to_string (self->manager ->GetError ()),
229+ " NetworkManager::StartListen" , nullptr );
230+ }
231+
155232std::unique_ptr<FlStreamHandlerError>
156233ConnectivityStreamHandler::OnListenInternal (
157234 const flutter::EncodableValue *arguments,
158235 std::unique_ptr<FlEventSink> &&events) {
159236 sink = std::move (events);
160237
161- auto callback =
162- std::bind (&ConnectivityStreamHandler::AddConnectivityEvent, this );
163-
164- if (!manager->StartListen (callback)) {
165- return std::make_unique<FlStreamHandlerError>(
166- std::to_string (manager->GetError ()), " NetworkManager::StartListen" ,
167- nullptr );
238+ if (!TryStartListen ()) {
239+ if (manager->GetError () == RPC_E_CANTCALLOUT_ININPUTSYNCCALL ) {
240+ // The platform thread is inside an input-synchronous call (e.g. a
241+ // SendMessage-driven window callback), where COM rejects the outgoing
242+ // Advise. The subscription itself is fine — retry once the message
243+ // loop spins, and still emit the initial snapshot below.
244+ retry_attempts_ = 0 ;
245+ ScheduleStartListenRetry ();
246+ } else {
247+ return std::make_unique<FlStreamHandlerError>(
248+ std::to_string (manager->GetError ()), " NetworkManager::StartListen" ,
249+ nullptr );
250+ }
168251 }
169252
170253 AddConnectivityEvent ();
@@ -174,6 +257,7 @@ ConnectivityStreamHandler::OnListenInternal(
174257std::unique_ptr<FlStreamHandlerError>
175258ConnectivityStreamHandler::OnCancelInternal (
176259 const flutter::EncodableValue *arguments) {
260+ CancelStartListenRetry ();
177261 manager->StopListen ();
178262 sink.reset ();
179263 return nullptr ;
0 commit comments