| // Copyright 2023 The Flutter Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. | |
| import 'api.dart'; | |
| /// Data model for a devtools extension event that will be sent and received | |
| /// over 'postMessage' between DevTools and an embedded extension iFrame. | |
| /// | |
| /// See [DevToolsExtensionEventType] for different types of events that are | |
| /// supported over this communication channel. | |
| class DevToolsExtensionEvent { | |
| DevToolsExtensionEvent(this.type, {this.data, this.source}); | |
| factory DevToolsExtensionEvent.parse(Map<String, Object?> json) { | |
| final eventType = DevToolsExtensionEventType.from( | |
| json[_typeKey]! as String, | |
| ); | |
| final data = (json[_dataKey] as Map?)?.cast<String, Object?>(); | |
| final source = json[sourceKey] as String?; | |
| return DevToolsExtensionEvent(eventType, data: data, source: source); | |
| } | |
| static DevToolsExtensionEvent? tryParse(Object data) { | |
| try { | |
| final dataAsMap = (data as Map).cast<String, Object?>(); | |
| return DevToolsExtensionEvent.parse(dataAsMap); | |
| } catch (_) { | |
| return null; | |
| } | |
| } | |
| static const _typeKey = 'type'; | |
| static const _dataKey = 'data'; | |
| static const sourceKey = 'source'; | |
| final DevToolsExtensionEventType type; | |
| final Map<String, Object?>? data; | |
| /// Optional field to describe the source that created and sent this event. | |
| final String? source; | |
| Map<String, Object?> toJson() { | |
| return {_typeKey: type.name, _dataKey: ?data}; | |
| } | |
| @override | |
| String toString() { | |
| return '[$type, data: ${data.toString()}' | |
| '${source != null ? ', source: $source' : ''}]'; | |
| } | |
| } | |
| /// A void callback that handles a [DevToolsExtensionEvent]. | |
| typedef ExtensionEventHandler = void Function(DevToolsExtensionEvent event); | |
| /// An extension event of type [DevToolsExtensionEventType.showNotification] | |
| /// that is sent from an extension to DevTools asking DevTools to post a | |
| /// notification the the DevTools notification framework. | |
| class ShowNotificationExtensionEvent extends DevToolsExtensionEvent { | |
| ShowNotificationExtensionEvent({required String message}) | |
| : super( | |
| DevToolsExtensionEventType.showNotification, | |
| data: {_messageKey: message}, | |
| ); | |
| factory ShowNotificationExtensionEvent.from(DevToolsExtensionEvent event) { | |
| assert(event.type == DevToolsExtensionEventType.showNotification); | |
| final message = event.data!.checkValid<String>(_messageKey); | |
| return ShowNotificationExtensionEvent(message: message); | |
| } | |
| static const _messageKey = 'message'; | |
| String get message => data![_messageKey] as String; | |
| } | |
| /// An extension event of type [DevToolsExtensionEventType.showBannerMessage] | |
| /// that is sent from an extension to DevTools asking DevTools to post a | |
| /// banner message to the extension's screen using the DevTools banner message | |
| /// framework. | |
| class ShowBannerMessageExtensionEvent extends DevToolsExtensionEvent { | |
| ShowBannerMessageExtensionEvent({ | |
| required String id, | |
| required String bannerMessageType, | |
| required String message, | |
| required String extensionName, | |
| bool ignoreIfAlreadyDismissed = true, | |
| bool dismissOnConnectionChanges = true, | |
| }) : assert(bannerMessageType == 'warning' || bannerMessageType == 'error'), | |
| super( | |
| DevToolsExtensionEventType.showBannerMessage, | |
| data: { | |
| _idKey: id, | |
| _bannerMessageTypeKey: bannerMessageType, | |
| _messageKey: message, | |
| _extensionNameKey: extensionName, | |
| _ignoreIfAlreadyDismissedKey: ignoreIfAlreadyDismissed, | |
| _dismissOnConnectionChangesKey: dismissOnConnectionChanges, | |
| }, | |
| ); | |
| factory ShowBannerMessageExtensionEvent.from(DevToolsExtensionEvent event) { | |
| assert(event.type == DevToolsExtensionEventType.showBannerMessage); | |
| final eventData = event.data!; | |
| final id = eventData.checkValid<String>(_idKey); | |
| final message = eventData.checkValid<String>(_messageKey); | |
| final type = eventData.checkValid<String>(_bannerMessageTypeKey); | |
| final extensionName = eventData.checkValid<String>(_extensionNameKey); | |
| final ignoreIfAlreadyDismissed = | |
| (eventData[_ignoreIfAlreadyDismissedKey] as bool?) ?? true; | |
| final dismissOnConnectionChanges = | |
| (eventData[_dismissOnConnectionChangesKey] as bool?) ?? true; | |
| return ShowBannerMessageExtensionEvent( | |
| id: id, | |
| bannerMessageType: type, | |
| message: message, | |
| extensionName: extensionName, | |
| ignoreIfAlreadyDismissed: ignoreIfAlreadyDismissed, | |
| dismissOnConnectionChanges: dismissOnConnectionChanges, | |
| ); | |
| } | |
| static const _messageKey = 'message'; | |
| static const _idKey = 'id'; | |
| static const _bannerMessageTypeKey = 'bannerMessageType'; | |
| static const _extensionNameKey = 'extensionName'; | |
| static const _ignoreIfAlreadyDismissedKey = 'ignoreIfAlreadyDismissed'; | |
| static const _dismissOnConnectionChangesKey = 'dismissOnConnectionChanges'; | |
| String get messageId => data![_idKey] as String; | |
| String get bannerMessageType => data![_bannerMessageTypeKey] as String; | |
| String get message => data![_messageKey] as String; | |
| String get extensionName => data![_extensionNameKey] as String; | |
| bool get ignoreIfAlreadyDismissed => | |
| (data![_ignoreIfAlreadyDismissedKey] as bool?) ?? true; | |
| /// Whether this message should be dismissed on app connection changes. | |
| bool get dismissOnConnectionChanges => | |
| (data![_dismissOnConnectionChangesKey] as bool?) ?? true; | |
| } | |
| /// An extension event of type [DevToolsExtensionEventType.copyToClipboard] | |
| /// that is sent from an extension to DevTools asking DevTools copy content to | |
| /// the user's clipboard. | |
| class CopyToClipboardExtensionEvent extends DevToolsExtensionEvent { | |
| CopyToClipboardExtensionEvent({ | |
| required String content, | |
| String successMessage = defaultSuccessMessage, | |
| }) : super( | |
| DevToolsExtensionEventType.copyToClipboard, | |
| data: {_contentKey: content, _successMessageKey: successMessage}, | |
| ); | |
| factory CopyToClipboardExtensionEvent.from(DevToolsExtensionEvent event) { | |
| assert(event.type == DevToolsExtensionEventType.copyToClipboard); | |
| final content = event.data!.checkValid<String>(_contentKey); | |
| final successMessage = event.data!.checkValid<String>(_successMessageKey); | |
| return CopyToClipboardExtensionEvent( | |
| content: content, | |
| successMessage: successMessage, | |
| ); | |
| } | |
| static const _contentKey = 'content'; | |
| static const _successMessageKey = 'successMessage'; | |
| static const defaultSuccessMessage = 'Copied to clipboard'; | |
| String get content => data![_contentKey] as String; | |
| String get successMessage => data![_successMessageKey] as String; | |
| } | |
| extension ParseExtension on Map<String, Object?> { | |
| T checkValid<T>(String key) { | |
| final element = this[key]; | |
| if (element == null) { | |
| throw FormatException("Missing key '$key'"); | |
| } | |
| if (element is! T) { | |
| throw FormatException( | |
| 'Expected element of type $T but got element of type ' | |
| '${element.runtimeType}.', | |
| ); | |
| } | |
| return element as T; | |
| } | |
| } |