11import 'dart:async' ;
2+ import 'dart:convert' ;
3+ import 'dart:js_interop' ;
24
35import 'package:universal_html/html.dart' as html;
46
7+ extension on JSObject {
8+ external JSAny ? operator [](String key);
9+ }
10+
511class WebAuthPopupSession {
612 WebAuthPopupSession ._(this ._popupWindow) {
713 _messageSubscription = html.window.onMessage.listen ((event) {
8- final data = event.data;
9- if (data is ! Map ) return ;
14+ final payload = _normalizeMessagePayload (event.data);
15+ if (payload == null ) {
16+ return ;
17+ }
18+
19+ final type = payload['type' ];
20+ final href = payload['href' ];
1021
11- final type = data['type' ];
12- final href = data['href' ];
1322 if (type != 'sectl-auth-callback' || href is ! String ) {
1423 return ;
1524 }
1625
17- final expectedOrigin = html.window.location.origin;
18- if (event.origin.isNotEmpty &&
19- expectedOrigin.isNotEmpty &&
20- event.origin != expectedOrigin) {
26+ if (href.isEmpty) {
2127 return ;
2228 }
2329
@@ -27,15 +33,78 @@ class WebAuthPopupSession {
2733 unawaited (close ());
2834 });
2935
36+ Future .delayed (const Duration (milliseconds: 500 ), () {
37+ if (_callbackCompleter.isCompleted) return ;
38+ _startClosedTimer ();
39+ });
40+ }
41+
42+ final html.WindowBase _popupWindow;
43+ final Completer <Uri > _callbackCompleter = Completer <Uri >();
44+ StreamSubscription <html.MessageEvent >? _messageSubscription;
45+ Timer ? _closedTimer;
46+
47+ Future <Uri > waitForCallback () => _callbackCompleter.future;
48+
49+ Map <String , dynamic >? _normalizeMessagePayload (dynamic data) {
50+ if (data is String ) {
51+ try {
52+ final decoded = jsonDecode (data);
53+ if (decoded is Map <String , dynamic >) {
54+ return decoded;
55+ }
56+ if (decoded is Map ) {
57+ return decoded.map (
58+ (key, value) => MapEntry (key.toString (), value),
59+ );
60+ }
61+ } catch (_) {
62+ return null ;
63+ }
64+ }
65+
66+ if (data is Map <String , dynamic >) {
67+ return data;
68+ }
69+
70+ if (data is Map ) {
71+ return data.map (
72+ (key, value) => MapEntry (key.toString (), value),
73+ );
74+ }
75+
76+ try {
77+ final object = data as JSObject ;
78+ final type = object['type' ];
79+ final href = object['href' ];
80+ if (type == null || href == null ) {
81+ return null ;
82+ }
83+ return {
84+ 'type' : (type as JSString ).toDart,
85+ 'href' : (href as JSString ).toDart,
86+ };
87+ } catch (_) {
88+ return null ;
89+ }
90+ }
91+
92+ void _startClosedTimer () {
3093 _closedTimer = Timer .periodic (const Duration (milliseconds: 300 ), (_) {
94+ if (_callbackCompleter.isCompleted) {
95+ _closedTimer? .cancel ();
96+ return ;
97+ }
98+
3199 final callbackUri = _tryReadPopupCallbackUri ();
32100 if (callbackUri != null && ! _callbackCompleter.isCompleted) {
33101 _callbackCompleter.complete (callbackUri);
34102 unawaited (close ());
35103 return ;
36104 }
37105
38- if (_popupWindow.closed == true && ! _callbackCompleter.isCompleted) {
106+ final isClosed = _isPopupClosed ();
107+ if (isClosed && ! _callbackCompleter.isCompleted) {
39108 _callbackCompleter.completeError (
40109 StateError ('The SECTL login popup was closed before finishing.' ),
41110 );
@@ -44,21 +113,30 @@ class WebAuthPopupSession {
44113 });
45114 }
46115
47- final html.WindowBase _popupWindow;
48- final Completer <Uri > _callbackCompleter = Completer <Uri >();
49- StreamSubscription <html.MessageEvent >? _messageSubscription;
50- Timer ? _closedTimer;
51-
52- Future <Uri > waitForCallback () => _callbackCompleter.future;
116+ bool _isPopupClosed () {
117+ try {
118+ final popupJs = _popupWindow as JSObject ;
119+ final closed = popupJs['closed' ];
120+ return (closed as JSBoolean ).toDart;
121+ } catch (_) {
122+ return false ;
123+ }
124+ }
53125
54126 Uri ? _tryReadPopupCallbackUri () {
55127 try {
56- final href = _popupWindow.location.href;
57- if (href == null || href.isEmpty) {
128+ final location = _popupWindow.location as JSObject ;
129+ final href = location['href' ];
130+ if (href == null ) {
131+ return null ;
132+ }
133+
134+ final hrefString = (href as JSString ).toDart;
135+ if (hrefString.isEmpty) {
58136 return null ;
59137 }
60138
61- final uri = Uri .parse (href );
139+ final uri = Uri .parse (hrefString );
62140 if (uri.queryParameters.containsKey ('code' ) ||
63141 uri.queryParameters.containsKey ('error' )) {
64142 return uri;
@@ -107,5 +185,8 @@ Future<WebAuthPopupSession?> openWebAuthPopup(String url) async {
107185 ].join (',' );
108186
109187 final popup = html.window.open (url, 'sectl_auth_popup' , features);
188+ if (popup == null ) {
189+ return null ;
190+ }
110191 return WebAuthPopupSession ._(popup);
111192}
0 commit comments