mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 12:48:41 +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 {
|
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();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<GroupMember?> getGroupMemberByPublicKey(Uint8List publicKey) async {
|
||||||
|
return (select(groupMembers)
|
||||||
|
..where((t) => t.groupPublicKey.equals(publicKey)))
|
||||||
|
.getSingleOrNull();
|
||||||
|
}
|
||||||
|
|
||||||
Future<Group?> createNewGroup(GroupsCompanion group) async {
|
Future<Group?> createNewGroup(GroupsCompanion group) async {
|
||||||
return _insertGroup(group);
|
return _insertGroup(group);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -797,5 +797,12 @@
|
||||||
"notificationTitleUnknownUser": "Jemand",
|
"notificationTitleUnknownUser": "Jemand",
|
||||||
"notificationCategoryMessageTitle": "Nachrichten",
|
"notificationCategoryMessageTitle": "Nachrichten",
|
||||||
"notificationCategoryMessageDesc": "Nachrichten von anderen Benutzern.",
|
"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",
|
"notificationTitleUnknownUser": "Someone",
|
||||||
"notificationCategoryMessageTitle": "Messages",
|
"notificationCategoryMessageTitle": "Messages",
|
||||||
"notificationCategoryMessageDesc": "Messages from other users.",
|
"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:
|
/// In en, this message translates to:
|
||||||
/// **'This will permanently delete all messages in this chat.'**
|
/// **'This will permanently delete all messages in this chat.'**
|
||||||
String get groupContextMenuDeleteGroup;
|
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
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -1397,4 +1397,28 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get groupContextMenuDeleteGroup =>
|
String get groupContextMenuDeleteGroup =>
|
||||||
'Dadurch werden alle Nachrichten in diesem Chat dauerhaft gelöscht.';
|
'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
|
@override
|
||||||
String get groupContextMenuDeleteGroup =>
|
String get groupContextMenuDeleteGroup =>
|
||||||
'This will permanently delete all messages in this chat.';
|
'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);
|
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)
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'NewGroupState', package: const $pb.PackageName(_omitMessageNames ? '' : 'http_requests'), createEmptyInstance: create)
|
||||||
..aOS(1, _omitFieldNames ? '' : 'groupId', protoName: 'groupId')
|
..aOS(1, _omitFieldNames ? '' : 'groupId')
|
||||||
..a<$fixnum.Int64>(2, _omitFieldNames ? '' : 'versionId', $pb.PbFieldType.OU6, protoName: 'versionId', defaultOrMaker: $fixnum.Int64.ZERO)
|
..a<$fixnum.Int64>(2, _omitFieldNames ? '' : 'versionId', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO)
|
||||||
..a<$core.List<$core.int>>(4, _omitFieldNames ? '' : 'encryptedGroupState', $pb.PbFieldType.OY)
|
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'encryptedGroupState', $pb.PbFieldType.OY)
|
||||||
..a<$core.List<$core.int>>(5, _omitFieldNames ? '' : 'publicKey', $pb.PbFieldType.OY)
|
..a<$core.List<$core.int>>(4, _omitFieldNames ? '' : 'publicKey', $pb.PbFieldType.OY)
|
||||||
..hasRequiredFields = false
|
..hasRequiredFields = false
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
@ -419,29 +419,202 @@ class NewGroupState extends $pb.GeneratedMessage {
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
void clearVersionId() => clearField(2);
|
void clearVersionId() => clearField(2);
|
||||||
|
|
||||||
@$pb.TagNumber(4)
|
@$pb.TagNumber(3)
|
||||||
$core.List<$core.int> get encryptedGroupState => $_getN(2);
|
$core.List<$core.int> get encryptedGroupState => $_getN(2);
|
||||||
@$pb.TagNumber(4)
|
@$pb.TagNumber(3)
|
||||||
set encryptedGroupState($core.List<$core.int> v) { $_setBytes(2, v); }
|
set encryptedGroupState($core.List<$core.int> v) { $_setBytes(2, v); }
|
||||||
@$pb.TagNumber(4)
|
@$pb.TagNumber(3)
|
||||||
$core.bool hasEncryptedGroupState() => $_has(2);
|
$core.bool hasEncryptedGroupState() => $_has(2);
|
||||||
@$pb.TagNumber(4)
|
@$pb.TagNumber(3)
|
||||||
void clearEncryptedGroupState() => clearField(4);
|
void clearEncryptedGroupState() => clearField(3);
|
||||||
|
|
||||||
@$pb.TagNumber(5)
|
@$pb.TagNumber(4)
|
||||||
$core.List<$core.int> get publicKey => $_getN(3);
|
$core.List<$core.int> get publicKey => $_getN(3);
|
||||||
@$pb.TagNumber(5)
|
@$pb.TagNumber(4)
|
||||||
set publicKey($core.List<$core.int> v) { $_setBytes(3, v); }
|
set publicKey($core.List<$core.int> v) { $_setBytes(3, v); }
|
||||||
@$pb.TagNumber(5)
|
@$pb.TagNumber(4)
|
||||||
$core.bool hasPublicKey() => $_has(3);
|
$core.bool hasPublicKey() => $_has(3);
|
||||||
@$pb.TagNumber(5)
|
@$pb.TagNumber(4)
|
||||||
void clearPublicKey() => clearField(5);
|
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 {
|
class GroupState extends $pb.GeneratedMessage {
|
||||||
factory GroupState({
|
factory GroupState({
|
||||||
$fixnum.Int64? versionId,
|
$fixnum.Int64? versionId,
|
||||||
$core.List<$core.int>? encryptedGroupState,
|
$core.List<$core.int>? encryptedGroupState,
|
||||||
|
$core.Iterable<AppendGroupState>? appendedGroupStates,
|
||||||
}) {
|
}) {
|
||||||
final $result = create();
|
final $result = create();
|
||||||
if (versionId != null) {
|
if (versionId != null) {
|
||||||
|
|
@ -450,6 +623,9 @@ class GroupState extends $pb.GeneratedMessage {
|
||||||
if (encryptedGroupState != null) {
|
if (encryptedGroupState != null) {
|
||||||
$result.encryptedGroupState = encryptedGroupState;
|
$result.encryptedGroupState = encryptedGroupState;
|
||||||
}
|
}
|
||||||
|
if (appendedGroupStates != null) {
|
||||||
|
$result.appendedGroupStates.addAll(appendedGroupStates);
|
||||||
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
GroupState._() : super();
|
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);
|
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)
|
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<$fixnum.Int64>(1, _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>>(2, _omitFieldNames ? '' : 'encryptedGroupState', $pb.PbFieldType.OY)
|
||||||
|
..pc<AppendGroupState>(3, _omitFieldNames ? '' : 'appendedGroupStates', $pb.PbFieldType.PM, subBuilder: AppendGroupState.create)
|
||||||
..hasRequiredFields = false
|
..hasRequiredFields = false
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
@ -492,14 +669,62 @@ class GroupState extends $pb.GeneratedMessage {
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
void clearVersionId() => clearField(1);
|
void clearVersionId() => clearField(1);
|
||||||
|
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(2)
|
||||||
$core.List<$core.int> get encryptedGroupState => $_getN(1);
|
$core.List<$core.int> get encryptedGroupState => $_getN(1);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(2)
|
||||||
set encryptedGroupState($core.List<$core.int> v) { $_setBytes(1, v); }
|
set encryptedGroupState($core.List<$core.int> v) { $_setBytes(1, v); }
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(2)
|
||||||
$core.bool hasEncryptedGroupState() => $_has(1);
|
$core.bool hasEncryptedGroupState() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearEncryptedGroupState() => clearField(2);
|
||||||
|
|
||||||
@$pb.TagNumber(3)
|
@$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 = {
|
const NewGroupState$json = {
|
||||||
'1': 'NewGroupState',
|
'1': 'NewGroupState',
|
||||||
'2': [
|
'2': [
|
||||||
{'1': 'groupId', '3': 1, '4': 1, '5': 9, '10': 'groupId'},
|
{'1': 'group_id', '3': 1, '4': 1, '5': 9, '10': 'groupId'},
|
||||||
{'1': 'versionId', '3': 2, '4': 1, '5': 4, '10': 'versionId'},
|
{'1': 'version_id', '3': 2, '4': 1, '5': 4, '10': 'versionId'},
|
||||||
{'1': 'encrypted_group_state', '3': 4, '4': 1, '5': 12, '10': 'encryptedGroupState'},
|
{'1': 'encrypted_group_state', '3': 3, '4': 1, '5': 12, '10': 'encryptedGroupState'},
|
||||||
{'1': 'public_key', '3': 5, '4': 1, '5': 12, '10': 'publicKey'},
|
{'1': 'public_key', '3': 4, '4': 1, '5': 12, '10': 'publicKey'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Descriptor for `NewGroupState`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `NewGroupState`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List newGroupStateDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List newGroupStateDescriptor = $convert.base64Decode(
|
||||||
'Cg1OZXdHcm91cFN0YXRlEhgKB2dyb3VwSWQYASABKAlSB2dyb3VwSWQSHAoJdmVyc2lvbklkGA'
|
'Cg1OZXdHcm91cFN0YXRlEhkKCGdyb3VwX2lkGAEgASgJUgdncm91cElkEh0KCnZlcnNpb25faW'
|
||||||
'IgASgEUgl2ZXJzaW9uSWQSMgoVZW5jcnlwdGVkX2dyb3VwX3N0YXRlGAQgASgMUhNlbmNyeXB0'
|
'QYAiABKARSCXZlcnNpb25JZBIyChVlbmNyeXB0ZWRfZ3JvdXBfc3RhdGUYAyABKAxSE2VuY3J5'
|
||||||
'ZWRHcm91cFN0YXRlEh0KCnB1YmxpY19rZXkYBSABKAxSCXB1YmxpY0tleQ==');
|
'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')
|
@$core.Deprecated('Use groupStateDescriptor instead')
|
||||||
const GroupState$json = {
|
const GroupState$json = {
|
||||||
'1': 'GroupState',
|
'1': 'GroupState',
|
||||||
'2': [
|
'2': [
|
||||||
{'1': 'versionId', '3': 1, '4': 1, '5': 4, '10': 'versionId'},
|
{'1': 'version_id', '3': 1, '4': 1, '5': 4, '10': 'versionId'},
|
||||||
{'1': 'encrypted_group_state', '3': 3, '4': 1, '5': 12, '10': 'encryptedGroupState'},
|
{'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`.
|
/// Descriptor for `GroupState`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List groupStateDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List groupStateDescriptor = $convert.base64Decode(
|
||||||
'CgpHcm91cFN0YXRlEhwKCXZlcnNpb25JZBgBIAEoBFIJdmVyc2lvbklkEjIKFWVuY3J5cHRlZF'
|
'CgpHcm91cFN0YXRlEh0KCnZlcnNpb25faWQYASABKARSCXZlcnNpb25JZBIyChVlbmNyeXB0ZW'
|
||||||
'9ncm91cF9zdGF0ZRgDIAEoDFITZW5jcnlwdGVkR3JvdXBTdGF0ZQ==');
|
'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:fixnum/fixnum.dart' as $fixnum;
|
||||||
import 'package:protobuf/protobuf.dart' as $pb;
|
import 'package:protobuf/protobuf.dart' as $pb;
|
||||||
|
|
||||||
|
import 'groups.pbenum.dart';
|
||||||
|
|
||||||
|
export 'groups.pbenum.dart';
|
||||||
|
|
||||||
/// Stored encrypted on the server in the members columns.
|
/// Stored encrypted on the server in the members columns.
|
||||||
class EncryptedGroupState extends $pb.GeneratedMessage {
|
class EncryptedGroupState extends $pb.GeneratedMessage {
|
||||||
factory EncryptedGroupState({
|
factory EncryptedGroupState({
|
||||||
|
|
@ -109,6 +113,56 @@ class EncryptedGroupState extends $pb.GeneratedMessage {
|
||||||
void clearPadding() => clearField(5);
|
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 {
|
class EncryptedGroupStateEnvelop extends $pb.GeneratedMessage {
|
||||||
factory EncryptedGroupStateEnvelop({
|
factory EncryptedGroupStateEnvelop({
|
||||||
$core.List<$core.int>? nonce,
|
$core.List<$core.int>? nonce,
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,22 @@
|
||||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
// 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'
|
'VzQWZ0ZXJNaWxsaXNlY29uZHOIAQESGAoHcGFkZGluZxgFIAEoDFIHcGFkZGluZ0IiCiBfZGVs'
|
||||||
'ZXRlTWVzc2FnZXNBZnRlck1pbGxpc2Vjb25kcw==');
|
'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')
|
@$core.Deprecated('Use encryptedGroupStateEnvelopDescriptor instead')
|
||||||
const EncryptedGroupStateEnvelop$json = {
|
const EncryptedGroupStateEnvelop$json = {
|
||||||
'1': 'EncryptedGroupStateEnvelop',
|
'1': 'EncryptedGroupStateEnvelop',
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,13 @@ message EncryptedGroupState {
|
||||||
bytes padding = 5;
|
bytes padding = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message EncryptedAppendedGroupState {
|
||||||
|
enum Type {
|
||||||
|
LEFT_GROUP = 0;
|
||||||
|
}
|
||||||
|
Type type = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message EncryptedGroupStateEnvelop {
|
message EncryptedGroupStateEnvelop {
|
||||||
bytes nonce = 1;
|
bytes nonce = 1;
|
||||||
bytes encryptedGroupState = 2;
|
bytes encryptedGroupState = 2;
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ Future<void> handleGroupCreate(
|
||||||
groupName: const Value(''),
|
groupName: const Value(''),
|
||||||
joinedGroup: const Value(false),
|
joinedGroup: const Value(false),
|
||||||
leftGroup: const Value(false),
|
leftGroup: const Value(false),
|
||||||
|
deletedContent: const Value(false),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
||||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hashlib/random.dart';
|
import 'package:hashlib/random.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
|
|
@ -170,32 +170,13 @@ Future<void> fetchMissingGroupPublicKey() async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
Future<List<int>?> _decryptEnvelop(
|
||||||
if (group.leftGroup) {
|
Group group,
|
||||||
Log.error(
|
List<int> encryptedGroupState,
|
||||||
'Could not refresh group state, as user is no longer part of the group',
|
) async {
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
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(
|
final envelope = EncryptedGroupStateEnvelop.fromBuffer(
|
||||||
groupStateServer.encryptedGroupState,
|
encryptedGroupState,
|
||||||
);
|
);
|
||||||
final chacha20 = FlutterChacha20.poly1305Aead();
|
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||||
|
|
||||||
|
|
@ -210,19 +191,111 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
||||||
secretKey: SecretKey(group.stateEncryptionKey!),
|
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 =
|
final encryptedGroupState =
|
||||||
EncryptedGroupState.fromBuffer(encryptedGroupStateRaw);
|
EncryptedGroupState.fromBuffer(encryptedStateRaw);
|
||||||
|
|
||||||
if (group.stateVersionId >= groupStateServer.versionId.toInt()) {
|
if (group.stateVersionId >= groupStateServer.versionId.toInt()) {
|
||||||
Log.info(
|
Log.info(
|
||||||
'Group ${group.groupId} has already newest group state from the server!',
|
'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...
|
// OH no, I am no longer a member of this group...
|
||||||
// ->
|
// Return from the group...
|
||||||
await twonlyDB.groupsDao.updateGroup(
|
await twonlyDB.groupsDao.updateGroup(
|
||||||
group.groupId,
|
group.groupId,
|
||||||
const GroupsCompanion(
|
const GroupsCompanion(
|
||||||
|
|
@ -232,9 +305,41 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
||||||
return (groupStateServer.versionId.toInt(), encryptedGroupState);
|
return (groupStateServer.versionId.toInt(), encryptedGroupState);
|
||||||
}
|
}
|
||||||
|
|
||||||
final isGroupAdmin = encryptedGroupState.adminIds
|
final isGroupAdmin =
|
||||||
.firstWhereOrNull((t) => t.toInt() == gUser.userId) !=
|
adminIds.firstWhereOrNull((t) => t.toInt() == gUser.userId) != null;
|
||||||
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(
|
await twonlyDB.groupsDao.updateGroup(
|
||||||
group.groupId,
|
group.groupId,
|
||||||
|
|
@ -251,7 +356,7 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
||||||
await twonlyDB.groupsDao.getGroupMembers(group.groupId);
|
await twonlyDB.groupsDao.getGroupMembers(group.groupId);
|
||||||
|
|
||||||
// First find and insert NEW members
|
// First find and insert NEW members
|
||||||
for (final memberId in encryptedGroupState.memberIds) {
|
for (final memberId in memberIds) {
|
||||||
if (memberId == Int64(gUser.userId)) {
|
if (memberId == Int64(gUser.userId)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -313,7 +418,7 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
||||||
|
|
||||||
MemberState? newMemberState;
|
MemberState? newMemberState;
|
||||||
|
|
||||||
if (encryptedGroupState.adminIds.contains(Int64(member.contactId))) {
|
if (adminIds.contains(Int64(member.contactId))) {
|
||||||
if (member.memberState == MemberState.normal) {
|
if (member.memberState == MemberState.normal) {
|
||||||
// user was promoted
|
// user was promoted
|
||||||
newMemberState = MemberState.admin;
|
newMemberState = MemberState.admin;
|
||||||
|
|
@ -376,6 +481,7 @@ Future<bool> _updateGroupState(
|
||||||
EncryptedGroupState state, {
|
EncryptedGroupState state, {
|
||||||
Uint8List? addAdmin,
|
Uint8List? addAdmin,
|
||||||
Uint8List? removeAdmin,
|
Uint8List? removeAdmin,
|
||||||
|
int? versionId,
|
||||||
}) async {
|
}) async {
|
||||||
final chacha20 = FlutterChacha20.poly1305Aead();
|
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||||
final encryptionNonce = chacha20.newNonce();
|
final encryptionNonce = chacha20.newNonce();
|
||||||
|
|
@ -397,26 +503,14 @@ Future<bool> _updateGroupState(
|
||||||
|
|
||||||
final keyPair = IdentityKeyPair.fromSerialized(group.myGroupPrivateKey!);
|
final keyPair = IdentityKeyPair.fromSerialized(group.myGroupPrivateKey!);
|
||||||
|
|
||||||
final publicKey = uint8ListToHex(keyPair.getPublicKey().serialize());
|
final nonce = await getNonce(keyPair.getPublicKey().serialize());
|
||||||
|
if (nonce == null) return false;
|
||||||
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 updateTBS = UpdateGroupState_UpdateTBS(
|
final updateTBS = UpdateGroupState_UpdateTBS(
|
||||||
versionId: Int64(group.stateVersionId + 1),
|
versionId: Int64(versionId ?? group.stateVersionId + 1),
|
||||||
encryptedGroupState: encryptedGroupState.writeToBuffer(),
|
encryptedGroupState: encryptedGroupState.writeToBuffer(),
|
||||||
publicKey: keyPair.getPublicKey().serialize(),
|
publicKey: keyPair.getPublicKey().serialize(),
|
||||||
nonce: responseNonce.bodyBytes,
|
nonce: nonce,
|
||||||
addAdmin: addAdmin,
|
addAdmin: addAdmin,
|
||||||
removeAdmin: removeAdmin,
|
removeAdmin: removeAdmin,
|
||||||
);
|
);
|
||||||
|
|
@ -452,7 +546,7 @@ Future<bool> _updateGroupState(
|
||||||
|
|
||||||
Future<bool> manageAdminState(
|
Future<bool> manageAdminState(
|
||||||
Group group,
|
Group group,
|
||||||
GroupMember member,
|
Uint8List groupPublicKey,
|
||||||
int contactId,
|
int contactId,
|
||||||
bool remove,
|
bool remove,
|
||||||
) async {
|
) async {
|
||||||
|
|
@ -469,7 +563,7 @@ Future<bool> manageAdminState(
|
||||||
if (remove) {
|
if (remove) {
|
||||||
if (state.adminIds.contains(userId)) {
|
if (state.adminIds.contains(userId)) {
|
||||||
state.adminIds.remove(userId);
|
state.adminIds.remove(userId);
|
||||||
removeAdmin = member.groupPublicKey;
|
removeAdmin = groupPublicKey;
|
||||||
} else {
|
} else {
|
||||||
Log.info('User was already removed as admin.');
|
Log.info('User was already removed as admin.');
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -477,7 +571,7 @@ Future<bool> manageAdminState(
|
||||||
} else {
|
} else {
|
||||||
if (!state.adminIds.contains(userId)) {
|
if (!state.adminIds.contains(userId)) {
|
||||||
state.adminIds.add(userId);
|
state.adminIds.add(userId);
|
||||||
addAdmin = member.groupPublicKey;
|
addAdmin = groupPublicKey;
|
||||||
} else {
|
} else {
|
||||||
Log.info('User is already admin.');
|
Log.info('User is already admin.');
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -524,7 +618,7 @@ Future<bool> manageAdminState(
|
||||||
return (await fetchGroupState(group)) != null;
|
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
|
// ensure the latest state is used
|
||||||
final currentState = await fetchGroupState(group);
|
final currentState = await fetchGroupState(group);
|
||||||
if (currentState == null) return false;
|
if (currentState == null) return false;
|
||||||
|
|
@ -624,7 +718,7 @@ Future<bool> addNewGroupMembers(
|
||||||
|
|
||||||
Future<bool> removeMemberFromGroup(
|
Future<bool> removeMemberFromGroup(
|
||||||
Group group,
|
Group group,
|
||||||
GroupMember member,
|
Uint8List groupPublicKey,
|
||||||
int removeContactId,
|
int removeContactId,
|
||||||
) async {
|
) async {
|
||||||
// ensure the latest state is used
|
// ensure the latest state is used
|
||||||
|
|
@ -642,16 +736,16 @@ Future<bool> removeMemberFromGroup(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (adminIdSet.contains(contactId)) {
|
if (adminIdSet.contains(contactId)) {
|
||||||
if (member.groupPublicKey == null) {
|
// if (member.groupPublicKey == null) {
|
||||||
// If the admin public key is not removed, that the user could potentially still update the group state. So only
|
// // 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
|
// // 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.
|
// // the he can but the other user, could still update the group state.
|
||||||
Log.error(
|
// Log.error(
|
||||||
'Could not remove user. User is admin, but groupPublicKey is unknown.',
|
// 'Could not remove user. User is admin, but groupPublicKey is unknown.',
|
||||||
);
|
// );
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
removeAdmin = member.groupPublicKey;
|
removeAdmin = groupPublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
membersIdSet.remove(contactId);
|
membersIdSet.remove(contactId);
|
||||||
|
|
@ -684,10 +778,126 @@ Future<bool> removeMemberFromGroup(
|
||||||
GroupHistoriesCompanion(
|
GroupHistoriesCompanion(
|
||||||
groupId: Value(group.groupId),
|
groupId: Value(group.groupId),
|
||||||
type: const Value(GroupActionType.removedMember),
|
type: const Value(GroupActionType.removedMember),
|
||||||
affectedContactId: Value(removeContactId),
|
affectedContactId: Value(
|
||||||
|
removeContactId == gUser.userId ? null : removeContactId,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Updates the groupMembers table :)
|
// Updates the groupMembers table :)
|
||||||
return (await fetchGroupState(group)) != null;
|
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 String text;
|
||||||
final Widget? subtitle;
|
final Widget? subtitle;
|
||||||
final Color? color;
|
final Color? color;
|
||||||
final VoidCallback onTap;
|
final VoidCallback? onTap;
|
||||||
final double iconSize;
|
final double iconSize;
|
||||||
final EdgeInsets? padding;
|
final EdgeInsets? padding;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.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/globals.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
import 'package:twonly/src/database/tables/groups.table.dart';
|
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/group.services.dart';
|
import 'package:twonly/src/services/group.services.dart';
|
||||||
import 'package:twonly/src/utils/misc.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/avatar_icon.component.dart';
|
||||||
import 'package:twonly/src/views/components/better_list_title.dart';
|
import 'package:twonly/src/views/components/better_list_title.dart';
|
||||||
import 'package:twonly/src/views/components/verified_shield.dart';
|
import 'package:twonly/src/views/components/verified_shield.dart';
|
||||||
|
|
@ -74,7 +76,7 @@ class _GroupViewState extends State<GroupView> {
|
||||||
newGroupName != null &&
|
newGroupName != null &&
|
||||||
newGroupName != '' &&
|
newGroupName != '' &&
|
||||||
newGroupName != group.groupName) {
|
newGroupName != group.groupName) {
|
||||||
if (!await updateGroupeName(group, newGroupName)) {
|
if (!await updateGroupName(group, newGroupName)) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
showNetworkIssue(context);
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|
@ -126,7 +182,7 @@ class _GroupViewState extends State<GroupView> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 50),
|
const SizedBox(height: 50),
|
||||||
if (group.isGroupAdmin)
|
if (group.isGroupAdmin && !group.leftGroup)
|
||||||
BetterListTile(
|
BetterListTile(
|
||||||
icon: FontAwesomeIcons.pencil,
|
icon: FontAwesomeIcons.pencil,
|
||||||
text: context.lang.groupNameInput,
|
text: context.lang.groupNameInput,
|
||||||
|
|
@ -145,7 +201,7 @@ class _GroupViewState extends State<GroupView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (group.isGroupAdmin)
|
if (group.isGroupAdmin && !group.leftGroup)
|
||||||
BetterListTile(
|
BetterListTile(
|
||||||
icon: FontAwesomeIcons.plus,
|
icon: FontAwesomeIcons.plus,
|
||||||
text: context.lang.addMember,
|
text: context.lang.addMember,
|
||||||
|
|
@ -197,11 +253,24 @@ class _GroupViewState extends State<GroupView> {
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
if (!group.leftGroup)
|
||||||
BetterListTile(
|
BetterListTile(
|
||||||
icon: FontAwesomeIcons.rightFromBracket,
|
icon: FontAwesomeIcons.rightFromBracket,
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
text: context.lang.leaveGroup,
|
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) {
|
void showNetworkIssue(BuildContext context) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Network issue. Try again later.'),
|
content: Text(context.lang.groupNetworkIssue),
|
||||||
duration: Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class GroupMemberContextMenu extends StatelessWidget {
|
||||||
if (ok) {
|
if (ok) {
|
||||||
if (!await manageAdminState(
|
if (!await manageAdminState(
|
||||||
group,
|
group,
|
||||||
member,
|
member.groupPublicKey!,
|
||||||
contact.userId,
|
contact.userId,
|
||||||
false,
|
false,
|
||||||
)) {
|
)) {
|
||||||
|
|
@ -58,7 +58,7 @@ class GroupMemberContextMenu extends StatelessWidget {
|
||||||
if (ok) {
|
if (ok) {
|
||||||
if (!await manageAdminState(
|
if (!await manageAdminState(
|
||||||
group,
|
group,
|
||||||
member,
|
member.groupPublicKey!,
|
||||||
contact.userId,
|
contact.userId,
|
||||||
true,
|
true,
|
||||||
)) {
|
)) {
|
||||||
|
|
@ -78,7 +78,7 @@ class GroupMemberContextMenu extends StatelessWidget {
|
||||||
if (ok) {
|
if (ok) {
|
||||||
if (!await removeMemberFromGroup(
|
if (!await removeMemberFromGroup(
|
||||||
group,
|
group,
|
||||||
member,
|
member.groupPublicKey!,
|
||||||
contact.userId,
|
contact.userId,
|
||||||
)) {
|
)) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
|
@ -159,7 +159,7 @@ class GroupMemberContextMenu extends StatelessWidget {
|
||||||
onTap: () => _removeContactAsAdmin(context),
|
onTap: () => _removeContactAsAdmin(context),
|
||||||
icon: FontAwesomeIcons.key,
|
icon: FontAwesomeIcons.key,
|
||||||
),
|
),
|
||||||
if (group.isGroupAdmin)
|
if (group.isGroupAdmin && member.groupPublicKey != null)
|
||||||
ContextMenuItem(
|
ContextMenuItem(
|
||||||
title: context.lang.removeFromGroup,
|
title: context.lang.removeFromGroup,
|
||||||
onTap: () => _removeContactFromGroup(context),
|
onTap: () => _removeContactFromGroup(context),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:hashlib/random.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/misc.dart';
|
||||||
import 'package:twonly/src/utils/pow.dart';
|
import 'package:twonly/src/utils/pow.dart';
|
||||||
import 'package:twonly/src/views/components/animate_icon.dart';
|
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||||
|
|
@ -60,5 +63,20 @@ void main() {
|
||||||
test('Reject values > 0x7fffffff', () {
|
test('Reject values > 0x7fffffff', () {
|
||||||
expect(() => getUUIDforDirectChat(0x80000000, 0), throwsArgumentError);
|
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