Group V2 invite links: in-conversation messages

This commit is contained in:
Scott Nonnenberg 2020-12-18 11:27:43 -08:00
parent 8e7379a591
commit 272e6cc614
10 changed files with 1775 additions and 206 deletions

View file

@ -3405,6 +3405,42 @@
"message": "An admin changed who can edit group membership to \"All members.\"",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--access-invite-link--disabled--you": {
"message": "You disabled admin approval for the group link.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--access-invite-link--disabled--other": {
"message": "$adminName$ disabled admin approval for the group link.",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"adminName": {
"content": "$1",
"example": "Alice"
}
}
},
"GroupV2--access-invite-link--disabled--unknown": {
"message": "Admin approval for the group link has been disabled.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--access-invite-link--enabled--you": {
"message": "You enabled admin approval for the group link.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--access-invite-link--enabled--other": {
"message": "$adminName$ enabled admin approval for the group link.",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"adminName": {
"content": "$1",
"example": "Alice"
}
}
},
"GroupV2--access-invite-link--enabled--unknown": {
"message": "Admin approval for the group link has been disabled.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--member-add--invited--you": {
"message": "You added invited member $inviteeName$.",
"description": "Shown in timeline or conversation preview when v2 group changes",
@ -3539,6 +3575,72 @@
"message": "You were added to the group.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--member-add-from-link--you--you": {
"message": "You joined the group via the group link.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--member-add-from-link--other": {
"message": "$memberName$ joined the group via the group link.",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"memberName": {
"content": "$1",
"example": "Alice"
}
}
},
"GroupV2--member-add-from-admin-approval--you--other": {
"message": "$adminName$ approved your request to join the group.",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"adminName": {
"content": "$1",
"example": "Alice"
}
}
},
"GroupV2--member-add-from-admin-approval--you--unknown": {
"message": "Your request to join the group has been approved.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--member-add-from-admin-approval--other--you": {
"message": "You approved a request to join the group from $joinerName$.",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"joinerName": {
"content": "$1",
"example": "Alice"
}
}
},
"GroupV2--member-add-from-admin-approval--other--other": {
"message": "$adminName$ approved a request to join the group from $joinerName$.",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"adminName": {
"content": "$1",
"example": "Bob"
},
"joinerName": {
"content": "$1",
"example": "Alice"
}
}
},
"GroupV2--member-add-from-admin-approval--other--unknown": {
"message": "A request to join the group from $joinerName$ has been approved.",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"joinerName": {
"content": "$1",
"example": "Alice"
}
}
},
"GroupV2--member-remove--other--other": {
"message": "$adminName$ removed $memberName$.",
"description": "Shown in timeline or conversation preview when v2 group changes",
@ -4030,6 +4132,138 @@
}
}
},
"GroupV2--admin-approval-add-one--you": {
"message": "You sent a request to join the group.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--admin-approval-add-one--other": {
"message": "$joinerName$ requested to join via the group link.",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"joinerName": {
"content": "$1",
"example": "Alice"
}
}
},
"GroupV2--admin-approval-remove-one--you--you": {
"message": "You canceled your request to join the group.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--admin-approval-remove-one--you--unknown": {
"message": "Your request to join the group has been denied by an admin.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--admin-approval-remove-one--other--you": {
"message": "You denied a request to join the group from $joinerName$.",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"joinerName": {
"content": "$1",
"example": "Alice"
}
}
},
"GroupV2--admin-approval-remove-one--other--own": {
"message": "$joinerName$ canceled their request to join the group.",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"joinerName": {
"content": "$1",
"example": "Alice"
}
}
},
"GroupV2--admin-approval-remove-one--other--other": {
"message": "$adminName$ denied a request to join the group from $joinerName$.",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"adminName": {
"content": "$1",
"example": "Bob"
},
"joinerName": {
"content": "$2",
"example": "Alice"
}
}
},
"GroupV2--group-link-add--disabled--you": {
"message": "You turned on the group link with admin approval disabled.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--group-link-add--disabled--other": {
"message": "$adminName$ turned on the group link with admin approval disabled.",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"adminName": {
"content": "$1",
"example": "Alice"
}
}
},
"GroupV2--group-link-add--disabled--unknown": {
"message": "The group link has been turned on with admin approval disabled.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--group-link-add--enabled--you": {
"message": "You turned on the group link with admin approval enabled.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--group-link-add--enabled--other": {
"message": "$adminName$ turned on the group link with admin approval enabled.",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"adminName": {
"content": "$1",
"example": "Alice"
}
}
},
"GroupV2--group-link-add--enabled--unknown": {
"message": "The group link has been turned on with admin approval enabled.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--group-link-remove--you": {
"message": "You turned off the group link.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--group-link-remove--other": {
"message": "$adminName$ turned off the group link.",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"adminName": {
"content": "$1",
"example": "Alice"
}
}
},
"GroupV2--group-link-remove--unknown": {
"message": "The group link has been turned off.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--group-link-reset--you": {
"message": "You reset the group link.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--group-link-reset--other": {
"message": "$adminName$ reset the group link.",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"adminName": {
"content": "$1",
"example": "Alice"
}
}
},
"GroupV2--group-link-reset--unknown": {
"message": "The group link has been reset.",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV1--Migration--disabled": {
"message": "Upgrade this group to activate new features like @mentions and admins. Members who have not shared their name or photo in this group will be invited to join. $learnMore$",
"description": "Shown instead of composition area when user is forced to migrate a legacy group (GV1).",

View file

@ -27,6 +27,11 @@ try {
title += ` - ${config.appInstance}`;
}
// Flags for testing
window.GV2_ENABLE_SINGLE_CHANGE_PROCESSING = true;
window.GV2_ENABLE_CHANGE_PROCESSING = true;
window.GV2_ENABLE_STATE_PROCESSING = true;
window.platform = process.platform;
window.getTitle = () => title;
window.getEnvironment = () => config.environment;

View file

@ -29,32 +29,44 @@ message Member {
uint32 joinedAtVersion = 5; // The Group.version this member joined at
}
message PendingMember {
message MemberPendingProfileKey {
Member member = 1; // The invited member
bytes addedByUserId = 2; // The UID who invited this member
uint64 timestamp = 3; // The time the invitation occurred
}
message MemberPendingAdminApproval {
bytes userId = 1;
bytes profileKey = 2;
bytes presentation = 3;
uint64 timestamp = 4;
}
message AccessControl {
enum AccessRequired {
UNKNOWN = 0;
ANY = 1;
MEMBER = 2; // Any group member can make the modification
ADMINISTRATOR = 3; // Only administrators can make the modification
UNSATISFIABLE = 4;
}
AccessRequired attributes = 1; // Who can modify the group title, avatar, disappearing messages timer
AccessRequired members = 2; // Who can add people to the group
AccessRequired attributes = 1; // Who can modify the group title, avatar, disappearing messages timer
AccessRequired members = 2; // Who can add people to the group
AccessRequired addFromInviteLink = 3;
}
message Group {
bytes publicKey = 1; // GroupPublicParams
bytes title = 2; // Encrypted title
string avatar = 3; // Pointer to encrypted avatar (key from AvatarUploadAttributes)
bytes disappearingMessagesTimer = 4; // Encrypted timer
AccessControl accessControl = 5;
uint32 version = 6; // Current group version number
repeated Member members = 7;
repeated PendingMember pendingMembers = 8;
bytes publicKey = 1; // GroupPublicParams
bytes title = 2; // Encrypted title
string avatar = 3; // Pointer to encrypted avatar (key from AvatarUploadAttributes)
bytes disappearingMessagesTimer = 4; // Encrypted timer
AccessControl accessControl = 5;
uint32 version = 6; // Current group version number
repeated Member members = 7;
repeated MemberPendingProfileKey membersPendingProfileKey = 8;
repeated MemberPendingAdminApproval membersPendingAdminApproval = 9;
bytes inviteLinkPassword = 10;
}
message GroupChange {
@ -62,7 +74,8 @@ message GroupChange {
message Actions {
message AddMemberAction {
Member added = 1;
Member added = 1;
bool joinFromInviteLink = 2;
}
message DeleteMemberAction {
@ -78,18 +91,32 @@ message GroupChange {
bytes presentation = 1;
}
message AddPendingMemberAction {
PendingMember added = 1;
message AddMemberPendingProfileKeyAction {
MemberPendingProfileKey added = 1;
}
message DeletePendingMemberAction {
message DeleteMemberPendingProfileKeyAction {
bytes deletedUserId = 1;
}
message PromotePendingMemberAction {
message PromoteMemberPendingProfileKeyAction {
bytes presentation = 1;
}
message AddMemberPendingAdminApprovalAction {
MemberPendingAdminApproval added = 1;
}
message DeleteMemberPendingAdminApprovalAction {
bytes deletedUserId = 1;
}
message PromoteMemberPendingAdminApprovalAction {
bytes userId = 1;
Member.Role role = 2;
}
message ModifyTitleAction {
bytes title = 1;
}
@ -114,20 +141,34 @@ message GroupChange {
AccessControl.AccessRequired membersAccess = 1;
}
bytes sourceUuid = 1; // Who made the change
uint32 version = 2; // The change version number
repeated AddMemberAction addMembers = 3; // Members added
repeated DeleteMemberAction deleteMembers = 4; // Members deleted
repeated ModifyMemberRoleAction modifyMemberRoles = 5; // Modified member roles
repeated ModifyMemberProfileKeyAction modifyMemberProfileKeys = 6; // Modified member profile keys
repeated AddPendingMemberAction addPendingMembers = 7; // Pending members added
repeated DeletePendingMemberAction deletePendingMembers = 8; // Pending members deleted
repeated PromotePendingMemberAction promotePendingMembers = 9; // Pending invitations accepted
ModifyTitleAction modifyTitle = 10; // Changed title
ModifyAvatarAction modifyAvatar = 11; // Changed avatar
ModifyDisappearingMessagesTimerAction modifyDisappearingMessagesTimer = 12; // Changed timer
ModifyAttributesAccessControlAction modifyAttributesAccess = 13; // Changed attributes access control
ModifyMembersAccessControlAction modifyMemberAccess = 14; // Changed membership access control
message ModifyAddFromInviteLinkAccessControlAction {
AccessControl.AccessRequired addFromInviteLinkAccess = 1;
}
message ModifyInviteLinkPasswordAction {
bytes inviteLinkPassword = 1;
}
bytes sourceUuid = 1; // Who made the change
uint32 version = 2; // The change version number
repeated AddMemberAction addMembers = 3; // Members added
repeated DeleteMemberAction deleteMembers = 4; // Members deleted
repeated ModifyMemberRoleAction modifyMemberRoles = 5; // Modified member roles
repeated ModifyMemberProfileKeyAction modifyMemberProfileKeys = 6; // Modified member profile keys
repeated AddMemberPendingProfileKeyAction addPendingMembers = 7; // Pending members added
repeated DeleteMemberPendingProfileKeyAction deletePendingMembers = 8; // Pending members deleted
repeated PromoteMemberPendingProfileKeyAction promotePendingMembers = 9; // Pending invitations accepted
ModifyTitleAction modifyTitle = 10; // Changed title
ModifyAvatarAction modifyAvatar = 11; // Changed avatar
ModifyDisappearingMessagesTimerAction modifyDisappearingMessagesTimer = 12; // Changed timer
ModifyAttributesAccessControlAction modifyAttributesAccess = 13; // Changed attributes access control
ModifyMembersAccessControlAction modifyMemberAccess = 14; // Changed membership access control
ModifyAddFromInviteLinkAccessControlAction modifyAddFromInviteLinkAccess = 15;
repeated AddMemberPendingAdminApprovalAction addMemberPendingAdminApprovals = 16;
repeated DeleteMemberPendingAdminApprovalAction deleteMemberPendingAdminApprovals = 17;
repeated PromoteMemberPendingAdminApprovalAction promoteMemberPendingAdminApprovals = 18;
ModifyInviteLinkPasswordAction modifyInviteLinkPassword = 19;
}
bytes actions = 1; // The serialized actions
@ -155,3 +196,14 @@ message GroupAttributeBlob {
message GroupExternalCredential {
string token = 1;
}
message GroupInviteLink {
message GroupInviteLinkContentsV1 {
bytes groupMasterKey = 1;
bytes inviteLinkPassword = 2;
}
oneof contents {
GroupInviteLinkContentsV1 v1Contents = 1;
}
}

View file

@ -23,11 +23,13 @@ const INVITEE_A = 'INVITEE_A';
class AccessControlEnum {
static UNKNOWN = 0;
static ADMINISTRATOR = 1;
static ANY = 1;
static ANY = 2;
static MEMBER = 2;
static MEMBER = 3;
static ADMINISTRATOR = 3;
static UNSATISFIABLE = 4;
}
class RoleEnum {
@ -342,6 +344,64 @@ storiesOf('Components/Conversation/GroupV2Change', module)
</>
);
})
.add('Access (Invite Link)', () => {
return (
<>
{renderChange({
from: OUR_ID,
details: [
{
type: 'access-invite-link',
newPrivilege: AccessControlEnum.ANY,
},
],
})}
{renderChange({
from: ADMIN_A,
details: [
{
type: 'access-invite-link',
newPrivilege: AccessControlEnum.ANY,
},
],
})}
{renderChange({
details: [
{
type: 'access-invite-link',
newPrivilege: AccessControlEnum.ANY,
},
],
})}
{renderChange({
from: OUR_ID,
details: [
{
type: 'access-invite-link',
newPrivilege: AccessControlEnum.ADMINISTRATOR,
},
],
})}
{renderChange({
from: ADMIN_A,
details: [
{
type: 'access-invite-link',
newPrivilege: AccessControlEnum.ADMINISTRATOR,
},
],
})}
{renderChange({
details: [
{
type: 'access-invite-link',
newPrivilege: AccessControlEnum.ADMINISTRATOR,
},
],
})}
</>
);
})
.add('Member Add', () => {
return (
<>
@ -400,7 +460,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
</>
);
})
.add('Member Add - add invited', () => {
.add('Member Add (from invited)', () => {
return (
<>
{/* the strings where someone added you - shown like a normal add */}
@ -505,6 +565,87 @@ storiesOf('Components/Conversation/GroupV2Change', module)
</>
);
})
.add('Member Add (from link)', () => {
return (
<>
{renderChange({
from: OUR_ID,
details: [
{
type: 'member-add-from-link',
conversationId: OUR_ID,
},
],
})}
{renderChange({
from: CONTACT_A,
details: [
{
type: 'member-add-from-link',
conversationId: CONTACT_A,
},
],
})}
{renderChange({
details: [
{
type: 'member-add-from-link',
conversationId: CONTACT_A,
},
],
})}
</>
);
})
.add('Member Add (from admin approval)', () => {
return (
<>
{renderChange({
from: ADMIN_A,
details: [
{
type: 'member-add-from-admin-approval',
conversationId: OUR_ID,
},
],
})}
{renderChange({
details: [
{
type: 'member-add-from-admin-approval',
conversationId: OUR_ID,
},
],
})}
{renderChange({
from: OUR_ID,
details: [
{
type: 'member-add-from-admin-approval',
conversationId: CONTACT_A,
},
],
})}
{renderChange({
from: ADMIN_A,
details: [
{
type: 'member-add-from-admin-approval',
conversationId: CONTACT_A,
},
],
})}
{renderChange({
details: [
{
type: 'member-add-from-admin-approval',
conversationId: CONTACT_A,
},
],
})}
</>
);
})
.add('Member Remove', () => {
return (
<>
@ -987,4 +1128,200 @@ storiesOf('Components/Conversation/GroupV2Change', module)
})}
</>
);
})
.add('Admin Approval (Add)', () => {
return (
<>
{renderChange({
details: [
{
type: 'admin-approval-add-one',
conversationId: OUR_ID,
},
],
})}
{renderChange({
details: [
{
type: 'admin-approval-add-one',
conversationId: CONTACT_A,
},
],
})}
</>
);
})
.add('Admin Approval (Remove)', () => {
return (
<>
{renderChange({
from: OUR_ID,
details: [
{
type: 'admin-approval-remove-one',
conversationId: OUR_ID,
},
],
})}
{renderChange({
details: [
{
type: 'admin-approval-remove-one',
conversationId: OUR_ID,
},
],
})}
{renderChange({
from: OUR_ID,
details: [
{
type: 'admin-approval-remove-one',
conversationId: CONTACT_A,
},
],
})}
{renderChange({
from: CONTACT_A,
details: [
{
type: 'admin-approval-remove-one',
conversationId: CONTACT_A,
},
],
})}
{renderChange({
from: ADMIN_A,
details: [
{
type: 'admin-approval-remove-one',
conversationId: CONTACT_A,
},
],
})}
{renderChange({
details: [
{
type: 'admin-approval-remove-one',
conversationId: CONTACT_A,
},
],
})}
</>
);
})
.add('Group Link (Add)', () => {
return (
<>
{renderChange({
from: OUR_ID,
details: [
{
type: 'group-link-add',
privilege: AccessControlEnum.ANY,
},
],
})}
{renderChange({
from: ADMIN_A,
details: [
{
type: 'group-link-add',
privilege: AccessControlEnum.ANY,
},
],
})}
{renderChange({
details: [
{
type: 'group-link-add',
privilege: AccessControlEnum.ANY,
},
],
})}
{renderChange({
from: OUR_ID,
details: [
{
type: 'group-link-add',
privilege: AccessControlEnum.ADMINISTRATOR,
},
],
})}
{renderChange({
from: ADMIN_A,
details: [
{
type: 'group-link-add',
privilege: AccessControlEnum.ADMINISTRATOR,
},
],
})}
{renderChange({
details: [
{
type: 'group-link-add',
privilege: AccessControlEnum.ADMINISTRATOR,
},
],
})}
</>
);
})
.add('Group Link (Reset)', () => {
return (
<>
{renderChange({
from: OUR_ID,
details: [
{
type: 'group-link-reset',
},
],
})}
{renderChange({
from: ADMIN_A,
details: [
{
type: 'group-link-reset',
},
],
})}
{renderChange({
details: [
{
type: 'group-link-reset',
},
],
})}
</>
);
})
.add('Group Link (Remove)', () => {
return (
<>
{renderChange({
from: OUR_ID,
details: [
{
type: 'group-link-remove',
},
],
})}
{renderChange({
from: ADMIN_A,
details: [
{
type: 'group-link-remove',
},
],
})}
{renderChange({
details: [
{
type: 'group-link-remove',
},
],
})}
</>
);
});

View file

@ -138,10 +138,12 @@ export function renderChangeDetail(
}
return renderString('GroupV2--access-attributes--all--unknown', i18n);
}
throw new Error(
window.log.warn(
`access-attributes change type, privilege ${newPrivilege} is unknown`
);
} else if (detail.type === 'access-members') {
return '';
}
if (detail.type === 'access-members') {
const { newPrivilege } = detail;
if (newPrivilege === AccessControlEnum.ADMINISTRATOR) {
@ -166,10 +168,52 @@ export function renderChangeDetail(
}
return renderString('GroupV2--access-members--all--unknown', i18n);
}
throw new Error(
window.log.warn(
`access-members change type, privilege ${newPrivilege} is unknown`
);
} else if (detail.type === 'member-add') {
return '';
}
if (detail.type === 'access-invite-link') {
const { newPrivilege } = detail;
if (newPrivilege === AccessControlEnum.ADMINISTRATOR) {
if (fromYou) {
return renderString('GroupV2--access-invite-link--enabled--you', i18n);
}
if (from) {
return renderString(
'GroupV2--access-invite-link--enabled--other',
i18n,
[renderContact(from)]
);
}
return renderString(
'GroupV2--access-invite-link--enabled--unknown',
i18n
);
}
if (newPrivilege === AccessControlEnum.ANY) {
if (fromYou) {
return renderString('GroupV2--access-invite-link--disabled--you', i18n);
}
if (from) {
return renderString(
'GroupV2--access-invite-link--disabled--other',
i18n,
[renderContact(from)]
);
}
return renderString(
'GroupV2--access-invite-link--disabled--unknown',
i18n
);
}
window.log.warn(
`access-invite-link change type, privilege ${newPrivilege} is unknown`
);
return '';
}
if (detail.type === 'member-add') {
const { conversationId } = detail;
const weAreJoiner = conversationId === ourConversationId;
@ -198,7 +242,8 @@ export function renderChangeDetail(
return renderString('GroupV2--member-add--other--unknown', i18n, [
renderContact(conversationId),
]);
} else if (detail.type === 'member-add-from-invite') {
}
if (detail.type === 'member-add-from-invite') {
const { conversationId, inviter } = detail;
const weAreJoiner = conversationId === ourConversationId;
const weAreInviter = Boolean(inviter && inviter === ourConversationId);
@ -259,7 +304,80 @@ export function renderChangeDetail(
inviteeName: renderContact(conversationId),
}
);
} else if (detail.type === 'member-remove') {
}
if (detail.type === 'member-add-from-link') {
const { conversationId } = detail;
if (fromYou && conversationId === ourConversationId) {
return renderString('GroupV2--member-add-from-link--you--you', i18n);
}
if (from && conversationId === from) {
return renderString('GroupV2--member-add-from-link--other', i18n, [
renderContact(from),
]);
}
// Note: this shouldn't happen, because we only capture 'add-from-link' status
// from group change events, which always have a sender.
window.log.warn('member-add-from-link change type; we have no from!');
return renderString('GroupV2--member-add--other--unknown', i18n, [
renderContact(conversationId),
]);
}
if (detail.type === 'member-add-from-admin-approval') {
const { conversationId } = detail;
const weAreJoiner = conversationId === ourConversationId;
if (weAreJoiner) {
if (from) {
return renderString(
'GroupV2--member-add-from-admin-approval--you--other',
i18n,
[renderContact(from)]
);
}
// Note: this shouldn't happen, because we only capture 'add-from-admin-approval'
// status from group change events, which always have a sender.
window.log.warn(
'member-add-from-admin-approval change type; we have no from, and we are joiner!'
);
return renderString(
'GroupV2--member-add-from-admin-approval--you--unknown',
i18n
);
}
if (fromYou) {
return renderString(
'GroupV2--member-add-from-admin-approval--other--you',
i18n,
[renderContact(conversationId)]
);
}
if (from) {
return renderString(
'GroupV2--member-add-from-admin-approval--other--other',
i18n,
{
adminName: renderContact(from),
joinerName: renderContact(conversationId),
}
);
}
// Note: this shouldn't happen, because we only capture 'add-from-admin-approval'
// status from group change events, which always have a sender.
window.log.warn(
'member-add-from-admin-approval change type; we have no from'
);
return renderString(
'GroupV2--member-add-from-admin-approval--other--unknown',
i18n,
[renderContact(conversationId)]
);
}
if (detail.type === 'member-remove') {
const { conversationId } = detail;
const weAreLeaver = conversationId === ourConversationId;
@ -294,7 +412,8 @@ export function renderChangeDetail(
return renderString('GroupV2--member-remove--other--unknown', i18n, [
renderContact(conversationId),
]);
} else if (detail.type === 'member-privilege') {
}
if (detail.type === 'member-privilege') {
const { conversationId, newPrivilege } = detail;
const weAreMember = conversationId === ourConversationId;
@ -375,10 +494,12 @@ export function renderChangeDetail(
[renderContact(conversationId)]
);
}
throw new Error(
window.log.warn(
`member-privilege change type, privilege ${newPrivilege} is unknown`
);
} else if (detail.type === 'pending-add-one') {
return '';
}
if (detail.type === 'pending-add-one') {
const { conversationId } = detail;
const weAreInvited = conversationId === ourConversationId;
if (weAreInvited) {
@ -400,7 +521,8 @@ export function renderChangeDetail(
]);
}
return renderString('GroupV2--pending-add--one--other--unknown', i18n);
} else if (detail.type === 'pending-add-many') {
}
if (detail.type === 'pending-add-many') {
const { count } = detail;
if (fromYou) {
@ -417,7 +539,8 @@ export function renderChangeDetail(
return renderString('GroupV2--pending-add--many--unknown', i18n, [
count.toString(),
]);
} else if (detail.type === 'pending-remove-one') {
}
if (detail.type === 'pending-remove-one') {
const { inviter, conversationId } = detail;
const weAreInviter = Boolean(inviter && inviter === ourConversationId);
const weAreInvited = conversationId === ourConversationId;
@ -511,7 +634,8 @@ export function renderChangeDetail(
]);
}
return renderString('GroupV2--pending-remove--revoke--one--unknown', i18n);
} else if (detail.type === 'pending-remove-many') {
}
if (detail.type === 'pending-remove-many') {
const { count, inviter } = detail;
const weAreInviter = Boolean(inviter && inviter === ourConversationId);
@ -590,7 +714,120 @@ export function renderChangeDetail(
i18n,
[count.toString()]
);
} else {
throw missingCaseError(detail);
}
if (detail.type === 'admin-approval-add-one') {
const { conversationId } = detail;
const weAreJoiner = conversationId === ourConversationId;
if (weAreJoiner) {
return renderString('GroupV2--admin-approval-add-one--you', i18n);
}
return renderString('GroupV2--admin-approval-add-one--other', i18n, [
renderContact(conversationId),
]);
}
if (detail.type === 'admin-approval-remove-one') {
const { conversationId } = detail;
const weAreJoiner = conversationId === ourConversationId;
if (weAreJoiner) {
if (fromYou) {
return renderString(
'GroupV2--admin-approval-remove-one--you--you',
i18n
);
}
return renderString(
'GroupV2--admin-approval-remove-one--you--unknown',
i18n
);
}
if (fromYou) {
return renderString(
'GroupV2--admin-approval-remove-one--other--you',
i18n,
[renderContact(conversationId)]
);
}
if (from && from === conversationId) {
return renderString(
'GroupV2--admin-approval-remove-one--other--own',
i18n,
[renderContact(conversationId)]
);
}
if (from) {
return renderString(
'GroupV2--admin-approval-remove-one--other--other',
i18n,
{
adminName: renderContact(from),
joinerName: renderContact(conversationId),
}
);
}
// We default to the user canceling their request, because it is far more likely that
// if an admin does the denial, we'll get a change event from them.
return renderString(
'GroupV2--admin-approval-remove-one--other--own',
i18n,
[renderContact(conversationId)]
);
}
if (detail.type === 'group-link-add') {
const { privilege } = detail;
if (privilege === AccessControlEnum.ADMINISTRATOR) {
if (fromYou) {
return renderString('GroupV2--group-link-add--enabled--you', i18n);
}
if (from) {
return renderString('GroupV2--group-link-add--enabled--other', i18n, [
renderContact(from),
]);
}
return renderString('GroupV2--group-link-add--enabled--unknown', i18n);
}
if (privilege === AccessControlEnum.ANY) {
if (fromYou) {
return renderString('GroupV2--group-link-add--disabled--you', i18n);
}
if (from) {
return renderString('GroupV2--group-link-add--disabled--other', i18n, [
renderContact(from),
]);
}
return renderString('GroupV2--group-link-add--disabled--unknown', i18n);
}
window.log.warn(
`group-link-add change type, privilege ${privilege} is unknown`
);
return '';
}
if (detail.type === 'group-link-reset') {
if (fromYou) {
return renderString('GroupV2--group-link-reset--you', i18n);
}
if (from) {
return renderString('GroupV2--group-link-reset--other', i18n, [
renderContact(from),
]);
}
return renderString('GroupV2--group-link-reset--unknown', i18n);
}
if (detail.type === 'group-link-remove') {
if (fromYou) {
return renderString('GroupV2--group-link-remove--you', i18n);
}
if (from) {
return renderString('GroupV2--group-link-remove--other', i18n, [
renderContact(from),
]);
}
return renderString('GroupV2--group-link-remove--unknown', i18n);
}
throw missingCaseError(detail);
}

File diff suppressed because it is too large Load diff

13
ts/model-types.d.ts vendored
View file

@ -230,6 +230,7 @@ export type ConversationAttributesType = {
accessControl?: {
attributes: AccessRequiredEnum;
members: AccessRequiredEnum;
addFromInviteLink: AccessRequiredEnum;
};
avatar?: {
url: string;
@ -239,6 +240,8 @@ export type ConversationAttributesType = {
expireTimer?: number;
membersV2?: Array<GroupV2MemberType>;
pendingMembersV2?: Array<GroupV2PendingMemberType>;
pendingAdminApprovalV2?: Array<GroupV2PendingAdminApprovalType>;
groupInviteLinkPassword?: string;
previousGroupV1Id?: string;
previousGroupV1Members?: Array<string>;
};
@ -247,6 +250,12 @@ export type GroupV2MemberType = {
conversationId: string;
role: MemberRoleEnum;
joinedAtVersion: number;
// Note that these are temporary flags, generated by applyGroupChange, but eliminated
// by applyGroupState. They are used to make our diff-generation more intelligent but
// not after that.
joinedFromLink?: boolean;
approvedByAdmin?: boolean;
};
export type GroupV2PendingMemberType = {
addedByUserId?: string;
@ -254,6 +263,10 @@ export type GroupV2PendingMemberType = {
timestamp: number;
role: MemberRoleEnum;
};
export type GroupV2PendingAdminApprovalType = {
conversationId: string;
timestamp: number;
};
export type VerificationOptions = {
key?: null | ArrayBuffer;

View file

@ -37,6 +37,12 @@ const directConsole = {
const logger = createLogger({
logger: directConsole,
predicate: (_getState, action) => {
if (action.type === 'network/CHECK_NETWORK_STATUS') {
return false;
}
return true;
},
});
const middlewareList = [

94
ts/textsecure.d.ts vendored
View file

@ -169,13 +169,15 @@ type DeviceNameProtobufTypes = {
type GroupsProtobufTypes = {
AvatarUploadAttributes: typeof AvatarUploadAttributesClass;
Member: typeof MemberClass;
PendingMember: typeof PendingMemberClass;
MemberPendingProfileKey: typeof MemberPendingProfileKeyClass;
MemberPendingAdminApproval: typeof MemberPendingAdminApprovalClass;
AccessControl: typeof AccessControlClass;
Group: typeof GroupClass;
GroupChange: typeof GroupChangeClass;
GroupChanges: typeof GroupChangesClass;
GroupAttributeBlob: typeof GroupAttributeBlobClass;
GroupExternalCredential: typeof GroupExternalCredentialClass;
GroupInviteLink: typeof GroupInviteLinkClass;
};
type SignalServiceProtobufTypes = {
@ -227,7 +229,7 @@ type ProtobufCollectionType = {
// with a type that the app can use. Being more rigorous with these
// types would require code changes, out of scope for now.
export type ProtoBinaryType = any;
type ProtoBigNumberType = any;
export type ProtoBigNumberType = any;
// Groups.proto
@ -272,17 +274,29 @@ export declare namespace MemberClass {
}
}
export declare class PendingMemberClass {
export declare class MemberPendingProfileKeyClass {
static decode: (
data: ArrayBuffer | ByteBufferClass,
encoding?: string
) => PendingMemberClass;
) => MemberPendingProfileKeyClass;
member?: MemberClass;
addedByUserId?: ProtoBinaryType;
timestamp?: ProtoBigNumberType;
}
export declare class MemberPendingAdminApprovalClass {
static decode: (
data: ArrayBuffer | ByteBufferClass,
encoding?: string
) => MemberPendingProfileKeyClass;
userId?: ProtoBinaryType;
profileKey?: ProtoBinaryType;
presentation?: ProtoBinaryType;
timestamp?: ProtoBigNumberType;
}
export declare class AccessControlClass {
static decode: (
data: ArrayBuffer | ByteBufferClass,
@ -291,6 +305,7 @@ export declare class AccessControlClass {
attributes?: AccessRequiredEnum;
members?: AccessRequiredEnum;
addFromInviteLink?: AccessRequiredEnum;
}
export type AccessRequiredEnum = number;
@ -298,10 +313,11 @@ export type AccessRequiredEnum = number;
// Note: we need to use namespaces to express nested classes in Typescript
export declare namespace AccessControlClass {
class AccessRequired {
static ANY: number;
static UNKNOWN: number;
static ANY: number;
static MEMBER: number;
static ADMINISTRATOR: number;
static UNSATISFIABLE: number;
}
}
@ -319,7 +335,9 @@ export declare class GroupClass {
accessControl?: AccessControlClass;
version?: number;
members?: Array<MemberClass>;
pendingMembers?: Array<PendingMemberClass>;
membersPendingProfileKey?: Array<MemberPendingProfileKeyClass>;
membersPendingAdminApproval?: Array<MemberPendingAdminApprovalClass>;
inviteLinkPassword?: ProtoBinaryType;
}
export declare class GroupChangeClass {
@ -351,18 +369,31 @@ export declare namespace GroupChangeClass {
modifyMemberProfileKeys?: Array<
GroupChangeClass.Actions.ModifyMemberProfileKeyAction
>;
addPendingMembers?: Array<GroupChangeClass.Actions.AddPendingMemberAction>;
addPendingMembers?: Array<
GroupChangeClass.Actions.AddMemberPendingProfileKeyAction
>;
deletePendingMembers?: Array<
GroupChangeClass.Actions.DeletePendingMemberAction
GroupChangeClass.Actions.DeleteMemberPendingProfileKeyAction
>;
promotePendingMembers?: Array<
GroupChangeClass.Actions.PromotePendingMemberAction
GroupChangeClass.Actions.PromoteMemberPendingProfileKeyAction
>;
modifyTitle?: GroupChangeClass.Actions.ModifyTitleAction;
modifyAvatar?: GroupChangeClass.Actions.ModifyAvatarAction;
modifyDisappearingMessagesTimer?: GroupChangeClass.Actions.ModifyDisappearingMessagesTimerAction;
modifyAttributesAccess?: GroupChangeClass.Actions.ModifyAttributesAccessControlAction;
modifyMemberAccess?: GroupChangeClass.Actions.ModifyMembersAccessControlAction;
modifyAddFromInviteLinkAccess?: GroupChangeClass.Actions.ModifyAddFromInviteLinkAccessControlAction;
addMemberPendingAdminApprovals?: Array<
GroupChangeClass.Actions.AddMemberPendingAdminApprovalAction
>;
deleteMemberPendingAdminApprovals?: Array<
GroupChangeClass.Actions.DeleteMemberPendingAdminApprovalAction
>;
promoteMemberPendingAdminApprovals?: Array<
GroupChangeClass.Actions.PromoteMemberPendingAdminApprovalAction
>;
modifyInviteLinkPassword?: GroupChangeClass.Actions.ModifyInviteLinkPasswordAction;
}
}
@ -370,6 +401,7 @@ export declare namespace GroupChangeClass {
export declare namespace GroupChangeClass.Actions {
class AddMemberAction {
added?: MemberClass;
joinFromInviteLink?: boolean;
}
class DeleteMemberAction {
@ -389,15 +421,15 @@ export declare namespace GroupChangeClass.Actions {
uuid: string;
}
class AddPendingMemberAction {
added?: PendingMemberClass;
class AddMemberPendingProfileKeyAction {
added?: MemberPendingProfileKeyClass;
}
class DeletePendingMemberAction {
class DeleteMemberPendingProfileKeyAction {
deletedUserId?: ProtoBinaryType;
}
class PromotePendingMemberAction {
class PromoteMemberPendingProfileKeyAction {
presentation?: ProtoBinaryType;
// The result of decryption
@ -405,6 +437,19 @@ export declare namespace GroupChangeClass.Actions {
uuid: string;
}
class AddMemberPendingAdminApprovalAction {
added?: MemberPendingAdminApprovalClass;
}
class DeleteMemberPendingAdminApprovalAction {
deletedUserId?: ProtoBinaryType;
}
class PromoteMemberPendingAdminApprovalAction {
userId?: ProtoBinaryType;
role?: MemberRoleEnum;
}
class ModifyTitleAction {
title?: ProtoBinaryType;
}
@ -424,6 +469,14 @@ export declare namespace GroupChangeClass.Actions {
class ModifyMembersAccessControlAction {
membersAccess?: AccessRequiredEnum;
}
class ModifyAddFromInviteLinkAccessControlAction {
addFromInviteLinkAccess?: AccessRequiredEnum;
}
class ModifyInviteLinkPasswordAction {
inviteLinkPassword?: ProtoBinaryType;
}
}
export declare class GroupChangesClass {
@ -452,6 +505,21 @@ export declare class GroupExternalCredentialClass {
token?: string;
}
export declare class GroupInviteLinkClass {
v1Contents?: GroupInviteLinkClass.GroupInviteLinkContentsV1;
// Note: this isn't part of the proto, but our protobuf library tells us which
// field has been set with this prop.
contents?: 'v1Contents';
}
export declare namespace GroupInviteLinkClass {
class GroupInviteLinkContentsV1 {
groupMasterKey?: ProtoBinaryType;
inviteLinkPassword?: ProtoBinaryType;
}
}
export declare class GroupAttributeBlobClass {
static decode: (
data: ArrayBuffer | ByteBufferClass,

3
ts/window.d.ts vendored
View file

@ -480,6 +480,9 @@ declare global {
// Flags
isGroupCallingEnabled: () => boolean;
GV2_ENABLE_SINGLE_CHANGE_PROCESSING: boolean;
GV2_ENABLE_CHANGE_PROCESSING: boolean;
GV2_ENABLE_STATE_PROCESSING: boolean;
}
interface Error {