From a5fce17d8c163678a2eb2e941536953aadd3d17b Mon Sep 17 00:00:00 2001 From: Maxim Shishmarev Date: Fri, 22 Nov 2019 16:16:43 +1100 Subject: [PATCH 1/4] public chat invitations --- _locales/en/messages.json | 9 ++ background.html | 1 + js/background.js | 60 ++++++++ js/models/conversations.js | 11 +- js/models/messages.js | 44 ++++-- js/modules/signal.js | 8 ++ js/views/app_view.js | 4 + js/views/conversation_view.js | 4 + js/views/invite_friends_dialog_view.js | 53 +++++++ js/views/message_view.js | 5 + libtextsecure/sendmessage.js | 15 +- protos/SignalService.proto | 6 + stylesheets/_conversation.scss | 74 ++++++++++ stylesheets/_mentions.scss | 9 +- test/index.html | 2 +- ts/components/ConversationListItem.tsx | 1 + .../conversation/ConversationHeader.tsx | 6 + .../conversation/GroupInvitation.tsx | 45 ++++++ .../conversation/InviteFriendsDialog.tsx | 132 ++++++++++++++++++ 19 files changed, 475 insertions(+), 14 deletions(-) create mode 100644 js/views/invite_friends_dialog_view.js create mode 100644 ts/components/conversation/GroupInvitation.tsx create mode 100644 ts/components/conversation/InviteFriendsDialog.tsx diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 7050831ae..3807a75f8 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2229,5 +2229,14 @@ }, "groupNamePlaceholder": { "message": "Group Name" + }, + "inviteFriends": { + "message": "Invite Friends" + }, + "groupInvitation": { + "message": "Group Invitation" + }, + "add-friends": { + "message": "Adding friends to" } } diff --git a/background.html b/background.html index 8757b64be..cc9282ffb 100644 --- a/background.html +++ b/background.html @@ -817,6 +817,7 @@ + diff --git a/js/background.js b/js/background.js index b367d641b..dda8f8199 100644 --- a/js/background.js +++ b/js/background.js @@ -799,6 +799,22 @@ appView.openConversation(groupId, {}); }; + window.sendGroupInvitations = (serverInfo, pubkeys) => { + pubkeys.forEach(async pubkey => { + const convo = await ConversationController.getOrCreateAndWait( + pubkey, + 'private' + ); + + if (convo) { + convo.sendMessage('', null, null, null, { + serverName: serverInfo.name, + serverAddress: serverInfo.address, + }); + } + }); + }; + Whisper.events.on('createNewGroup', async () => { if (appView) { appView.showCreateGroup(); @@ -811,6 +827,50 @@ } }); + Whisper.events.on('inviteFriends', async groupConvo => { + if (appView) { + appView.showInviteFriendsDialog(groupConvo); + } + }); + + Whisper.events.on('invitationAccepted', async serverAddress => { + // To some degree this has been copy-pasted + // form connection_to_server_dialog_view.js: + const channelId = 1; + const rawServerUrl = serverAddress + .replace(/^https?:\/\//i, '') + .replace(/[/\\]+$/i, ''); + const sslServerUrl = `https://${rawServerUrl}`; + const conversationId = `publicChat:${channelId}@${rawServerUrl}`; + + const conversationExists = ConversationController.get(conversationId); + if (conversationExists) { + window.log.warn('We are already a member of this public chat'); + return; + } + + const serverAPI = await window.lokiPublicChatAPI.findOrCreateServer( + sslServerUrl + ); + if (!serverAPI) { + window.log.warn(`Could not connect to ${serverAddress}`); + return; + } + + const conversation = await ConversationController.getOrCreateAndWait( + conversationId, + 'group' + ); + + serverAPI.findOrCreateChannel(channelId, conversationId); + await conversation.setPublicSource(sslServerUrl, channelId); + await conversation.setFriendRequestStatus( + window.friends.friendRequestStatusEnum.friends + ); + + appView.openConversation(conversationId, {}); + }); + Whisper.events.on('leaveGroup', async groupConvo => { if (appView) { appView.showLeaveGroupDialog(groupConvo); diff --git a/js/models/conversations.js b/js/models/conversations.js index a682e6fd9..b2fde98ee 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -1418,7 +1418,13 @@ }; }, - async sendMessage(body, attachments, quote, preview) { + async sendMessage( + body, + attachments, + quote, + preview, + groupInvitation = null + ) { this.clearTypingTimers(); const destination = this.id; @@ -1510,6 +1516,7 @@ } const attributes = { ...messageWithSchema, + group_invitation: groupInvitation, id: window.getGuid(), }; @@ -1589,6 +1596,8 @@ options.publicSendData = await this.getPublicSendData(); } + options.groupInvitation = groupInvitation; + const groupNumbers = this.getRecipients(); const promise = (() => { diff --git a/js/models/messages.js b/js/models/messages.js index 30a8be424..fec2bb975 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -104,6 +104,8 @@ this.propsForGroupNotification = this.getPropsForGroupNotification(); } else if (this.isFriendRequest()) { this.propsForFriendRequest = this.getPropsForFriendRequest(); + } else if (this.isGroupInvitation()) { + this.propsForGroupInvitation = this.getPropsForGroupInvitation(); } else { this.propsForSearchResult = this.getPropsForSearchResult(); this.propsForMessage = this.getPropsForMessage(); @@ -250,6 +252,9 @@ if (this.isIncoming() && this.hasErrors()) { return i18n('incomingError'); } + if (this.isGroupInvitation()) { + return `<${i18n('groupInvitation')}>`; + } return this.get('body'); }, isVerifiedChange() { @@ -261,6 +266,9 @@ isFriendRequest() { return this.get('type') === 'friend-request'; }, + isGroupInvitation() { + return !!this.get('group_invitation'); + }, getNotificationText() { const description = this.getDescription(); if (description) { @@ -438,6 +446,29 @@ onRetrySend, }; }, + getPropsForGroupInvitation() { + const invitation = this.get('group_invitation'); + + let direction = 'incoming'; + + if (this.get('direction')) { + direction = this.get('direction'); + } else if (this.get('type') === 'outgoing') { + direction = 'outgoing'; + } + + return { + serverName: invitation.serverName, + serverAddress: invitation.serverAddress, + direction, + onClick: () => { + Whisper.events.trigger( + 'invitationAccepted', + invitation.serverAddress + ); + }, + }; + }, findContact(phoneNumber) { return ConversationController.get(phoneNumber); }, @@ -1991,6 +2022,10 @@ } } + if (initialMessage.groupInvitation) { + message.set({ group_invitation: initialMessage.groupInvitation }); + } + const urls = window.Signal.LinkPreviews.findLinks(dataMessage.body); const incomingPreview = dataMessage.preview || []; const preview = incomingPreview.filter( @@ -2216,15 +2251,6 @@ } else if (message.get('type') !== 'outgoing') { // Ignore 'outgoing' messages because they are sync messages await sendingDeviceConversation.onFriendRequestAccepted(); - // We need to return for these types of messages because android struggles - if ( - !message.get('body') && - !message.get('attachments').length && - !message.get('preview').length && - !message.get('group_update') - ) { - return; - } } const id = await window.Signal.Data.saveMessage(message.attributes, { Message: Whisper.Message, diff --git a/js/modules/signal.js b/js/modules/signal.js index 91459bc65..3caa1cdca 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -52,6 +52,12 @@ const { EditProfileDialog } = require('../../ts/components/EditProfileDialog'); const { UpdateGroupDialog, } = require('../../ts/components/conversation/UpdateGroupDialog'); +const { + InviteFriendsDialog, +} = require('../../ts/components/conversation/InviteFriendsDialog'); +const { + GroupInvitation, +} = require('../../ts/components/conversation/GroupInvitation'); const { ConfirmDialog } = require('../../ts/components/ConfirmDialog'); const { MediaGallery, @@ -232,6 +238,8 @@ exports.setup = (options = {}) => { EditProfileDialog, ConfirmDialog, UpdateGroupDialog, + InviteFriendsDialog, + GroupInvitation, BulkEdit, MediaGallery, Message, diff --git a/js/views/app_view.js b/js/views/app_view.js index 5a0a8fc6f..b7e7a011d 100644 --- a/js/views/app_view.js +++ b/js/views/app_view.js @@ -251,5 +251,9 @@ const dialog = new Whisper.LeaveGroupDialogView(groupConvo); this.el.append(dialog.el); }, + showInviteFriendsDialog(groupConvo) { + const dialog = new Whisper.InviteFriendsDialogView(groupConvo); + this.el.append(dialog.el); + }, }); })(); diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 4ba95d709..09a580373 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -286,6 +286,10 @@ onLeaveGroup: () => { window.Whisper.events.trigger('leaveGroup', this.model); }, + + onInviteFriends: () => { + window.Whisper.events.trigger('inviteFriends', this.model); + }, }; }; this.titleView = new Whisper.ReactWrapperView({ diff --git a/js/views/invite_friends_dialog_view.js b/js/views/invite_friends_dialog_view.js new file mode 100644 index 000000000..4a8a1735b --- /dev/null +++ b/js/views/invite_friends_dialog_view.js @@ -0,0 +1,53 @@ +/* global Whisper */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.InviteFriendsDialogView = Whisper.View.extend({ + className: 'loki-dialog modal', + initialize(convo) { + this.close = this.close.bind(this); + this.submit = this.submit.bind(this); + + const convos = window.getConversations().models; + + const friends = convos.filter( + d => !!d && d.isFriend() && d.isPrivate() && !d.isMe() + ); + + this.friends = friends; + this.chatName = convo.get('name'); + this.chatServer = convo.get('server'); + + this.$el.focus(); + this.render(); + }, + render() { + const view = new Whisper.ReactWrapperView({ + className: 'invite-friends-dialog', + Component: window.Signal.Components.InviteFriendsDialog, + props: { + friendList: this.friends, + onSubmit: this.submit, + onClose: this.close, + chatName: this.chatName, + }, + }); + + this.$el.append(view.el); + return this; + }, + close() { + this.remove(); + }, + submit(pubkeys) { + window.sendGroupInvitations( + { address: this.chatServer, name: this.chatName }, + pubkeys + ); + }, + }); +})(); diff --git a/js/views/message_view.js b/js/views/message_view.js index 6da1ffaec..32d87a78b 100644 --- a/js/views/message_view.js +++ b/js/views/message_view.js @@ -74,6 +74,11 @@ Component: Components.FriendRequest, props: this.model.propsForFriendRequest, }; + } else if (this.model.propsForGroupInvitation) { + return { + Component: Components.GroupInvitation, + props: this.model.propsForGroupInvitation, + }; } return { diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index 902d100e3..81afbe085 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -27,6 +27,7 @@ function Message(options) { this.expireTimer = options.expireTimer; this.profileKey = options.profileKey; this.profile = options.profile; + this.groupInvitation = options.groupInvitation; if (!(this.recipients instanceof Array)) { throw new Error('Invalid recipient list'); @@ -160,6 +161,15 @@ Message.prototype = { proto.profile = profile; } + if (this.groupInvitation) { + proto.groupInvitation = new textsecure.protobuf.DataMessage.GroupInvitation( + { + serverAddress: this.groupInvitation.serverAddress, + serverName: this.groupInvitation.serverName, + } + ); + } + this.dataMessage = proto; return proto; }, @@ -404,7 +414,7 @@ MessageSender.prototype = { ); numbers.forEach(number => { - // Note: if we are sending a private group message, we make our best to + // Note: if we are sending a private group message, we do our best to // ensure we have signal protocol sessions with every member, but if we // fail, let's at least send messages to those members with which we do: const haveSession = _.some( @@ -941,6 +951,8 @@ MessageSender.prototype = { ? textsecure.protobuf.DataMessage.Flags.BACKGROUND_FRIEND_REQUEST : undefined; + const { groupInvitation } = options; + return this.sendMessage( { recipients: [number], @@ -954,6 +966,7 @@ MessageSender.prototype = { profileKey, profile, flags, + groupInvitation, }, options ); diff --git a/protos/SignalService.proto b/protos/SignalService.proto index f70505168..b2203a11f 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -200,6 +200,11 @@ message DataMessage { optional string avatar = 2; } + message GroupInvitation { + optional string serverAddress = 1; + optional string serverName = 2; + } + optional string body = 1; repeated AttachmentPointer attachments = 2; optional GroupContext group = 3; @@ -211,6 +216,7 @@ message DataMessage { repeated Contact contact = 9; repeated Preview preview = 10; optional LokiProfile profile = 101; // Loki: The profile of the current user + optional GroupInvitation groupInvitation = 102; // Loki: Invitation to a public chat } message NullMessage { diff --git a/stylesheets/_conversation.scss b/stylesheets/_conversation.scss index 8c5c8dd43..8008c2add 100644 --- a/stylesheets/_conversation.scss +++ b/stylesheets/_conversation.scss @@ -204,6 +204,80 @@ padding-top: 4px; } +.group-invitation-container { + display: flex; + flex-direction: column; +} + +.group-invitation { + background-color: #f4f4f0; + display: inline-block; + margin: 4px 16px; + padding: 4px; + + border: solid; + border-width: 0.5px; + border-radius: 4px; + border-color: #e0e0e0; + + align-self: flex-start; + + box-shadow: 2px 2px lightgrey; + + .title { + margin: 6px; + color: darkslategray; + font-variant-caps: all-small-caps; + user-select: none; + } + + .contents { + display: flex; + align-items: center; + margin: 6px; + + .invite-group-avatar { + height: 48px; + width: 48px; + } + + .group-details { + display: inline-flex; + flex-direction: column; + + padding: 8px; + + .group-name { + font-weight: lighter; + padding-bottom: 4px; + } + + .group-address { + color: grey; + } + } + + .join-btn { + background-color: #e0e0e0; + padding: 6px 10px; + margin-left: 6px; + border-radius: 6px; + box-shadow: 2px 2px 1px #c0c0c0; + color: #404040; + user-select: none; + cursor: pointer; + + &:hover { + background-color: #c7c7c7; + } + } + } +} + +.invitation-outgoing { + align-self: flex-end; +} + .message-selected { background-color: #60554060; } diff --git a/stylesheets/_mentions.scss b/stylesheets/_mentions.scss index b3d226c56..9eeea12f3 100644 --- a/stylesheets/_mentions.scss +++ b/stylesheets/_mentions.scss @@ -29,6 +29,7 @@ margin-left: 10px; } +.invite-friends-dialog, .create-group-dialog { .content { max-width: 100% !important; @@ -46,7 +47,9 @@ font-size: large; text-align: center; } +} +.create-group-dialog { .no-friends { text-align: center; } @@ -124,7 +127,8 @@ } .member-list-container, -.create-group-dialog { +.create-group-dialog, +.invite-friends-dialog { .member-item { padding: 4px; user-select: none; @@ -176,7 +180,8 @@ .dark-theme { .member-list-container, - .create-group-dialog { + .create-group-dialog, + .invite-friends-dialog { .member-item { &:hover:not(.member-selected) { background-color: $color-dark-55; diff --git a/test/index.html b/test/index.html index f8cae2a6e..cdeaea87e 100644 --- a/test/index.html +++ b/test/index.html @@ -576,7 +576,7 @@ - + diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx index 4e14a7350..d26d1fe08 100644 --- a/ts/components/ConversationListItem.tsx +++ b/ts/components/ConversationListItem.tsx @@ -42,6 +42,7 @@ export type PropsData = { hasNickname?: boolean; isFriendItem?: boolean; isSecondary?: boolean; + isGroupInvitation?: boolean; }; type PropsHousekeeping = { diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index e79181f1a..062e0aee2 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -67,6 +67,8 @@ interface Props { onUpdateGroup: () => void; onLeaveGroup: () => void; + onInviteFriends: () => void; + i18n: LocalizerType; } @@ -230,6 +232,7 @@ export class ConversationHeader extends React.Component { onCopyPublicKey, onUpdateGroup, onLeaveGroup, + onInviteFriends, } = this.props; const isPrivateGroup = isGroup && !isPublic; @@ -248,6 +251,9 @@ export class ConversationHeader extends React.Component { {i18n('leaveGroup')} ) : null} {/* TODO: add delete group */} + {isGroup && isPublic ? ( + {i18n('inviteFriends')} + ) : null} {!isMe && isClosable && !isPrivateGroup ? ( !isPublic ? ( diff --git a/ts/components/conversation/GroupInvitation.tsx b/ts/components/conversation/GroupInvitation.tsx new file mode 100644 index 000000000..b8b276f9c --- /dev/null +++ b/ts/components/conversation/GroupInvitation.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import classNames from 'classnames'; + +interface Props { + serverName: string; + serverAddress: string; + direction: string; + onClick: any; +} + +export class GroupInvitation extends React.Component { + public render() { + const classes = ['group-invitation']; + + if (this.props.direction === 'outgoing') { + classes.push('invitation-outgoing'); + } + + return ( +
+
+
Group invitation
+
+ group-avatar + + {this.props.serverName} + {this.props.serverAddress} + + + Join + +
+
+
+ ); + } +} diff --git a/ts/components/conversation/InviteFriendsDialog.tsx b/ts/components/conversation/InviteFriendsDialog.tsx new file mode 100644 index 000000000..fa502ea43 --- /dev/null +++ b/ts/components/conversation/InviteFriendsDialog.tsx @@ -0,0 +1,132 @@ +import React from 'react'; +import { Contact, MemberList } from './MemberList'; + +interface Props { + friendList: Array; + chatName: string; + onSubmit: any; + onClose: any; +} + +declare global { + interface Window { + i18n: any; + } +} + +interface State { + friendList: Array; +} + +export class InviteFriendsDialog extends React.Component { + constructor(props: any) { + super(props); + + this.onMemberClicked = this.onMemberClicked.bind(this); + this.closeDialog = this.closeDialog.bind(this); + this.onClickOK = this.onClickOK.bind(this); + this.onKeyUp = this.onKeyUp.bind(this); + + let friends = this.props.friendList; + friends = friends.map(d => { + const lokiProfile = d.getLokiProfile(); + const name = lokiProfile ? lokiProfile.displayName : 'Anonymous'; + + // TODO: should take existing members into account + const existingMember = false; + + return { + id: d.id, + authorPhoneNumber: d.id, + authorProfileName: name, + selected: false, + authorName: name, + authorColor: d.getColor(), + checkmarked: false, + existingMember, + }; + }); + + this.state = { + friendList: friends, + }; + + window.addEventListener('keyup', this.onKeyUp); + } + + public render() { + const titleText = `${window.i18n('add-friends')} ${this.props.chatName}`; + const cancelText = window.i18n('cancel'); + const okText = window.i18n('ok'); + + return ( +
+

{titleText}

+
+ +
+
+ + +
+
+ ); + } + + private onClickOK() { + const selectedFriends = this.state.friendList + .filter(d => d.checkmarked) + .map(d => d.id); + + if (selectedFriends.length > 0) { + this.props.onSubmit(selectedFriends); + } + + this.closeDialog(); + } + + private onKeyUp(event: any) { + switch (event.key) { + case 'Enter': + this.onClickOK(); + break; + case 'Esc': + case 'Escape': + this.closeDialog(); + break; + default: + } + } + + private closeDialog() { + window.removeEventListener('keyup', this.onKeyUp); + + this.props.onClose(); + } + + private onMemberClicked(selected: any) { + const updatedFriends = this.state.friendList.map(member => { + if (member.id === selected.id) { + return { ...member, checkmarked: !member.checkmarked }; + } else { + return member; + } + }); + + this.setState(state => { + return { + ...state, + friendList: updatedFriends, + }; + }); + } +} From 8ea82b14e3b96a222aa59bab745a42ed07c23a86 Mon Sep 17 00:00:00 2001 From: Maxim Shishmarev Date: Mon, 25 Nov 2019 10:02:24 +1100 Subject: [PATCH 2/4] Add channel id field to invitations --- js/background.js | 69 ++++++++++++++------------ js/models/messages.js | 3 +- js/views/invite_friends_dialog_view.js | 7 ++- libtextsecure/sendmessage.js | 1 + protos/SignalService.proto | 3 +- 5 files changed, 47 insertions(+), 36 deletions(-) diff --git a/js/background.js b/js/background.js index dda8f8199..9c8ac20ef 100644 --- a/js/background.js +++ b/js/background.js @@ -809,6 +809,7 @@ if (convo) { convo.sendMessage('', null, null, null, { serverName: serverInfo.name, + channelId: serverInfo.channelId, serverAddress: serverInfo.address, }); } @@ -833,43 +834,45 @@ } }); - Whisper.events.on('invitationAccepted', async serverAddress => { - // To some degree this has been copy-pasted - // form connection_to_server_dialog_view.js: - const channelId = 1; - const rawServerUrl = serverAddress - .replace(/^https?:\/\//i, '') - .replace(/[/\\]+$/i, ''); - const sslServerUrl = `https://${rawServerUrl}`; - const conversationId = `publicChat:${channelId}@${rawServerUrl}`; - - const conversationExists = ConversationController.get(conversationId); - if (conversationExists) { - window.log.warn('We are already a member of this public chat'); - return; - } + Whisper.events.on( + 'invitationAccepted', + async (serverAddress, channelId) => { + // To some degree this has been copy-pasted + // form connection_to_server_dialog_view.js: + const rawServerUrl = serverAddress + .replace(/^https?:\/\//i, '') + .replace(/[/\\]+$/i, ''); + const sslServerUrl = `https://${rawServerUrl}`; + const conversationId = `publicChat:${channelId}@${rawServerUrl}`; + + const conversationExists = ConversationController.get(conversationId); + if (conversationExists) { + window.log.warn('We are already a member of this public chat'); + return; + } - const serverAPI = await window.lokiPublicChatAPI.findOrCreateServer( - sslServerUrl - ); - if (!serverAPI) { - window.log.warn(`Could not connect to ${serverAddress}`); - return; - } + const serverAPI = await window.lokiPublicChatAPI.findOrCreateServer( + sslServerUrl + ); + if (!serverAPI) { + window.log.warn(`Could not connect to ${serverAddress}`); + return; + } - const conversation = await ConversationController.getOrCreateAndWait( - conversationId, - 'group' - ); + const conversation = await ConversationController.getOrCreateAndWait( + conversationId, + 'group' + ); - serverAPI.findOrCreateChannel(channelId, conversationId); - await conversation.setPublicSource(sslServerUrl, channelId); - await conversation.setFriendRequestStatus( - window.friends.friendRequestStatusEnum.friends - ); + serverAPI.findOrCreateChannel(channelId, conversationId); + await conversation.setPublicSource(sslServerUrl, channelId); + await conversation.setFriendRequestStatus( + window.friends.friendRequestStatusEnum.friends + ); - appView.openConversation(conversationId, {}); - }); + appView.openConversation(conversationId, {}); + } + ); Whisper.events.on('leaveGroup', async groupConvo => { if (appView) { diff --git a/js/models/messages.js b/js/models/messages.js index fec2bb975..4e558816f 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -464,7 +464,8 @@ onClick: () => { Whisper.events.trigger( 'invitationAccepted', - invitation.serverAddress + invitation.serverAddress, + invitation.channelId ); }, }; diff --git a/js/views/invite_friends_dialog_view.js b/js/views/invite_friends_dialog_view.js index 4a8a1735b..06d59e9df 100644 --- a/js/views/invite_friends_dialog_view.js +++ b/js/views/invite_friends_dialog_view.js @@ -21,6 +21,7 @@ this.friends = friends; this.chatName = convo.get('name'); this.chatServer = convo.get('server'); + this.channelId = convo.get('channelId'); this.$el.focus(); this.render(); @@ -45,7 +46,11 @@ }, submit(pubkeys) { window.sendGroupInvitations( - { address: this.chatServer, name: this.chatName }, + { + address: this.chatServer, + name: this.chatName, + channelId: this.channelId, + }, pubkeys ); }, diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index 81afbe085..a021fd9df 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -165,6 +165,7 @@ Message.prototype = { proto.groupInvitation = new textsecure.protobuf.DataMessage.GroupInvitation( { serverAddress: this.groupInvitation.serverAddress, + channelId: this.groupInvitation.channelId, serverName: this.groupInvitation.serverName, } ); diff --git a/protos/SignalService.proto b/protos/SignalService.proto index b2203a11f..9c0000ede 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -202,7 +202,8 @@ message DataMessage { message GroupInvitation { optional string serverAddress = 1; - optional string serverName = 2; + optional string channelId = 2; + optional string serverName = 3; } optional string body = 1; From 56bb2ed7920eded8a7877e588e75f008acd40626 Mon Sep 17 00:00:00 2001 From: Maxim Shishmarev Date: Mon, 25 Nov 2019 10:06:08 +1100 Subject: [PATCH 3/4] Address review comments --- js/models/messages.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/js/models/messages.js b/js/models/messages.js index 4e558816f..a63aa42f4 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -449,12 +449,9 @@ getPropsForGroupInvitation() { const invitation = this.get('group_invitation'); - let direction = 'incoming'; - - if (this.get('direction')) { - direction = this.get('direction'); - } else if (this.get('type') === 'outgoing') { - direction = 'outgoing'; + let direction = this.get('direction'); + if (!direction) { + direction = this.get('type') === 'outgoing' ? 'outgoing' : 'incoming'; } return { From 89e5e919f27fc0e2a7f7095e5a667f4bfdf75432 Mon Sep 17 00:00:00 2001 From: Maxim Shishmarev Date: Mon, 25 Nov 2019 12:02:03 +1100 Subject: [PATCH 4/4] Address more review comments --- _locales/en/messages.json | 2 +- js/background.js | 2 +- js/models/conversations.js | 2 +- js/models/messages.js | 9 +++++---- protos/SignalService.proto | 2 +- ts/components/conversation/InviteFriendsDialog.tsx | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 3807a75f8..9e1a9346c 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2236,7 +2236,7 @@ "groupInvitation": { "message": "Group Invitation" }, - "add-friends": { + "addingFriends": { "message": "Adding friends to" } } diff --git a/js/background.js b/js/background.js index 9c8ac20ef..32c207f7f 100644 --- a/js/background.js +++ b/js/background.js @@ -835,7 +835,7 @@ }); Whisper.events.on( - 'invitationAccepted', + 'publicChatInvitationAccepted', async (serverAddress, channelId) => { // To some degree this has been copy-pasted // form connection_to_server_dialog_view.js: diff --git a/js/models/conversations.js b/js/models/conversations.js index b2fde98ee..96df55e7a 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -1516,7 +1516,7 @@ } const attributes = { ...messageWithSchema, - group_invitation: groupInvitation, + groupInvitation, id: window.getGuid(), }; diff --git a/js/models/messages.js b/js/models/messages.js index a63aa42f4..9a80b428a 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -267,7 +267,7 @@ return this.get('type') === 'friend-request'; }, isGroupInvitation() { - return !!this.get('group_invitation'); + return !!this.get('groupInvitation'); }, getNotificationText() { const description = this.getDescription(); @@ -447,7 +447,7 @@ }; }, getPropsForGroupInvitation() { - const invitation = this.get('group_invitation'); + const invitation = this.get('groupInvitation'); let direction = this.get('direction'); if (!direction) { @@ -460,7 +460,7 @@ direction, onClick: () => { Whisper.events.trigger( - 'invitationAccepted', + 'publicChatInvitationAccepted', invitation.serverAddress, invitation.channelId ); @@ -1938,6 +1938,7 @@ window.log.info( `Starting handleDataMessage for message ${message.idForLogging()} in conversation ${conversation.idForLogging()}` ); + const withQuoteReference = await this.copyFromQuotedMessage( initialMessage ); @@ -2021,7 +2022,7 @@ } if (initialMessage.groupInvitation) { - message.set({ group_invitation: initialMessage.groupInvitation }); + message.set({ groupInvitation: initialMessage.groupInvitation }); } const urls = window.Signal.LinkPreviews.findLinks(dataMessage.body); diff --git a/protos/SignalService.proto b/protos/SignalService.proto index 9c0000ede..0f78ebc05 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -202,7 +202,7 @@ message DataMessage { message GroupInvitation { optional string serverAddress = 1; - optional string channelId = 2; + optional uint32 channelId = 2; optional string serverName = 3; } diff --git a/ts/components/conversation/InviteFriendsDialog.tsx b/ts/components/conversation/InviteFriendsDialog.tsx index fa502ea43..a624dc118 100644 --- a/ts/components/conversation/InviteFriendsDialog.tsx +++ b/ts/components/conversation/InviteFriendsDialog.tsx @@ -55,7 +55,7 @@ export class InviteFriendsDialog extends React.Component { } public render() { - const titleText = `${window.i18n('add-friends')} ${this.props.chatName}`; + const titleText = `${window.i18n('addingFriends')} ${this.props.chatName}`; const cancelText = window.i18n('cancel'); const okText = window.i18n('ok');