Skip to content
102 changes: 60 additions & 42 deletions dwds/debug_extension_mv3/web/background.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import 'dart:async';
import 'dart:html';

import 'package:dwds/data/debug_info.dart';
import 'package:dwds/data/extension_request.dart';
import 'package:js/js.dart';

import 'data_types.dart';
Expand All @@ -23,8 +22,6 @@ import 'storage.dart';
import 'utils.dart';
import 'web_api.dart';

const _authSuccessResponse = 'Dart Debug Authentication Success!';

void main() {
_registerListeners();
}
Expand Down Expand Up @@ -63,59 +60,66 @@ void _registerListeners() {
));
}

Future<void> _startDebugSession(int tabId, {required Trigger trigger}) async {
final debugInfo = await _fetchDebugInfo(tabId);
final extensionUrl = debugInfo?.extensionUrl;
if (extensionUrl == null) {
_showWarningNotification('Can\'t debug Dart app. Extension URL not found.');
sendConnectFailureMessage(
ConnectFailureReason.noDartApp,
dartAppTabId: tabId,
);
return;
}
final isAuthenticated = await _authenticateUser(extensionUrl, tabId);
if (!isAuthenticated) {
sendConnectFailureMessage(
ConnectFailureReason.authentication,
dartAppTabId: tabId,
);
return;
}
Future<void> _startDebugSession(
int tabId, {
required Trigger trigger,
}) async {
final isAuthenticated = await _authenticateUser(tabId);
if (!isAuthenticated) return;

maybeCreateLifelinePort(tabId);
attachDebugger(tabId, trigger: trigger);
}

Future<bool> _authenticateUser(String extensionUrl, int tabId) async {
final authUrl = _constructAuthUrl(extensionUrl).toString();
final response = await fetchRequest(authUrl);
final responseBody = response.body ?? '';
if (!responseBody.contains(_authSuccessResponse)) {
debugError('Not authenticated: ${response.status} / $responseBody',
verbose: true);
_showWarningNotification('Please re-authenticate and try again.');
await createTab(authUrl, inNewWindow: false);
Future<bool> _authenticateUser(int tabId) async {
final isAlreadyAuthenticated = await _fetchIsAuthenticated(tabId);
if (isAlreadyAuthenticated) return true;
final debugInfo = await _fetchDebugInfo(tabId);
final authUrl = debugInfo?.authUrl;
if (authUrl == null) {
_showWarningNotification('Cannot authenticate user.');
return false;
}
return true;
}

Uri _constructAuthUrl(String extensionUrl) {
final authUri = Uri.parse(extensionUrl).replace(path: authenticationPath);
if (authUri.scheme == 'ws') {
return authUri.replace(scheme: 'http');
}
if (authUri.scheme == 'wss') {
return authUri.replace(scheme: 'https');
final isAuthenticated = await _sendAuthRequest(authUrl);
if (isAuthenticated) {
await setStorageObject<String>(
type: StorageObject.isAuthenticated,
value: '$isAuthenticated',
tabId: tabId,
);
} else {
sendConnectFailureMessage(
ConnectFailureReason.authentication,
dartAppTabId: tabId,
);
await createTab(authUrl, inNewWindow: false);
}
return authUri;
return isAuthenticated;
}

void _handleRuntimeMessages(
dynamic jsRequest, MessageSender sender, Function sendResponse) async {
if (jsRequest is! String) return;

interceptMessage<String>(
message: jsRequest,
expectedType: MessageType.isAuthenticated,
expectedSender: Script.detector,
expectedRecipient: Script.background,
messageHandler: (String isAuthenticated) async {
final dartTab = sender.tab;
if (dartTab == null) {
debugWarn('Received auth info but tab is missing.');
return;
}
// Save the authentication info in storage:
await setStorageObject<String>(
type: StorageObject.isAuthenticated,
value: isAuthenticated,
tabId: dartTab.id,
);
});

interceptMessage<DebugInfo>(
message: jsRequest,
expectedType: MessageType.debugInfo,
Expand Down Expand Up @@ -194,6 +198,20 @@ Future<DebugInfo?> _fetchDebugInfo(int tabId) {
);
}

Future<bool> _fetchIsAuthenticated(int tabId) async {
final authenticated = await fetchStorageObject<String>(
type: StorageObject.isAuthenticated,
tabId: tabId,
);
return authenticated == 'true';
}

Future<bool> _sendAuthRequest(String authUrl) async {
final response = await fetchRequest(authUrl);
final responseBody = response.body ?? '';
return responseBody.contains('Dart Debug Authentication Success!');
}

void _showWarningNotification(String message) {
chrome.notifications.create(
/*notificationId*/ null,
Expand Down
2 changes: 1 addition & 1 deletion dwds/debug_extension_mv3/web/data_serializers.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 21 additions & 6 deletions dwds/debug_extension_mv3/web/data_types.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions dwds/debug_extension_mv3/web/detector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
@JS()
library detector;

import 'dart:convert';
import 'dart:html';
import 'dart:js_util';

import 'package:dwds/data/debug_info.dart';
import 'package:js/js.dart';

import 'chrome_api.dart';
import 'data_serializers.dart';
import 'logger.dart';
import 'messaging.dart';

Expand All @@ -19,6 +23,7 @@ void main() {

void _registerListeners() {
document.addEventListener('dart-app-ready', _onDartAppReadyEvent);
document.addEventListener('dart-auth-response', _onDartAuthEvent);
}

void _onDartAppReadyEvent(Event event) {
Expand All @@ -32,9 +37,19 @@ void _onDartAppReadyEvent(Event event) {
type: MessageType.debugInfo,
body: debugInfo,
);
_sendAuthRequest(debugInfo);
}
}

void _onDartAuthEvent(Event event) {
final isAuthenticated = getProperty(event, 'detail') as String?;
if (isAuthenticated == null) return;
_sendMessageToBackgroundScript(
type: MessageType.isAuthenticated,
body: isAuthenticated,
);
}

// TODO(elliette): Remove once DWDS 17.0.0 is in Flutter stable. If we are on an
// older version of DWDS, then the debug info is not sent along with the ready
// event. Therefore we must read it from the Window object, which is slower.
Expand All @@ -57,3 +72,12 @@ void _sendMessageToBackgroundScript({
recipient: Script.background,
);
}

void _sendAuthRequest(String debugInfoJson) {
final debugInfo =
serializers.deserialize(jsonDecode(debugInfoJson)) as DebugInfo?;
final appOrigin = debugInfo?.appOrigin;
if (appOrigin != null) {
window.postMessage('dart-auth-request', appOrigin);
}
}
9 changes: 7 additions & 2 deletions dwds/debug_extension_mv3/web/messaging.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enum Script {
}

enum MessageType {
isAuthenticated,
connectFailure,
debugInfo,
debugStateChange,
Expand Down Expand Up @@ -87,8 +88,12 @@ void interceptMessage<T>({
decodedMessage.from != expectedSender) {
return;
}
messageHandler(
serializers.deserialize(jsonDecode(decodedMessage.body)) as T);
if (T == String) {
messageHandler(decodedMessage.body as T);
} else {
messageHandler(
serializers.deserialize(jsonDecode(decodedMessage.body)) as T);
}
} catch (error) {
debugError(
'Error intercepting $expectedType from $expectedSender to $expectedRecipient: $error');
Expand Down
5 changes: 4 additions & 1 deletion dwds/debug_extension_mv3/web/storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ enum StorageObject {
debugInfo,
devToolsOpener,
devToolsUri,
encodedUri;
encodedUri,
isAuthenticated;

Persistance get persistance {
switch (this) {
Expand All @@ -31,6 +32,8 @@ enum StorageObject {
return Persistance.sessionOnly;
case StorageObject.encodedUri:
return Persistance.sessionOnly;
case StorageObject.isAuthenticated:
return Persistance.sessionOnly;
}
}
}
Expand Down
1 change: 1 addition & 0 deletions dwds/lib/data/debug_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ abstract class DebugInfo implements Built<DebugInfo, DebugInfoBuilder> {
String? get appInstanceId;
String? get appOrigin;
String? get appUrl;
String? get authUrl;
String? get dwdsVersion;
String? get extensionUrl;
bool? get isInternalBuild;
Expand Down
Loading