refactor: null safety for all utils
This commit is contained in:
parent
687a6341f1
commit
a7818bbd0f
|
|
@ -864,7 +864,8 @@ class KeyManager {
|
|||
}
|
||||
} else if (event.type == EventTypes.ForwardedRoomKey) {
|
||||
// we *received* an incoming key request
|
||||
if (event.encryptedContent == null) {
|
||||
final encryptedContent = event.encryptedContent;
|
||||
if (encryptedContent == null) {
|
||||
return; // event wasn't encrypted, this is a security risk
|
||||
}
|
||||
final request = outgoingShareRequests.values.firstWhereOrNull((r) =>
|
||||
|
|
@ -876,7 +877,7 @@ class KeyManager {
|
|||
}
|
||||
final device = request.devices.firstWhereOrNull((d) =>
|
||||
d.userId == event.sender &&
|
||||
d.curve25519Key == event.encryptedContent['sender_key']);
|
||||
d.curve25519Key == encryptedContent['sender_key']);
|
||||
if (device == null) {
|
||||
return; // someone we didn't send our request to replied....better ignore this
|
||||
}
|
||||
|
|
@ -885,7 +886,7 @@ class KeyManager {
|
|||
event.content['forwarding_curve25519_key_chain'] = <String>[];
|
||||
}
|
||||
event.content['forwarding_curve25519_key_chain']
|
||||
.add(event.encryptedContent['sender_key']);
|
||||
.add(encryptedContent['sender_key']);
|
||||
// TODO: verify that the keys work to decrypt a message
|
||||
// alright, all checks out, let's go ahead and store this session
|
||||
setInboundGroupSession(
|
||||
|
|
@ -920,7 +921,8 @@ class KeyManager {
|
|||
} else if (event.type == EventTypes.RoomKey) {
|
||||
Logs().v(
|
||||
'[KeyManager] Received room key with session ${event.content['session_id']}');
|
||||
if (event.encryptedContent == null) {
|
||||
final encryptedContent = event.encryptedContent;
|
||||
if (encryptedContent == null) {
|
||||
Logs().v('[KeyManager] not encrypted, ignoring...');
|
||||
return; // the event wasn't encrypted, this is a security risk;
|
||||
}
|
||||
|
|
@ -932,8 +934,8 @@ class KeyManager {
|
|||
event.content['sender_claimed_ed25519_key'] = sender_ed25519;
|
||||
}
|
||||
Logs().v('[KeyManager] Keeping room key');
|
||||
setInboundGroupSession(roomId, sessionId,
|
||||
event.encryptedContent['sender_key'], event.content,
|
||||
setInboundGroupSession(
|
||||
roomId, sessionId, encryptedContent['sender_key'], event.content,
|
||||
forwarded: false);
|
||||
}
|
||||
}
|
||||
|
|
@ -972,11 +974,11 @@ class RoomKeyRequest extends ToDeviceEvent {
|
|||
KeyManager keyManager;
|
||||
KeyManagerKeyShareRequest request;
|
||||
RoomKeyRequest.fromToDeviceEvent(
|
||||
ToDeviceEvent toDeviceEvent, this.keyManager, this.request) {
|
||||
sender = toDeviceEvent.sender;
|
||||
content = toDeviceEvent.content;
|
||||
type = toDeviceEvent.type;
|
||||
}
|
||||
ToDeviceEvent toDeviceEvent, this.keyManager, this.request)
|
||||
: super(
|
||||
sender: toDeviceEvent.sender,
|
||||
content: toDeviceEvent.content,
|
||||
type: toDeviceEvent.type);
|
||||
|
||||
Room get room => request.room;
|
||||
|
||||
|
|
|
|||
|
|
@ -653,11 +653,12 @@ class OlmManager {
|
|||
if (event.type == EventTypes.Dummy) {
|
||||
// We receive dan encrypted m.dummy. This means that the other end was not able to
|
||||
// decrypt our last message. So, we re-send it.
|
||||
if (event.encryptedContent == null || client.database == null) {
|
||||
final encryptedContent = event.encryptedContent;
|
||||
if (encryptedContent == null || client.database == null) {
|
||||
return;
|
||||
}
|
||||
final device = client.getUserDeviceKeysByCurve25519Key(
|
||||
event.encryptedContent.tryGet<String>('sender_key') ?? '');
|
||||
encryptedContent.tryGet<String>('sender_key') ?? '');
|
||||
if (device == null) {
|
||||
return; // device not found
|
||||
}
|
||||
|
|
|
|||
|
|
@ -509,9 +509,10 @@ class SSSS {
|
|||
} else if (event.type == EventTypes.SecretSend) {
|
||||
// receiving a secret we asked for
|
||||
Logs().i('[SSSS] Received shared secret...');
|
||||
final encryptedContent = event.encryptedContent;
|
||||
if (event.sender != client.userID ||
|
||||
!pendingShareRequests.containsKey(event.content['request_id']) ||
|
||||
event.encryptedContent == null) {
|
||||
encryptedContent == null) {
|
||||
Logs().i('[SSSS] Not by us or unknown request');
|
||||
return; // we have no idea what we just received
|
||||
}
|
||||
|
|
@ -519,7 +520,7 @@ class SSSS {
|
|||
// alright, as we received a known request id, let's check if the sender is valid
|
||||
final device = request.devices.firstWhereOrNull((d) =>
|
||||
d.userId == event.sender &&
|
||||
d.curve25519Key == event.encryptedContent['sender_key']);
|
||||
d.curve25519Key == encryptedContent['sender_key']);
|
||||
if (device == null) {
|
||||
Logs().i('[SSSS] Someone else replied?');
|
||||
return; // someone replied whom we didn't send the share request to
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
// @dart=2.9
|
||||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020, 2021 Famedly GmbH
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
// @dart=2.9
|
||||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020, 2021 Famedly GmbH
|
||||
|
|
@ -17,6 +16,8 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import '../../encryption.dart';
|
||||
import '../../matrix.dart';
|
||||
import '../event.dart';
|
||||
|
|
@ -77,7 +78,7 @@ abstract class EventLocalizations {
|
|||
// This map holds how to localize event types, and thus which event types exist.
|
||||
// If an event exists but it does not have a localized body, set its callback to null
|
||||
static final Map<String,
|
||||
String Function(Event event, MatrixLocalizations i18n, String body)>
|
||||
String Function(Event event, MatrixLocalizations i18n, String body)?>
|
||||
localizationsMap = {
|
||||
EventTypes.Sticker: (event, i18n, body) =>
|
||||
i18n.sentASticker(event.sender.calcDisplayname()),
|
||||
|
|
@ -91,11 +92,9 @@ abstract class EventLocalizations {
|
|||
i18n.createdTheChat(event.sender.calcDisplayname()),
|
||||
EventTypes.RoomTombstone: (event, i18n, body) => i18n.roomHasBeenUpgraded,
|
||||
EventTypes.RoomJoinRules: (event, i18n, body) {
|
||||
final joinRules = JoinRules.values.firstWhere(
|
||||
(r) =>
|
||||
r.toString().replaceAll('JoinRules.', '') ==
|
||||
event.content['join_rule'],
|
||||
orElse: () => null);
|
||||
final joinRules = JoinRules.values.firstWhereOrNull((r) =>
|
||||
r.toString().replaceAll('JoinRules.', '') ==
|
||||
event.content['join_rule']);
|
||||
if (joinRules == null) {
|
||||
return i18n.changedTheJoinRules(event.sender.calcDisplayname());
|
||||
} else {
|
||||
|
|
@ -177,11 +176,9 @@ abstract class EventLocalizations {
|
|||
EventTypes.RoomAvatar: (event, i18n, body) =>
|
||||
i18n.changedTheChatAvatar(event.sender.calcDisplayname()),
|
||||
EventTypes.GuestAccess: (event, i18n, body) {
|
||||
final guestAccess = GuestAccess.values.firstWhere(
|
||||
(r) =>
|
||||
r.toString().replaceAll('GuestAccess.', '') ==
|
||||
event.content['guest_access'],
|
||||
orElse: () => null);
|
||||
final guestAccess = GuestAccess.values.firstWhereOrNull((r) =>
|
||||
r.toString().replaceAll('GuestAccess.', '') ==
|
||||
event.content['guest_access']);
|
||||
if (guestAccess == null) {
|
||||
return i18n.changedTheGuestAccessRules(event.sender.calcDisplayname());
|
||||
} else {
|
||||
|
|
@ -190,11 +187,9 @@ abstract class EventLocalizations {
|
|||
}
|
||||
},
|
||||
EventTypes.HistoryVisibility: (event, i18n, body) {
|
||||
final historyVisibility = HistoryVisibility.values.firstWhere(
|
||||
(r) =>
|
||||
r.toString().replaceAll('HistoryVisibility.', '') ==
|
||||
event.content['history_visibility'],
|
||||
orElse: () => null);
|
||||
final historyVisibility = HistoryVisibility.values.firstWhereOrNull((r) =>
|
||||
r.toString().replaceAll('HistoryVisibility.', '') ==
|
||||
event.content['history_visibility']);
|
||||
if (historyVisibility == null) {
|
||||
return i18n.changedTheHistoryVisibility(event.sender.calcDisplayname());
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
// @dart=2.9
|
||||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2021 Famedly GmbH
|
||||
|
|
@ -17,6 +16,8 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:html/dom.dart';
|
||||
import 'package:html_unescape/html_unescape.dart';
|
||||
|
|
@ -71,9 +72,9 @@ class HtmlToText {
|
|||
}
|
||||
final language =
|
||||
RegExp(r'language-(\w+)', multiLine: false, caseSensitive: false)
|
||||
.firstMatch(match.group(1));
|
||||
.firstMatch(match.group(1)!);
|
||||
if (language != null) {
|
||||
text = language.group(1) + text;
|
||||
text = language.group(1)! + text;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
|
@ -116,17 +117,16 @@ class HtmlToText {
|
|||
opts.listDepth++;
|
||||
final entries = _listChildNodes(opts, node, {'li'});
|
||||
opts.listDepth--;
|
||||
var entry = 1;
|
||||
if (node.attributes['start'] is String &&
|
||||
RegExp(r'^[0-9]+$', multiLine: false)
|
||||
.hasMatch(node.attributes['start'])) {
|
||||
entry = int.parse(node.attributes['start']);
|
||||
}
|
||||
final startStr = node.attributes['start'];
|
||||
final start = (startStr is String &&
|
||||
RegExp(r'^[0-9]+$', multiLine: false).hasMatch(startStr))
|
||||
? int.parse(startStr)
|
||||
: 1;
|
||||
|
||||
return entries
|
||||
.map((s) =>
|
||||
.mapIndexed((index, s) =>
|
||||
(' ' * opts.listDepth) +
|
||||
'${entry++}. ' +
|
||||
'${start + index}. ' +
|
||||
s.replaceAll('\n', '\n' + (' ' * opts.listDepth) + ' '))
|
||||
.join('\n');
|
||||
}
|
||||
|
|
@ -134,14 +134,14 @@ class HtmlToText {
|
|||
static const _listBulletPoints = <String>['●', '○', '■', '‣'];
|
||||
|
||||
static List<String> _listChildNodes(_ConvertOpts opts, Element node,
|
||||
[Iterable<String> types]) {
|
||||
[Iterable<String>? types]) {
|
||||
final replies = <String>[];
|
||||
for (final child in node.nodes) {
|
||||
if (types != null &&
|
||||
types.isNotEmpty &&
|
||||
((child is Text) ||
|
||||
((child is Element) &&
|
||||
!types.contains(child.localName.toLowerCase())))) {
|
||||
!types.contains(child.localName!.toLowerCase())))) {
|
||||
continue;
|
||||
}
|
||||
replies.add(_walkNode(opts, child));
|
||||
|
|
@ -166,7 +166,7 @@ class HtmlToText {
|
|||
var reply = '';
|
||||
var lastTag = '';
|
||||
for (final child in node.nodes) {
|
||||
final thisTag = child is Element ? child.localName.toLowerCase() : '';
|
||||
final thisTag = child is Element ? child.localName!.toLowerCase() : '';
|
||||
if (thisTag == 'p' && lastTag == 'p') {
|
||||
reply += '\n\n';
|
||||
} else if (_blockTags.contains(thisTag) &&
|
||||
|
|
@ -187,7 +187,7 @@ class HtmlToText {
|
|||
// ignore \n between single nodes
|
||||
return node.text == '\n' ? '' : node.text;
|
||||
} else if (node is Element) {
|
||||
final tag = node.localName.toLowerCase();
|
||||
final tag = node.localName!.toLowerCase();
|
||||
switch (tag) {
|
||||
case 'em':
|
||||
case 'i':
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
// @dart=2.9
|
||||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2020, 2021 Famedly GmbH
|
||||
|
|
@ -24,10 +23,10 @@ import '../room.dart';
|
|||
|
||||
extension ImagePackRoomExtension on Room {
|
||||
/// Get all the active image packs for the specified [usage], mapped by their slug
|
||||
Map<String, ImagePackContent> getImagePacks([ImagePackUsage usage]) {
|
||||
Map<String, ImagePackContent> getImagePacks([ImagePackUsage? usage]) {
|
||||
final allMxcs = <Uri>{}; // used for easy deduplication
|
||||
final packs = <String, ImagePackContent>{};
|
||||
final addImagePack = (BasicEvent event, {Room room, String slug}) {
|
||||
final addImagePack = (BasicEvent? event, {Room? room, String? slug}) {
|
||||
if (event == null) return;
|
||||
final imagePack = event.parsedImagePackContent;
|
||||
final finalSlug = slugify(slug ?? 'pack');
|
||||
|
|
@ -42,17 +41,16 @@ extension ImagePackRoomExtension on Room {
|
|||
!imageUsage.contains(usage)) {
|
||||
continue;
|
||||
}
|
||||
if (!packs.containsKey(finalSlug)) {
|
||||
packs[finalSlug] = ImagePackContent.fromJson(<String, dynamic>{});
|
||||
packs[finalSlug].pack.displayName = imagePack.pack.displayName ??
|
||||
room?.displayname ??
|
||||
finalSlug ??
|
||||
'';
|
||||
packs[finalSlug].pack.avatarUrl =
|
||||
imagePack.pack.avatarUrl ?? room?.avatar;
|
||||
packs[finalSlug].pack.attribution = imagePack.pack.attribution;
|
||||
}
|
||||
packs[finalSlug].images[entry.key] = image;
|
||||
packs
|
||||
.putIfAbsent(
|
||||
finalSlug,
|
||||
() => ImagePackContent.fromJson({})
|
||||
..pack.displayName = imagePack.pack.displayName ??
|
||||
room?.displayname ??
|
||||
finalSlug
|
||||
..pack.avatarUrl = imagePack.pack.avatarUrl ?? room?.avatar
|
||||
..pack.attribution = imagePack.pack.attribution)
|
||||
.images[entry.key] = image;
|
||||
allMxcs.add(image.url);
|
||||
}
|
||||
};
|
||||
|
|
@ -89,7 +87,7 @@ extension ImagePackRoomExtension on Room {
|
|||
|
||||
/// Get a flat view of all the image packs of a specified [usage], that is a map of all
|
||||
/// slugs to a map of the image code to their mxc url
|
||||
Map<String, Map<String, String>> getImagePacksFlat([ImagePackUsage usage]) =>
|
||||
Map<String, Map<String, String>> getImagePacksFlat([ImagePackUsage? usage]) =>
|
||||
getImagePacks(usage).map((k, v) =>
|
||||
MapEntry(k, v.images.map((k, v) => MapEntry(k, v.url.toString()))));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
// @dart=2.9
|
||||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2020, 2021 Famedly GmbH
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
// @dart=2.9
|
||||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2020, 2021 Famedly GmbH
|
||||
|
|
@ -37,7 +36,7 @@ class SpoilerSyntax extends TagSyntax {
|
|||
|
||||
@override
|
||||
Node close(InlineParser parser, Delimiter opener, Delimiter closer,
|
||||
{List<Node> Function() getChildren}) {
|
||||
{required List<Node> Function() getChildren}) {
|
||||
final children = getChildren();
|
||||
final newChildren = <Node>[];
|
||||
var searchingForReason = true;
|
||||
|
|
@ -72,16 +71,16 @@ class SpoilerSyntax extends TagSyntax {
|
|||
}
|
||||
|
||||
class EmoteSyntax extends InlineSyntax {
|
||||
final Map<String, Map<String, String>> Function() getEmotePacks;
|
||||
Map<String, Map<String, String>> emotePacks;
|
||||
final Map<String, Map<String, String>> Function()? getEmotePacks;
|
||||
Map<String, Map<String, String>>? emotePacks;
|
||||
EmoteSyntax(this.getEmotePacks) : super(r':(?:([-\w]+)~)?([-\w]+):');
|
||||
|
||||
@override
|
||||
bool onMatch(InlineParser parser, Match match) {
|
||||
emotePacks ??= getEmotePacks?.call() ?? <String, Map<String, String>>{};
|
||||
final emotePacks = this.emotePacks ??= getEmotePacks?.call() ?? {};
|
||||
final pack = match[1] ?? '';
|
||||
final emote = match[2];
|
||||
String mxc;
|
||||
String? mxc;
|
||||
if (pack.isEmpty) {
|
||||
// search all packs
|
||||
for (final emotePack in emotePacks.values) {
|
||||
|
|
@ -91,11 +90,11 @@ class EmoteSyntax extends InlineSyntax {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
mxc = emotePacks[pack] != null ? emotePacks[pack][emote] : null;
|
||||
mxc = emotePacks[pack]?[emote];
|
||||
}
|
||||
if (mxc == null) {
|
||||
// emote not found. Insert the whole thing as plain text
|
||||
parser.addNode(Text(match[0]));
|
||||
parser.addNode(Text(match[0]!));
|
||||
return true;
|
||||
}
|
||||
final element = Element.empty('img');
|
||||
|
|
@ -116,8 +115,8 @@ class InlineLatexSyntax extends TagSyntax {
|
|||
@override
|
||||
bool onMatch(InlineParser parser, Match match) {
|
||||
final element =
|
||||
Element('span', [Element.text('code', htmlEscape.convert(match[1]))]);
|
||||
element.attributes['data-mx-maths'] = htmlAttrEscape.convert(match[1]);
|
||||
Element('span', [Element.text('code', htmlEscape.convert(match[1]!))]);
|
||||
element.attributes['data-mx-maths'] = htmlAttrEscape.convert(match[1]!);
|
||||
parser.addNode(element);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -136,11 +135,11 @@ class BlockLatexSyntax extends BlockSyntax {
|
|||
var first = true;
|
||||
while (!parser.isDone) {
|
||||
final match = endPattern.firstMatch(parser.current);
|
||||
if (match == null || (first && match.group(1).trim().isEmpty)) {
|
||||
if (match == null || (first && match[1]!.trim().isEmpty)) {
|
||||
childLines.add(parser.current);
|
||||
parser.advance();
|
||||
} else {
|
||||
childLines.add(match.group(1));
|
||||
childLines.add(match[1]!);
|
||||
parser.advance();
|
||||
break;
|
||||
}
|
||||
|
|
@ -171,10 +170,10 @@ class PillSyntax extends InlineSyntax {
|
|||
bool onMatch(InlineParser parser, Match match) {
|
||||
if (match.start > 0 &&
|
||||
!RegExp(r'[\s.!?:;\(]').hasMatch(match.input[match.start - 1])) {
|
||||
parser.addNode(Text(match[0]));
|
||||
parser.addNode(Text(match[0]!));
|
||||
return true;
|
||||
}
|
||||
final identifier = match[1];
|
||||
final identifier = match[1]!;
|
||||
final element = Element.text('a', htmlEscape.convert(identifier));
|
||||
element.attributes['href'] =
|
||||
htmlAttrEscape.convert('https://matrix.to/#/$identifier');
|
||||
|
|
@ -184,19 +183,19 @@ class PillSyntax extends InlineSyntax {
|
|||
}
|
||||
|
||||
class MentionSyntax extends InlineSyntax {
|
||||
final String Function(String) getMention;
|
||||
final String Function(String)? getMention;
|
||||
MentionSyntax(this.getMention) : super(r'(@(?:\[[^\]:]+\]|\w+)(?:#\w+)?)');
|
||||
|
||||
@override
|
||||
bool onMatch(InlineParser parser, Match match) {
|
||||
final mention = getMention?.call(match[1]);
|
||||
final mention = getMention?.call(match[1]!);
|
||||
if ((match.start > 0 &&
|
||||
!RegExp(r'[\s.!?:;\(]').hasMatch(match.input[match.start - 1])) ||
|
||||
mention == null) {
|
||||
parser.addNode(Text(match[0]));
|
||||
parser.addNode(Text(match[0]!));
|
||||
return true;
|
||||
}
|
||||
final element = Element.text('a', htmlEscape.convert(match[1]));
|
||||
final element = Element.text('a', htmlEscape.convert(match[1]!));
|
||||
element.attributes['href'] =
|
||||
htmlAttrEscape.convert('https://matrix.to/#/$mention');
|
||||
parser.addNode(element);
|
||||
|
|
@ -206,8 +205,8 @@ class MentionSyntax extends InlineSyntax {
|
|||
|
||||
String markdown(
|
||||
String text, {
|
||||
Map<String, Map<String, String>> Function() getEmotePacks,
|
||||
String Function(String) getMention,
|
||||
Map<String, Map<String, String>> Function()? getEmotePacks,
|
||||
String Function(String)? getMention,
|
||||
}) {
|
||||
var ret = markdownToHtml(
|
||||
text,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
// @dart=2.9
|
||||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2020, 2021 Famedly GmbH
|
||||
|
|
@ -20,36 +19,31 @@
|
|||
import '../../matrix.dart';
|
||||
|
||||
class ToDeviceEvent extends BasicEventWithSender {
|
||||
Map<String, dynamic> encryptedContent;
|
||||
Map<String, dynamic>? encryptedContent;
|
||||
|
||||
String get sender => senderId;
|
||||
set sender(String sender) => senderId = sender;
|
||||
|
||||
ToDeviceEvent({
|
||||
String sender,
|
||||
String type,
|
||||
Map<String, dynamic> content,
|
||||
required String sender,
|
||||
required String type,
|
||||
required Map<String, dynamic> content,
|
||||
this.encryptedContent,
|
||||
}) {
|
||||
senderId = sender;
|
||||
this.type = type;
|
||||
this.content = content;
|
||||
}
|
||||
}) : super(senderId: sender, type: type, content: content);
|
||||
|
||||
ToDeviceEvent.fromJson(Map<String, dynamic> json) {
|
||||
factory ToDeviceEvent.fromJson(Map<String, dynamic> json) {
|
||||
final event = BasicEventWithSender.fromJson(json);
|
||||
senderId = event.senderId;
|
||||
type = event.type;
|
||||
content = event.content;
|
||||
return ToDeviceEvent(
|
||||
sender: event.senderId, type: event.type, content: event.content);
|
||||
}
|
||||
}
|
||||
|
||||
class ToDeviceEventDecryptionError extends ToDeviceEvent {
|
||||
Exception exception;
|
||||
StackTrace stackTrace;
|
||||
StackTrace? stackTrace;
|
||||
ToDeviceEventDecryptionError({
|
||||
ToDeviceEvent toDeviceEvent,
|
||||
this.exception,
|
||||
required ToDeviceEvent toDeviceEvent,
|
||||
required this.exception,
|
||||
this.stackTrace,
|
||||
}) : super(
|
||||
sender: toDeviceEvent.senderId,
|
||||
|
|
|
|||
Loading…
Reference in New Issue