mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:08:40 +00:00
leave groups works now
This commit is contained in:
parent
0dc8c87732
commit
2a3152e707
18 changed files with 903 additions and 117 deletions
|
|
@ -38,10 +38,21 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
|||
}
|
||||
|
||||
Future<List<GroupMember>> getGroupMembers(String groupId) async {
|
||||
return (select(groupMembers)..where((t) => t.groupId.equals(groupId)))
|
||||
return (select(groupMembers)
|
||||
..where(
|
||||
(t) =>
|
||||
t.groupId.equals(groupId) &
|
||||
t.memberState.equals(MemberState.leftGroup.name).not(),
|
||||
))
|
||||
.get();
|
||||
}
|
||||
|
||||
Future<GroupMember?> getGroupMemberByPublicKey(Uint8List publicKey) async {
|
||||
return (select(groupMembers)
|
||||
..where((t) => t.groupPublicKey.equals(publicKey)))
|
||||
.getSingleOrNull();
|
||||
}
|
||||
|
||||
Future<Group?> createNewGroup(GroupsCompanion group) async {
|
||||
return _insertGroup(group);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -797,5 +797,12 @@
|
|||
"notificationTitleUnknownUser": "Jemand",
|
||||
"notificationCategoryMessageTitle": "Nachrichten",
|
||||
"notificationCategoryMessageDesc": "Nachrichten von anderen Benutzern.",
|
||||
"groupContextMenuDeleteGroup": "Dadurch werden alle Nachrichten in diesem Chat dauerhaft gelöscht."
|
||||
"groupContextMenuDeleteGroup": "Dadurch werden alle Nachrichten in diesem Chat dauerhaft gelöscht.",
|
||||
"groupYouAreNowLongerAMember": "Du bist nicht mehr Mitglied dieser Gruppe.",
|
||||
"groupNetworkIssue": "Netzwerkproblem. Bitte probiere es später noch einmal.",
|
||||
"leaveGroupSelectOtherAdminTitle": "Einen Admin auswählen",
|
||||
"leaveGroupSelectOtherAdminBody": "Um die Gruppe zu verlassen, musst du zuerst einen neuen Administrator auswählen.",
|
||||
"leaveGroupSureTitle": "Gruppe verlassen",
|
||||
"leaveGroupSureBody": "Willst du die Gruppe wirklich verlassen?",
|
||||
"leaveGroupSureOkBtn": "Gruppe verlassen"
|
||||
}
|
||||
|
|
@ -575,5 +575,12 @@
|
|||
"notificationTitleUnknownUser": "Someone",
|
||||
"notificationCategoryMessageTitle": "Messages",
|
||||
"notificationCategoryMessageDesc": "Messages from other users.",
|
||||
"groupContextMenuDeleteGroup": "This will permanently delete all messages in this chat."
|
||||
"groupContextMenuDeleteGroup": "This will permanently delete all messages in this chat.",
|
||||
"groupYouAreNowLongerAMember": "You are no longer part of this group.",
|
||||
"groupNetworkIssue": "Network issue. Try again later.",
|
||||
"leaveGroupSelectOtherAdminTitle": "Select another admin",
|
||||
"leaveGroupSelectOtherAdminBody": "To leave the group, you must first select a new administrator.",
|
||||
"leaveGroupSureTitle": "Leave group",
|
||||
"leaveGroupSureBody": "Do you really want to leave the group?",
|
||||
"leaveGroupSureOkBtn": "Leave group"
|
||||
}
|
||||
|
|
@ -2557,6 +2557,48 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'This will permanently delete all messages in this chat.'**
|
||||
String get groupContextMenuDeleteGroup;
|
||||
|
||||
/// No description provided for @groupYouAreNowLongerAMember.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You are no longer part of this group.'**
|
||||
String get groupYouAreNowLongerAMember;
|
||||
|
||||
/// No description provided for @groupNetworkIssue.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Network issue. Try again later.'**
|
||||
String get groupNetworkIssue;
|
||||
|
||||
/// No description provided for @leaveGroupSelectOtherAdminTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select another admin'**
|
||||
String get leaveGroupSelectOtherAdminTitle;
|
||||
|
||||
/// No description provided for @leaveGroupSelectOtherAdminBody.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'To leave the group, you must first select a new administrator.'**
|
||||
String get leaveGroupSelectOtherAdminBody;
|
||||
|
||||
/// No description provided for @leaveGroupSureTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Leave group'**
|
||||
String get leaveGroupSureTitle;
|
||||
|
||||
/// No description provided for @leaveGroupSureBody.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Do you really want to leave the group?'**
|
||||
String get leaveGroupSureBody;
|
||||
|
||||
/// No description provided for @leaveGroupSureOkBtn.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Leave group'**
|
||||
String get leaveGroupSureOkBtn;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -1397,4 +1397,28 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get groupContextMenuDeleteGroup =>
|
||||
'Dadurch werden alle Nachrichten in diesem Chat dauerhaft gelöscht.';
|
||||
|
||||
@override
|
||||
String get groupYouAreNowLongerAMember =>
|
||||
'Du bist nicht mehr Mitglied dieser Gruppe.';
|
||||
|
||||
@override
|
||||
String get groupNetworkIssue =>
|
||||
'Netzwerkproblem. Bitte probiere es später noch einmal.';
|
||||
|
||||
@override
|
||||
String get leaveGroupSelectOtherAdminTitle => 'Einen Admin auswählen';
|
||||
|
||||
@override
|
||||
String get leaveGroupSelectOtherAdminBody =>
|
||||
'Um die Gruppe zu verlassen, musst du zuerst einen neuen Administrator auswählen.';
|
||||
|
||||
@override
|
||||
String get leaveGroupSureTitle => 'Gruppe verlassen';
|
||||
|
||||
@override
|
||||
String get leaveGroupSureBody => 'Willst du die Gruppe wirklich verlassen?';
|
||||
|
||||
@override
|
||||
String get leaveGroupSureOkBtn => 'Gruppe verlassen';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1389,4 +1389,27 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get groupContextMenuDeleteGroup =>
|
||||
'This will permanently delete all messages in this chat.';
|
||||
|
||||
@override
|
||||
String get groupYouAreNowLongerAMember =>
|
||||
'You are no longer part of this group.';
|
||||
|
||||
@override
|
||||
String get groupNetworkIssue => 'Network issue. Try again later.';
|
||||
|
||||
@override
|
||||
String get leaveGroupSelectOtherAdminTitle => 'Select another admin';
|
||||
|
||||
@override
|
||||
String get leaveGroupSelectOtherAdminBody =>
|
||||
'To leave the group, you must first select a new administrator.';
|
||||
|
||||
@override
|
||||
String get leaveGroupSureTitle => 'Leave group';
|
||||
|
||||
@override
|
||||
String get leaveGroupSureBody => 'Do you really want to leave the group?';
|
||||
|
||||
@override
|
||||
String get leaveGroupSureOkBtn => 'Leave group';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -373,10 +373,10 @@ class NewGroupState extends $pb.GeneratedMessage {
|
|||
factory NewGroupState.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'NewGroupState', package: const $pb.PackageName(_omitMessageNames ? '' : 'http_requests'), createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'groupId', protoName: 'groupId')
|
||||
..a<$fixnum.Int64>(2, _omitFieldNames ? '' : 'versionId', $pb.PbFieldType.OU6, protoName: 'versionId', defaultOrMaker: $fixnum.Int64.ZERO)
|
||||
..a<$core.List<$core.int>>(4, _omitFieldNames ? '' : 'encryptedGroupState', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(5, _omitFieldNames ? '' : 'publicKey', $pb.PbFieldType.OY)
|
||||
..aOS(1, _omitFieldNames ? '' : 'groupId')
|
||||
..a<$fixnum.Int64>(2, _omitFieldNames ? '' : 'versionId', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO)
|
||||
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'encryptedGroupState', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(4, _omitFieldNames ? '' : 'publicKey', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
|
|
@ -419,29 +419,202 @@ class NewGroupState extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(2)
|
||||
void clearVersionId() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<$core.int> get encryptedGroupState => $_getN(2);
|
||||
@$pb.TagNumber(4)
|
||||
@$pb.TagNumber(3)
|
||||
set encryptedGroupState($core.List<$core.int> v) { $_setBytes(2, v); }
|
||||
@$pb.TagNumber(4)
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasEncryptedGroupState() => $_has(2);
|
||||
@$pb.TagNumber(4)
|
||||
void clearEncryptedGroupState() => clearField(4);
|
||||
@$pb.TagNumber(3)
|
||||
void clearEncryptedGroupState() => clearField(3);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
@$pb.TagNumber(4)
|
||||
$core.List<$core.int> get publicKey => $_getN(3);
|
||||
@$pb.TagNumber(5)
|
||||
@$pb.TagNumber(4)
|
||||
set publicKey($core.List<$core.int> v) { $_setBytes(3, v); }
|
||||
@$pb.TagNumber(5)
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasPublicKey() => $_has(3);
|
||||
@$pb.TagNumber(5)
|
||||
void clearPublicKey() => clearField(5);
|
||||
@$pb.TagNumber(4)
|
||||
void clearPublicKey() => clearField(4);
|
||||
}
|
||||
|
||||
class AppendGroupState_AppendTBS extends $pb.GeneratedMessage {
|
||||
factory AppendGroupState_AppendTBS({
|
||||
$core.List<$core.int>? encryptedGroupStateAppend,
|
||||
$core.List<$core.int>? publicKey,
|
||||
$core.String? groupId,
|
||||
$core.List<$core.int>? nonce,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (encryptedGroupStateAppend != null) {
|
||||
$result.encryptedGroupStateAppend = encryptedGroupStateAppend;
|
||||
}
|
||||
if (publicKey != null) {
|
||||
$result.publicKey = publicKey;
|
||||
}
|
||||
if (groupId != null) {
|
||||
$result.groupId = groupId;
|
||||
}
|
||||
if (nonce != null) {
|
||||
$result.nonce = nonce;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
AppendGroupState_AppendTBS._() : super();
|
||||
factory AppendGroupState_AppendTBS.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory AppendGroupState_AppendTBS.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'AppendGroupState.AppendTBS', package: const $pb.PackageName(_omitMessageNames ? '' : 'http_requests'), createEmptyInstance: create)
|
||||
..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'encryptedGroupStateAppend', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'publicKey', $pb.PbFieldType.OY)
|
||||
..aOS(3, _omitFieldNames ? '' : 'groupId')
|
||||
..a<$core.List<$core.int>>(4, _omitFieldNames ? '' : 'nonce', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
AppendGroupState_AppendTBS clone() => AppendGroupState_AppendTBS()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
AppendGroupState_AppendTBS copyWith(void Function(AppendGroupState_AppendTBS) updates) => super.copyWith((message) => updates(message as AppendGroupState_AppendTBS)) as AppendGroupState_AppendTBS;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static AppendGroupState_AppendTBS create() => AppendGroupState_AppendTBS._();
|
||||
AppendGroupState_AppendTBS createEmptyInstance() => create();
|
||||
static $pb.PbList<AppendGroupState_AppendTBS> createRepeated() => $pb.PbList<AppendGroupState_AppendTBS>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static AppendGroupState_AppendTBS getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<AppendGroupState_AppendTBS>(create);
|
||||
static AppendGroupState_AppendTBS? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<$core.int> get encryptedGroupStateAppend => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set encryptedGroupStateAppend($core.List<$core.int> v) { $_setBytes(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasEncryptedGroupStateAppend() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearEncryptedGroupStateAppend() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get publicKey => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set publicKey($core.List<$core.int> v) { $_setBytes(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasPublicKey() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearPublicKey() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.String get groupId => $_getSZ(2);
|
||||
@$pb.TagNumber(3)
|
||||
set groupId($core.String v) { $_setString(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasGroupId() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearGroupId() => clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.List<$core.int> get nonce => $_getN(3);
|
||||
@$pb.TagNumber(4)
|
||||
set nonce($core.List<$core.int> v) { $_setBytes(3, v); }
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasNonce() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearNonce() => clearField(4);
|
||||
}
|
||||
|
||||
class AppendGroupState extends $pb.GeneratedMessage {
|
||||
factory AppendGroupState({
|
||||
$core.List<$core.int>? signature,
|
||||
AppendGroupState_AppendTBS? appendTBS,
|
||||
$fixnum.Int64? versionId,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (signature != null) {
|
||||
$result.signature = signature;
|
||||
}
|
||||
if (appendTBS != null) {
|
||||
$result.appendTBS = appendTBS;
|
||||
}
|
||||
if (versionId != null) {
|
||||
$result.versionId = versionId;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
AppendGroupState._() : super();
|
||||
factory AppendGroupState.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory AppendGroupState.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'AppendGroupState', package: const $pb.PackageName(_omitMessageNames ? '' : 'http_requests'), createEmptyInstance: create)
|
||||
..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'signature', $pb.PbFieldType.OY)
|
||||
..aOM<AppendGroupState_AppendTBS>(2, _omitFieldNames ? '' : 'appendTBS', protoName: 'appendTBS', subBuilder: AppendGroupState_AppendTBS.create)
|
||||
..a<$fixnum.Int64>(3, _omitFieldNames ? '' : 'versionId', $pb.PbFieldType.OU6, protoName: 'versionId', defaultOrMaker: $fixnum.Int64.ZERO)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
AppendGroupState clone() => AppendGroupState()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
AppendGroupState copyWith(void Function(AppendGroupState) updates) => super.copyWith((message) => updates(message as AppendGroupState)) as AppendGroupState;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static AppendGroupState create() => AppendGroupState._();
|
||||
AppendGroupState createEmptyInstance() => create();
|
||||
static $pb.PbList<AppendGroupState> createRepeated() => $pb.PbList<AppendGroupState>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static AppendGroupState getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<AppendGroupState>(create);
|
||||
static AppendGroupState? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<$core.int> get signature => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set signature($core.List<$core.int> v) { $_setBytes(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasSignature() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearSignature() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
AppendGroupState_AppendTBS get appendTBS => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set appendTBS(AppendGroupState_AppendTBS v) { setField(2, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasAppendTBS() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearAppendTBS() => clearField(2);
|
||||
@$pb.TagNumber(2)
|
||||
AppendGroupState_AppendTBS ensureAppendTBS() => $_ensure(1);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$fixnum.Int64 get versionId => $_getI64(2);
|
||||
@$pb.TagNumber(3)
|
||||
set versionId($fixnum.Int64 v) { $_setInt64(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasVersionId() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearVersionId() => clearField(3);
|
||||
}
|
||||
|
||||
class GroupState extends $pb.GeneratedMessage {
|
||||
factory GroupState({
|
||||
$fixnum.Int64? versionId,
|
||||
$core.List<$core.int>? encryptedGroupState,
|
||||
$core.Iterable<AppendGroupState>? appendedGroupStates,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (versionId != null) {
|
||||
|
|
@ -450,6 +623,9 @@ class GroupState extends $pb.GeneratedMessage {
|
|||
if (encryptedGroupState != null) {
|
||||
$result.encryptedGroupState = encryptedGroupState;
|
||||
}
|
||||
if (appendedGroupStates != null) {
|
||||
$result.appendedGroupStates.addAll(appendedGroupStates);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
GroupState._() : super();
|
||||
|
|
@ -457,8 +633,9 @@ class GroupState extends $pb.GeneratedMessage {
|
|||
factory GroupState.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'GroupState', package: const $pb.PackageName(_omitMessageNames ? '' : 'http_requests'), createEmptyInstance: create)
|
||||
..a<$fixnum.Int64>(1, _omitFieldNames ? '' : 'versionId', $pb.PbFieldType.OU6, protoName: 'versionId', defaultOrMaker: $fixnum.Int64.ZERO)
|
||||
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'encryptedGroupState', $pb.PbFieldType.OY)
|
||||
..a<$fixnum.Int64>(1, _omitFieldNames ? '' : 'versionId', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO)
|
||||
..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'encryptedGroupState', $pb.PbFieldType.OY)
|
||||
..pc<AppendGroupState>(3, _omitFieldNames ? '' : 'appendedGroupStates', $pb.PbFieldType.PM, subBuilder: AppendGroupState.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
|
|
@ -492,14 +669,62 @@ class GroupState extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(1)
|
||||
void clearVersionId() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get encryptedGroupState => $_getN(1);
|
||||
@$pb.TagNumber(3)
|
||||
@$pb.TagNumber(2)
|
||||
set encryptedGroupState($core.List<$core.int> v) { $_setBytes(1, v); }
|
||||
@$pb.TagNumber(3)
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasEncryptedGroupState() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearEncryptedGroupState() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
void clearEncryptedGroupState() => clearField(3);
|
||||
$core.List<AppendGroupState> get appendedGroupStates => $_getList(2);
|
||||
}
|
||||
|
||||
/// this is just a database helper to store multiple appends
|
||||
class AppendGroupStateHelper extends $pb.GeneratedMessage {
|
||||
factory AppendGroupStateHelper({
|
||||
$core.Iterable<AppendGroupState>? appendedGroupStates,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (appendedGroupStates != null) {
|
||||
$result.appendedGroupStates.addAll(appendedGroupStates);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
AppendGroupStateHelper._() : super();
|
||||
factory AppendGroupStateHelper.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory AppendGroupStateHelper.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'AppendGroupStateHelper', package: const $pb.PackageName(_omitMessageNames ? '' : 'http_requests'), createEmptyInstance: create)
|
||||
..pc<AppendGroupState>(1, _omitFieldNames ? '' : 'appendedGroupStates', $pb.PbFieldType.PM, subBuilder: AppendGroupState.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
AppendGroupStateHelper clone() => AppendGroupStateHelper()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
AppendGroupStateHelper copyWith(void Function(AppendGroupStateHelper) updates) => super.copyWith((message) => updates(message as AppendGroupStateHelper)) as AppendGroupStateHelper;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static AppendGroupStateHelper create() => AppendGroupStateHelper._();
|
||||
AppendGroupStateHelper createEmptyInstance() => create();
|
||||
static $pb.PbList<AppendGroupStateHelper> createRepeated() => $pb.PbList<AppendGroupStateHelper>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static AppendGroupStateHelper getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<AppendGroupStateHelper>(create);
|
||||
static AppendGroupStateHelper? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<AppendGroupState> get appendedGroupStates => $_getList(0);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -89,30 +89,77 @@ final $typed_data.Uint8List updateGroupStateDescriptor = $convert.base64Decode(
|
|||
const NewGroupState$json = {
|
||||
'1': 'NewGroupState',
|
||||
'2': [
|
||||
{'1': 'groupId', '3': 1, '4': 1, '5': 9, '10': 'groupId'},
|
||||
{'1': 'versionId', '3': 2, '4': 1, '5': 4, '10': 'versionId'},
|
||||
{'1': 'encrypted_group_state', '3': 4, '4': 1, '5': 12, '10': 'encryptedGroupState'},
|
||||
{'1': 'public_key', '3': 5, '4': 1, '5': 12, '10': 'publicKey'},
|
||||
{'1': 'group_id', '3': 1, '4': 1, '5': 9, '10': 'groupId'},
|
||||
{'1': 'version_id', '3': 2, '4': 1, '5': 4, '10': 'versionId'},
|
||||
{'1': 'encrypted_group_state', '3': 3, '4': 1, '5': 12, '10': 'encryptedGroupState'},
|
||||
{'1': 'public_key', '3': 4, '4': 1, '5': 12, '10': 'publicKey'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `NewGroupState`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List newGroupStateDescriptor = $convert.base64Decode(
|
||||
'Cg1OZXdHcm91cFN0YXRlEhgKB2dyb3VwSWQYASABKAlSB2dyb3VwSWQSHAoJdmVyc2lvbklkGA'
|
||||
'IgASgEUgl2ZXJzaW9uSWQSMgoVZW5jcnlwdGVkX2dyb3VwX3N0YXRlGAQgASgMUhNlbmNyeXB0'
|
||||
'ZWRHcm91cFN0YXRlEh0KCnB1YmxpY19rZXkYBSABKAxSCXB1YmxpY0tleQ==');
|
||||
'Cg1OZXdHcm91cFN0YXRlEhkKCGdyb3VwX2lkGAEgASgJUgdncm91cElkEh0KCnZlcnNpb25faW'
|
||||
'QYAiABKARSCXZlcnNpb25JZBIyChVlbmNyeXB0ZWRfZ3JvdXBfc3RhdGUYAyABKAxSE2VuY3J5'
|
||||
'cHRlZEdyb3VwU3RhdGUSHQoKcHVibGljX2tleRgEIAEoDFIJcHVibGljS2V5');
|
||||
|
||||
@$core.Deprecated('Use appendGroupStateDescriptor instead')
|
||||
const AppendGroupState$json = {
|
||||
'1': 'AppendGroupState',
|
||||
'2': [
|
||||
{'1': 'signature', '3': 1, '4': 1, '5': 12, '10': 'signature'},
|
||||
{'1': 'appendTBS', '3': 2, '4': 1, '5': 11, '6': '.http_requests.AppendGroupState.AppendTBS', '10': 'appendTBS'},
|
||||
{'1': 'versionId', '3': 3, '4': 1, '5': 4, '10': 'versionId'},
|
||||
],
|
||||
'3': [AppendGroupState_AppendTBS$json],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use appendGroupStateDescriptor instead')
|
||||
const AppendGroupState_AppendTBS$json = {
|
||||
'1': 'AppendTBS',
|
||||
'2': [
|
||||
{'1': 'encrypted_group_state_append', '3': 1, '4': 1, '5': 12, '10': 'encryptedGroupStateAppend'},
|
||||
{'1': 'public_key', '3': 2, '4': 1, '5': 12, '10': 'publicKey'},
|
||||
{'1': 'group_id', '3': 3, '4': 1, '5': 9, '10': 'groupId'},
|
||||
{'1': 'nonce', '3': 4, '4': 1, '5': 12, '10': 'nonce'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `AppendGroupState`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List appendGroupStateDescriptor = $convert.base64Decode(
|
||||
'ChBBcHBlbmRHcm91cFN0YXRlEhwKCXNpZ25hdHVyZRgBIAEoDFIJc2lnbmF0dXJlEkcKCWFwcG'
|
||||
'VuZFRCUxgCIAEoCzIpLmh0dHBfcmVxdWVzdHMuQXBwZW5kR3JvdXBTdGF0ZS5BcHBlbmRUQlNS'
|
||||
'CWFwcGVuZFRCUxIcCgl2ZXJzaW9uSWQYAyABKARSCXZlcnNpb25JZBqcAQoJQXBwZW5kVEJTEj'
|
||||
'8KHGVuY3J5cHRlZF9ncm91cF9zdGF0ZV9hcHBlbmQYASABKAxSGWVuY3J5cHRlZEdyb3VwU3Rh'
|
||||
'dGVBcHBlbmQSHQoKcHVibGljX2tleRgCIAEoDFIJcHVibGljS2V5EhkKCGdyb3VwX2lkGAMgAS'
|
||||
'gJUgdncm91cElkEhQKBW5vbmNlGAQgASgMUgVub25jZQ==');
|
||||
|
||||
@$core.Deprecated('Use groupStateDescriptor instead')
|
||||
const GroupState$json = {
|
||||
'1': 'GroupState',
|
||||
'2': [
|
||||
{'1': 'versionId', '3': 1, '4': 1, '5': 4, '10': 'versionId'},
|
||||
{'1': 'encrypted_group_state', '3': 3, '4': 1, '5': 12, '10': 'encryptedGroupState'},
|
||||
{'1': 'version_id', '3': 1, '4': 1, '5': 4, '10': 'versionId'},
|
||||
{'1': 'encrypted_group_state', '3': 2, '4': 1, '5': 12, '10': 'encryptedGroupState'},
|
||||
{'1': 'appended_group_states', '3': 3, '4': 3, '5': 11, '6': '.http_requests.AppendGroupState', '10': 'appendedGroupStates'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `GroupState`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List groupStateDescriptor = $convert.base64Decode(
|
||||
'CgpHcm91cFN0YXRlEhwKCXZlcnNpb25JZBgBIAEoBFIJdmVyc2lvbklkEjIKFWVuY3J5cHRlZF'
|
||||
'9ncm91cF9zdGF0ZRgDIAEoDFITZW5jcnlwdGVkR3JvdXBTdGF0ZQ==');
|
||||
'CgpHcm91cFN0YXRlEh0KCnZlcnNpb25faWQYASABKARSCXZlcnNpb25JZBIyChVlbmNyeXB0ZW'
|
||||
'RfZ3JvdXBfc3RhdGUYAiABKAxSE2VuY3J5cHRlZEdyb3VwU3RhdGUSUwoVYXBwZW5kZWRfZ3Jv'
|
||||
'dXBfc3RhdGVzGAMgAygLMh8uaHR0cF9yZXF1ZXN0cy5BcHBlbmRHcm91cFN0YXRlUhNhcHBlbm'
|
||||
'RlZEdyb3VwU3RhdGVz');
|
||||
|
||||
@$core.Deprecated('Use appendGroupStateHelperDescriptor instead')
|
||||
const AppendGroupStateHelper$json = {
|
||||
'1': 'AppendGroupStateHelper',
|
||||
'2': [
|
||||
{'1': 'appended_group_states', '3': 1, '4': 3, '5': 11, '6': '.http_requests.AppendGroupState', '10': 'appendedGroupStates'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `AppendGroupStateHelper`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List appendGroupStateHelperDescriptor = $convert.base64Decode(
|
||||
'ChZBcHBlbmRHcm91cFN0YXRlSGVscGVyElMKFWFwcGVuZGVkX2dyb3VwX3N0YXRlcxgBIAMoCz'
|
||||
'IfLmh0dHBfcmVxdWVzdHMuQXBwZW5kR3JvdXBTdGF0ZVITYXBwZW5kZWRHcm91cFN0YXRlcw==');
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ import 'dart:core' as $core;
|
|||
import 'package:fixnum/fixnum.dart' as $fixnum;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'groups.pbenum.dart';
|
||||
|
||||
export 'groups.pbenum.dart';
|
||||
|
||||
/// Stored encrypted on the server in the members columns.
|
||||
class EncryptedGroupState extends $pb.GeneratedMessage {
|
||||
factory EncryptedGroupState({
|
||||
|
|
@ -109,6 +113,56 @@ class EncryptedGroupState extends $pb.GeneratedMessage {
|
|||
void clearPadding() => clearField(5);
|
||||
}
|
||||
|
||||
class EncryptedAppendedGroupState extends $pb.GeneratedMessage {
|
||||
factory EncryptedAppendedGroupState({
|
||||
EncryptedAppendedGroupState_Type? type,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (type != null) {
|
||||
$result.type = type;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
EncryptedAppendedGroupState._() : super();
|
||||
factory EncryptedAppendedGroupState.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory EncryptedAppendedGroupState.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedAppendedGroupState', createEmptyInstance: create)
|
||||
..e<EncryptedAppendedGroupState_Type>(1, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, defaultOrMaker: EncryptedAppendedGroupState_Type.LEFT_GROUP, valueOf: EncryptedAppendedGroupState_Type.valueOf, enumValues: EncryptedAppendedGroupState_Type.values)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
EncryptedAppendedGroupState clone() => EncryptedAppendedGroupState()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
EncryptedAppendedGroupState copyWith(void Function(EncryptedAppendedGroupState) updates) => super.copyWith((message) => updates(message as EncryptedAppendedGroupState)) as EncryptedAppendedGroupState;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static EncryptedAppendedGroupState create() => EncryptedAppendedGroupState._();
|
||||
EncryptedAppendedGroupState createEmptyInstance() => create();
|
||||
static $pb.PbList<EncryptedAppendedGroupState> createRepeated() => $pb.PbList<EncryptedAppendedGroupState>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static EncryptedAppendedGroupState getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EncryptedAppendedGroupState>(create);
|
||||
static EncryptedAppendedGroupState? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
EncryptedAppendedGroupState_Type get type => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set type(EncryptedAppendedGroupState_Type v) { setField(1, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasType() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearType() => clearField(1);
|
||||
}
|
||||
|
||||
class EncryptedGroupStateEnvelop extends $pb.GeneratedMessage {
|
||||
factory EncryptedGroupStateEnvelop({
|
||||
$core.List<$core.int>? nonce,
|
||||
|
|
|
|||
|
|
@ -9,3 +9,22 @@
|
|||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
class EncryptedAppendedGroupState_Type extends $pb.ProtobufEnum {
|
||||
static const EncryptedAppendedGroupState_Type LEFT_GROUP = EncryptedAppendedGroupState_Type._(0, _omitEnumNames ? '' : 'LEFT_GROUP');
|
||||
|
||||
static const $core.List<EncryptedAppendedGroupState_Type> values = <EncryptedAppendedGroupState_Type> [
|
||||
LEFT_GROUP,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, EncryptedAppendedGroupState_Type> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static EncryptedAppendedGroupState_Type? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const EncryptedAppendedGroupState_Type._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
|
||||
const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names');
|
||||
|
|
|
|||
|
|
@ -36,6 +36,28 @@ final $typed_data.Uint8List encryptedGroupStateDescriptor = $convert.base64Decod
|
|||
'VzQWZ0ZXJNaWxsaXNlY29uZHOIAQESGAoHcGFkZGluZxgFIAEoDFIHcGFkZGluZ0IiCiBfZGVs'
|
||||
'ZXRlTWVzc2FnZXNBZnRlck1pbGxpc2Vjb25kcw==');
|
||||
|
||||
@$core.Deprecated('Use encryptedAppendedGroupStateDescriptor instead')
|
||||
const EncryptedAppendedGroupState$json = {
|
||||
'1': 'EncryptedAppendedGroupState',
|
||||
'2': [
|
||||
{'1': 'type', '3': 1, '4': 1, '5': 14, '6': '.EncryptedAppendedGroupState.Type', '10': 'type'},
|
||||
],
|
||||
'4': [EncryptedAppendedGroupState_Type$json],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use encryptedAppendedGroupStateDescriptor instead')
|
||||
const EncryptedAppendedGroupState_Type$json = {
|
||||
'1': 'Type',
|
||||
'2': [
|
||||
{'1': 'LEFT_GROUP', '2': 0},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `EncryptedAppendedGroupState`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List encryptedAppendedGroupStateDescriptor = $convert.base64Decode(
|
||||
'ChtFbmNyeXB0ZWRBcHBlbmRlZEdyb3VwU3RhdGUSNQoEdHlwZRgBIAEoDjIhLkVuY3J5cHRlZE'
|
||||
'FwcGVuZGVkR3JvdXBTdGF0ZS5UeXBlUgR0eXBlIhYKBFR5cGUSDgoKTEVGVF9HUk9VUBAA');
|
||||
|
||||
@$core.Deprecated('Use encryptedGroupStateEnvelopDescriptor instead')
|
||||
const EncryptedGroupStateEnvelop$json = {
|
||||
'1': 'EncryptedGroupStateEnvelop',
|
||||
|
|
|
|||
|
|
@ -9,6 +9,13 @@ message EncryptedGroupState {
|
|||
bytes padding = 5;
|
||||
}
|
||||
|
||||
message EncryptedAppendedGroupState {
|
||||
enum Type {
|
||||
LEFT_GROUP = 0;
|
||||
}
|
||||
Type type = 1;
|
||||
}
|
||||
|
||||
message EncryptedGroupStateEnvelop {
|
||||
bytes nonce = 1;
|
||||
bytes encryptedGroupState = 2;
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ Future<void> handleGroupCreate(
|
|||
groupName: const Value(''),
|
||||
joinedGroup: const Value(false),
|
||||
leftGroup: const Value(false),
|
||||
deletedContent: const Value(false),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hashlib/random.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
|
|
@ -170,32 +170,13 @@ Future<void> fetchMissingGroupPublicKey() async {
|
|||
}
|
||||
}
|
||||
|
||||
Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
||||
if (group.leftGroup) {
|
||||
Log.error(
|
||||
'Could not refresh group state, as user is no longer part of the group',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
Future<List<int>?> _decryptEnvelop(
|
||||
Group group,
|
||||
List<int> encryptedGroupState,
|
||||
) async {
|
||||
try {
|
||||
var isSuccess = true;
|
||||
|
||||
final response = await http
|
||||
.get(
|
||||
Uri.parse('${getGroupStateUrl()}/${group.groupId}'),
|
||||
)
|
||||
.timeout(const Duration(seconds: 10));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
Log.error(
|
||||
'Could not load group state. Got status code ${response.statusCode} from server.',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
final groupStateServer = GroupState.fromBuffer(response.bodyBytes);
|
||||
final envelope = EncryptedGroupStateEnvelop.fromBuffer(
|
||||
groupStateServer.encryptedGroupState,
|
||||
encryptedGroupState,
|
||||
);
|
||||
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||
|
||||
|
|
@ -210,19 +191,111 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
|||
secretKey: SecretKey(group.stateEncryptionKey!),
|
||||
);
|
||||
|
||||
return encryptedGroupStateRaw;
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
||||
try {
|
||||
var isSuccess = true;
|
||||
|
||||
final response = await http
|
||||
.get(
|
||||
Uri.parse('${getGroupStateUrl()}/${group.groupId}'),
|
||||
)
|
||||
.timeout(const Duration(seconds: 10));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
if (response.statusCode == 404) {
|
||||
// group does not exists any more.
|
||||
await twonlyDB.groupsDao.updateGroup(
|
||||
group.groupId,
|
||||
const GroupsCompanion(
|
||||
leftGroup: Value(true),
|
||||
),
|
||||
);
|
||||
}
|
||||
Log.error(
|
||||
'Could not load group state. Got status code ${response.statusCode} from server.',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
final groupStateServer = GroupState.fromBuffer(response.bodyBytes);
|
||||
|
||||
final encryptedStateRaw =
|
||||
await _decryptEnvelop(group, groupStateServer.encryptedGroupState);
|
||||
if (encryptedStateRaw == null) return null;
|
||||
|
||||
final encryptedGroupState =
|
||||
EncryptedGroupState.fromBuffer(encryptedGroupStateRaw);
|
||||
EncryptedGroupState.fromBuffer(encryptedStateRaw);
|
||||
|
||||
if (group.stateVersionId >= groupStateServer.versionId.toInt()) {
|
||||
Log.info(
|
||||
'Group ${group.groupId} has already newest group state from the server!',
|
||||
);
|
||||
// return (groupStateServer.versionId.toInt(), encryptedGroupState);
|
||||
}
|
||||
|
||||
if (!encryptedGroupState.memberIds.contains(Int64(gUser.userId))) {
|
||||
final memberIds = List<Int64>.from(encryptedGroupState.memberIds);
|
||||
final adminIds = List<Int64>.from(encryptedGroupState.adminIds);
|
||||
|
||||
for (final appendedState in groupStateServer.appendedGroupStates) {
|
||||
final identityKey = IdentityKey.fromBytes(
|
||||
Uint8List.fromList(appendedState.appendTBS.publicKey),
|
||||
0,
|
||||
);
|
||||
|
||||
final valid = Curve.verifySignature(
|
||||
identityKey.publicKey,
|
||||
appendedState.appendTBS.writeToBuffer(),
|
||||
Uint8List.fromList(appendedState.signature),
|
||||
);
|
||||
|
||||
if (!valid) {
|
||||
Log.error('Invalid signature for the appendedState');
|
||||
continue;
|
||||
}
|
||||
|
||||
final encryptedStateRaw = await _decryptEnvelop(
|
||||
group,
|
||||
appendedState.appendTBS.encryptedGroupStateAppend,
|
||||
);
|
||||
if (encryptedStateRaw == null) continue;
|
||||
|
||||
final appended =
|
||||
EncryptedAppendedGroupState.fromBuffer(encryptedStateRaw);
|
||||
if (appended.type == EncryptedAppendedGroupState_Type.LEFT_GROUP) {
|
||||
final keyPair =
|
||||
IdentityKeyPair.fromSerialized(group.myGroupPrivateKey!);
|
||||
|
||||
final appendedPubKey = appendedState.appendTBS.publicKey;
|
||||
final myPubKey = keyPair.getPublicKey().serialize().toList();
|
||||
|
||||
if (listEquals(appendedPubKey, myPubKey)) {
|
||||
adminIds.remove(Int64(gUser.userId));
|
||||
memberIds
|
||||
.remove(Int64(gUser.userId)); // -> Will remove the user later...
|
||||
} else {
|
||||
Log.info('A non admin left the group!!!');
|
||||
|
||||
final member = await twonlyDB.groupsDao
|
||||
.getGroupMemberByPublicKey(Uint8List.fromList(appendedPubKey));
|
||||
if (member == null) {
|
||||
Log.error('Member is already not in this group...');
|
||||
continue;
|
||||
}
|
||||
adminIds.remove(Int64(member.contactId));
|
||||
memberIds.remove(Int64(member.contactId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!memberIds.contains(Int64(gUser.userId))) {
|
||||
// OH no, I am no longer a member of this group...
|
||||
// ->
|
||||
// Return from the group...
|
||||
await twonlyDB.groupsDao.updateGroup(
|
||||
group.groupId,
|
||||
const GroupsCompanion(
|
||||
|
|
@ -232,9 +305,41 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
|||
return (groupStateServer.versionId.toInt(), encryptedGroupState);
|
||||
}
|
||||
|
||||
final isGroupAdmin = encryptedGroupState.adminIds
|
||||
.firstWhereOrNull((t) => t.toInt() == gUser.userId) !=
|
||||
null;
|
||||
final isGroupAdmin =
|
||||
adminIds.firstWhereOrNull((t) => t.toInt() == gUser.userId) != null;
|
||||
|
||||
if (!listEquals(memberIds, encryptedGroupState.memberIds)) {
|
||||
if (isGroupAdmin) {
|
||||
try {
|
||||
// this removes the appended_group_state from the server and merges the changes into the main group state
|
||||
final newState = EncryptedGroupState(
|
||||
groupName: encryptedGroupState.groupName,
|
||||
deleteMessagesAfterMilliseconds:
|
||||
encryptedGroupState.deleteMessagesAfterMilliseconds,
|
||||
memberIds: memberIds,
|
||||
adminIds: adminIds,
|
||||
padding: List<int>.generate(Random().nextInt(80), (_) => 0),
|
||||
);
|
||||
// send new state to the server
|
||||
if (!await _updateGroupState(
|
||||
group,
|
||||
newState,
|
||||
versionId: groupStateServer.versionId.toInt() + 1,
|
||||
)) {
|
||||
// could not update the group state...
|
||||
Log.error('Update the state to remove the appended state...');
|
||||
return null;
|
||||
}
|
||||
// the state is now updated and the appended_group_state should be removed on the server, so just call this
|
||||
// function again, to sync the local database
|
||||
return fetchGroupState(group);
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// in case this is not an admin, just work with the new memberIds and adminIds...
|
||||
}
|
||||
|
||||
await twonlyDB.groupsDao.updateGroup(
|
||||
group.groupId,
|
||||
|
|
@ -251,7 +356,7 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
|||
await twonlyDB.groupsDao.getGroupMembers(group.groupId);
|
||||
|
||||
// First find and insert NEW members
|
||||
for (final memberId in encryptedGroupState.memberIds) {
|
||||
for (final memberId in memberIds) {
|
||||
if (memberId == Int64(gUser.userId)) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -313,7 +418,7 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
|||
|
||||
MemberState? newMemberState;
|
||||
|
||||
if (encryptedGroupState.adminIds.contains(Int64(member.contactId))) {
|
||||
if (adminIds.contains(Int64(member.contactId))) {
|
||||
if (member.memberState == MemberState.normal) {
|
||||
// user was promoted
|
||||
newMemberState = MemberState.admin;
|
||||
|
|
@ -376,6 +481,7 @@ Future<bool> _updateGroupState(
|
|||
EncryptedGroupState state, {
|
||||
Uint8List? addAdmin,
|
||||
Uint8List? removeAdmin,
|
||||
int? versionId,
|
||||
}) async {
|
||||
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||
final encryptionNonce = chacha20.newNonce();
|
||||
|
|
@ -397,26 +503,14 @@ Future<bool> _updateGroupState(
|
|||
|
||||
final keyPair = IdentityKeyPair.fromSerialized(group.myGroupPrivateKey!);
|
||||
|
||||
final publicKey = uint8ListToHex(keyPair.getPublicKey().serialize());
|
||||
|
||||
final responseNonce = await http
|
||||
.get(
|
||||
Uri.parse('${getGroupChallengeUrl()}/$publicKey'),
|
||||
)
|
||||
.timeout(const Duration(seconds: 10));
|
||||
|
||||
if (responseNonce.statusCode != 200) {
|
||||
Log.error(
|
||||
'Could not load nonce. Got status code ${responseNonce.statusCode} from server.',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
final nonce = await getNonce(keyPair.getPublicKey().serialize());
|
||||
if (nonce == null) return false;
|
||||
|
||||
final updateTBS = UpdateGroupState_UpdateTBS(
|
||||
versionId: Int64(group.stateVersionId + 1),
|
||||
versionId: Int64(versionId ?? group.stateVersionId + 1),
|
||||
encryptedGroupState: encryptedGroupState.writeToBuffer(),
|
||||
publicKey: keyPair.getPublicKey().serialize(),
|
||||
nonce: responseNonce.bodyBytes,
|
||||
nonce: nonce,
|
||||
addAdmin: addAdmin,
|
||||
removeAdmin: removeAdmin,
|
||||
);
|
||||
|
|
@ -452,7 +546,7 @@ Future<bool> _updateGroupState(
|
|||
|
||||
Future<bool> manageAdminState(
|
||||
Group group,
|
||||
GroupMember member,
|
||||
Uint8List groupPublicKey,
|
||||
int contactId,
|
||||
bool remove,
|
||||
) async {
|
||||
|
|
@ -469,7 +563,7 @@ Future<bool> manageAdminState(
|
|||
if (remove) {
|
||||
if (state.adminIds.contains(userId)) {
|
||||
state.adminIds.remove(userId);
|
||||
removeAdmin = member.groupPublicKey;
|
||||
removeAdmin = groupPublicKey;
|
||||
} else {
|
||||
Log.info('User was already removed as admin.');
|
||||
return true;
|
||||
|
|
@ -477,7 +571,7 @@ Future<bool> manageAdminState(
|
|||
} else {
|
||||
if (!state.adminIds.contains(userId)) {
|
||||
state.adminIds.add(userId);
|
||||
addAdmin = member.groupPublicKey;
|
||||
addAdmin = groupPublicKey;
|
||||
} else {
|
||||
Log.info('User is already admin.');
|
||||
return true;
|
||||
|
|
@ -524,7 +618,7 @@ Future<bool> manageAdminState(
|
|||
return (await fetchGroupState(group)) != null;
|
||||
}
|
||||
|
||||
Future<bool> updateGroupeName(Group group, String groupName) async {
|
||||
Future<bool> updateGroupName(Group group, String groupName) async {
|
||||
// ensure the latest state is used
|
||||
final currentState = await fetchGroupState(group);
|
||||
if (currentState == null) return false;
|
||||
|
|
@ -624,7 +718,7 @@ Future<bool> addNewGroupMembers(
|
|||
|
||||
Future<bool> removeMemberFromGroup(
|
||||
Group group,
|
||||
GroupMember member,
|
||||
Uint8List groupPublicKey,
|
||||
int removeContactId,
|
||||
) async {
|
||||
// ensure the latest state is used
|
||||
|
|
@ -642,16 +736,16 @@ Future<bool> removeMemberFromGroup(
|
|||
return true;
|
||||
}
|
||||
if (adminIdSet.contains(contactId)) {
|
||||
if (member.groupPublicKey == null) {
|
||||
// If the admin public key is not removed, that the user could potentially still update the group state. So only
|
||||
// allow the user removal, if this key is known. It is better the users can not remove the other user, then
|
||||
// the he can but the other user, could still update the group state.
|
||||
Log.error(
|
||||
'Could not remove user. User is admin, but groupPublicKey is unknown.',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
removeAdmin = member.groupPublicKey;
|
||||
// if (member.groupPublicKey == null) {
|
||||
// // If the admin public key is not removed, that the user could potentially still update the group state. So only
|
||||
// // allow the user removal, if this key is known. It is better the users can not remove the other user, then
|
||||
// // the he can but the other user, could still update the group state.
|
||||
// Log.error(
|
||||
// 'Could not remove user. User is admin, but groupPublicKey is unknown.',
|
||||
// );
|
||||
// return false;
|
||||
// }
|
||||
removeAdmin = groupPublicKey;
|
||||
}
|
||||
|
||||
membersIdSet.remove(contactId);
|
||||
|
|
@ -684,10 +778,126 @@ Future<bool> removeMemberFromGroup(
|
|||
GroupHistoriesCompanion(
|
||||
groupId: Value(group.groupId),
|
||||
type: const Value(GroupActionType.removedMember),
|
||||
affectedContactId: Value(removeContactId),
|
||||
affectedContactId: Value(
|
||||
removeContactId == gUser.userId ? null : removeContactId,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Updates the groupMembers table :)
|
||||
return (await fetchGroupState(group)) != null;
|
||||
}
|
||||
|
||||
Future<Uint8List?> getNonce(Uint8List publicKey) async {
|
||||
final publicKeyHex = uint8ListToHex(publicKey);
|
||||
|
||||
final responseNonce = await http
|
||||
.get(
|
||||
Uri.parse('${getGroupChallengeUrl()}/$publicKeyHex'),
|
||||
)
|
||||
.timeout(const Duration(seconds: 10));
|
||||
|
||||
if (responseNonce.statusCode != 200) {
|
||||
Log.error(
|
||||
'Could not load nonce. Got status code ${responseNonce.statusCode} from server.',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return responseNonce.bodyBytes;
|
||||
}
|
||||
|
||||
Future<bool> leaveAsNonAdminFromGroup(Group group) async {
|
||||
final currentState = await fetchGroupState(group);
|
||||
if (currentState == null) {
|
||||
Log.error('Could not load current state');
|
||||
return false;
|
||||
}
|
||||
|
||||
final (version, _) = currentState;
|
||||
if (group.stateVersionId != version) {
|
||||
Log.error('Version is not valid. Just retry.');
|
||||
return false;
|
||||
}
|
||||
|
||||
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||
final encryptionNonce = chacha20.newNonce();
|
||||
|
||||
final state = EncryptedAppendedGroupState(
|
||||
type: EncryptedAppendedGroupState_Type.LEFT_GROUP,
|
||||
);
|
||||
|
||||
final secretBox = await chacha20.encrypt(
|
||||
state.writeToBuffer(),
|
||||
secretKey: SecretKey(group.stateEncryptionKey!),
|
||||
nonce: encryptionNonce,
|
||||
);
|
||||
|
||||
final encryptedGroupStateAppend = EncryptedGroupStateEnvelop(
|
||||
nonce: encryptionNonce,
|
||||
encryptedGroupState: secretBox.cipherText,
|
||||
mac: secretBox.mac.bytes,
|
||||
);
|
||||
|
||||
{
|
||||
// Upload the group state, if this fails, the group can not be created.
|
||||
|
||||
final keyPair = IdentityKeyPair.fromSerialized(group.myGroupPrivateKey!);
|
||||
|
||||
final nonce = await getNonce(keyPair.getPublicKey().serialize());
|
||||
if (nonce == null) return false;
|
||||
|
||||
final appendTBS = AppendGroupState_AppendTBS(
|
||||
publicKey: keyPair.getPublicKey().serialize(),
|
||||
encryptedGroupStateAppend: encryptedGroupStateAppend.writeToBuffer(),
|
||||
groupId: group.groupId,
|
||||
nonce: nonce,
|
||||
);
|
||||
|
||||
final random = getRandomUint8List(32);
|
||||
final signature = sign(
|
||||
keyPair.getPrivateKey().serialize(),
|
||||
appendTBS.writeToBuffer(),
|
||||
random,
|
||||
);
|
||||
|
||||
final newGroupState = AppendGroupState(
|
||||
versionId: Int64(group.stateVersionId + 1),
|
||||
appendTBS: appendTBS,
|
||||
signature: signature,
|
||||
);
|
||||
|
||||
final response = await http
|
||||
.post(
|
||||
Uri.parse('${getGroupStateUrl()}/append'),
|
||||
body: newGroupState.writeToBuffer(),
|
||||
)
|
||||
.timeout(const Duration(seconds: 10));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
Log.error(
|
||||
'Could not patch group state. Got status code ${response.statusCode} from server.',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const groupActionType = GroupActionType.leftGroup;
|
||||
await sendCipherTextToGroup(
|
||||
group.groupId,
|
||||
EncryptedContent(
|
||||
groupUpdate: EncryptedContent_GroupUpdate(
|
||||
groupActionType: groupActionType.name,
|
||||
affectedContactId: Int64(gUser.userId),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await twonlyDB.groupsDao.insertGroupAction(
|
||||
GroupHistoriesCompanion(
|
||||
groupId: Value(group.groupId),
|
||||
type: const Value(groupActionType),
|
||||
),
|
||||
);
|
||||
|
||||
// Updates the table :)
|
||||
return (await fetchGroupState(group)) != null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class BetterListTile extends StatelessWidget {
|
|||
final String text;
|
||||
final Widget? subtitle;
|
||||
final Color? color;
|
||||
final VoidCallback onTap;
|
||||
final VoidCallback? onTap;
|
||||
final double iconSize;
|
||||
final EdgeInsets? padding;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/group.services.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
||||
import 'package:twonly/src/views/components/better_list_title.dart';
|
||||
import 'package:twonly/src/views/components/verified_shield.dart';
|
||||
|
|
@ -74,7 +76,7 @@ class _GroupViewState extends State<GroupView> {
|
|||
newGroupName != null &&
|
||||
newGroupName != '' &&
|
||||
newGroupName != group.groupName) {
|
||||
if (!await updateGroupeName(group, newGroupName)) {
|
||||
if (!await updateGroupName(group, newGroupName)) {
|
||||
if (mounted) {
|
||||
showNetworkIssue(context);
|
||||
}
|
||||
|
|
@ -97,6 +99,60 @@ class _GroupViewState extends State<GroupView> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _leaveGroup() async {
|
||||
final ok = await showAlertDialog(
|
||||
context,
|
||||
context.lang.leaveGroupSureTitle,
|
||||
context.lang.leaveGroupSureBody,
|
||||
customOk: context.lang.leaveGroupSureOkBtn,
|
||||
);
|
||||
if (!ok) return;
|
||||
|
||||
// 1. Check if I am the only admin, while there are still normal members
|
||||
// -> ERROR first select new admin
|
||||
|
||||
if (members.isNotEmpty) {
|
||||
// In case there are other members, check that there is at least one other admin before I leave the group.
|
||||
|
||||
if (group.isGroupAdmin) {
|
||||
if (!members.any((m) => m.$2.memberState == MemberState.admin)) {
|
||||
if (!mounted) return;
|
||||
await showAlertDialog(
|
||||
context,
|
||||
context.lang.leaveGroupSelectOtherAdminTitle,
|
||||
context.lang.leaveGroupSelectOtherAdminBody,
|
||||
customCancel: '',
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
late bool success;
|
||||
|
||||
if (group.isGroupAdmin) {
|
||||
// Current user is a admin, to the state can be updated by the user him self.
|
||||
final keyPair = IdentityKeyPair.fromSerialized(group.myGroupPrivateKey!);
|
||||
success = await removeMemberFromGroup(
|
||||
group,
|
||||
keyPair.getPublicKey().serialize(),
|
||||
gUser.userId,
|
||||
);
|
||||
} else {
|
||||
success = await leaveAsNonAdminFromGroup(group);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
if (mounted) {
|
||||
showNetworkIssue(context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If not admin -> append to the server state
|
||||
|
||||
// -> Inform the other users
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
|
@ -126,7 +182,7 @@ class _GroupViewState extends State<GroupView> {
|
|||
],
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
if (group.isGroupAdmin)
|
||||
if (group.isGroupAdmin && !group.leftGroup)
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.pencil,
|
||||
text: context.lang.groupNameInput,
|
||||
|
|
@ -145,7 +201,7 @@ class _GroupViewState extends State<GroupView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (group.isGroupAdmin)
|
||||
if (group.isGroupAdmin && !group.leftGroup)
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.plus,
|
||||
text: context.lang.addMember,
|
||||
|
|
@ -197,11 +253,24 @@ class _GroupViewState extends State<GroupView> {
|
|||
const SizedBox(height: 10),
|
||||
const Divider(),
|
||||
const SizedBox(height: 10),
|
||||
if (!group.leftGroup)
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.rightFromBracket,
|
||||
color: Colors.red,
|
||||
text: context.lang.leaveGroup,
|
||||
onTap: () => {},
|
||||
onTap: _leaveGroup,
|
||||
)
|
||||
else
|
||||
ListTile(
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(left: 17),
|
||||
child: Text(
|
||||
context.lang.groupYouAreNowLongerAMember,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -246,9 +315,9 @@ Future<String?> showGroupNameChangeDialog(
|
|||
|
||||
void showNetworkIssue(BuildContext context) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Network issue. Try again later.'),
|
||||
duration: Duration(seconds: 3),
|
||||
SnackBar(
|
||||
content: Text(context.lang.groupNetworkIssue),
|
||||
duration: const Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class GroupMemberContextMenu extends StatelessWidget {
|
|||
if (ok) {
|
||||
if (!await manageAdminState(
|
||||
group,
|
||||
member,
|
||||
member.groupPublicKey!,
|
||||
contact.userId,
|
||||
false,
|
||||
)) {
|
||||
|
|
@ -58,7 +58,7 @@ class GroupMemberContextMenu extends StatelessWidget {
|
|||
if (ok) {
|
||||
if (!await manageAdminState(
|
||||
group,
|
||||
member,
|
||||
member.groupPublicKey!,
|
||||
contact.userId,
|
||||
true,
|
||||
)) {
|
||||
|
|
@ -78,7 +78,7 @@ class GroupMemberContextMenu extends StatelessWidget {
|
|||
if (ok) {
|
||||
if (!await removeMemberFromGroup(
|
||||
group,
|
||||
member,
|
||||
member.groupPublicKey!,
|
||||
contact.userId,
|
||||
)) {
|
||||
if (context.mounted) {
|
||||
|
|
@ -159,7 +159,7 @@ class GroupMemberContextMenu extends StatelessWidget {
|
|||
onTap: () => _removeContactAsAdmin(context),
|
||||
icon: FontAwesomeIcons.key,
|
||||
),
|
||||
if (group.isGroupAdmin)
|
||||
if (group.isGroupAdmin && member.groupPublicKey != null)
|
||||
ContextMenuItem(
|
||||
title: context.lang.removeFromGroup,
|
||||
onTap: () => _removeContactFromGroup(context),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hashlib/random.dart';
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:libsignal_protocol_dart/src/ecc/ed25519.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/pow.dart';
|
||||
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||
|
|
@ -60,5 +63,20 @@ void main() {
|
|||
test('Reject values > 0x7fffffff', () {
|
||||
expect(() => getUUIDforDirectChat(0x80000000, 0), throwsArgumentError);
|
||||
});
|
||||
|
||||
test('sign and verify', () {
|
||||
final keyPair = generateIdentityKeyPair();
|
||||
final message = Uint8List(10);
|
||||
|
||||
final random = getRandomUint8List(32);
|
||||
|
||||
final signature =
|
||||
sign(keyPair.getPrivateKey().serialize(), message, random);
|
||||
|
||||
expect(
|
||||
verifySig(keyPair.getPublicKey().serialize(), message, signature),
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue