diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 1ac15caa4c..81b8da2cad 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -56,6 +56,7 @@ import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPay import RoomListStore from "../../stores/room-list/RoomListStore"; import NonUrgentToastContainer from "./NonUrgentToastContainer"; import { ToggleRightPanelPayload } from "../../dispatcher/payloads/ToggleRightPanelPayload"; +import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. @@ -81,7 +82,7 @@ interface IProps { // eslint-disable-next-line camelcase page_type: string; autoJoin: boolean; - thirdPartyInvite?: object; + threepidInvite?: IThreepidInvite; roomOobData?: object; currentRoomId: string; ConferenceHandler?: object; @@ -631,7 +632,7 @@ class LoggedInView extends React.Component { ref={this._roomView} autoJoin={this.props.autoJoin} onRegistered={this.props.onRegistered} - thirdPartyInvite={this.props.thirdPartyInvite} + threepidInvite={this.props.threepidInvite} oobData={this.props.roomOobData} viaServers={this.props.viaServers} key={this.props.currentRoomId || 'roomview'} diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index d3d5835dae..13e0c21858 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -78,6 +78,7 @@ import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotif import { SettingLevel } from "../../settings/SettingLevel"; import { leaveRoomBehaviour } from "../../utils/membership"; import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog"; +import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore"; /** constants for MatrixChat.state.view */ export enum Views { @@ -137,9 +138,9 @@ interface IRoomInfo { auto_join?: boolean; highlighted?: boolean; - third_party_invite?: object; oob_data?: object; via_servers?: string[]; + threepid_invite?: IThreepidInvite; } /* eslint-enable camelcase */ @@ -196,7 +197,7 @@ interface IState { resizeNotifier: ResizeNotifier; serverConfig?: ValidatedServerConfig; ready: boolean; - thirdPartyInvite?: object; + threepidInvite?: IThreepidInvite, roomOobData?: object; viaServers?: string[]; pendingInitialSync?: boolean; @@ -260,6 +261,14 @@ export default class MatrixChat extends React.PureComponent { // outside this.state because updating it should never trigger a // rerender. this.screenAfterLogin = this.props.initialScreenAfterLogin; + if (this.screenAfterLogin) { + const params = this.screenAfterLogin.params || {}; + if (this.screenAfterLogin.screen.startsWith("room/") && params['signurl'] && params['email']) { + // probably a threepid invite - try to store it + const roomId = this.screenAfterLogin.screen.substring("room/".length); + ThreepidInviteStore.instance.storeInvite(roomId, params as IThreepidInviteWireFormat); + } + } this.windowWidth = 10000; this.handleResize(); @@ -835,10 +844,8 @@ export default class MatrixChat extends React.PureComponent { // context of that particular event. // @param {boolean=} roomInfo.highlighted If true, add event_id to the hash of the URL // and alter the EventTile to appear highlighted. - // @param {Object=} roomInfo.third_party_invite Object containing data about the third party - // we received to join the room, if any. - // @param {string=} roomInfo.third_party_invite.inviteSignUrl 3pid invite sign URL - // @param {string=} roomInfo.third_party_invite.invitedEmail The email address the invite was sent to + // @param {Object=} roomInfo.threepid_invite Object containing data about the third party + // we received to join the room, if any. // @param {Object=} roomInfo.oob_data Object of additional data about the room // that has been passed out-of-band (eg. // room name and avatar from an invite email) @@ -896,7 +903,7 @@ export default class MatrixChat extends React.PureComponent { view: Views.LOGGED_IN, currentRoomId: roomInfo.room_id || null, page_type: PageTypes.RoomView, - thirdPartyInvite: roomInfo.third_party_invite, + threepidInvite: roomInfo.threepid_invite, roomOobData: roomInfo.oob_data, viaServers: roomInfo.via_servers, ready: true, @@ -1639,16 +1646,11 @@ export default class MatrixChat extends React.PureComponent { // TODO: Handle encoded room/event IDs: https://github.com/vector-im/element-web/issues/9149 - // FIXME: sort_out caseConsistency - const thirdPartyInvite = { - inviteSignUrl: params.signurl, - invitedEmail: params.email, - }; - const oobData = { - name: params.room_name, - avatarUrl: params.room_avatar_url, - inviterName: params.inviter_name, - }; + let threepidInvite: IThreepidInvite; + if (params.signurl && params.email) { + threepidInvite = ThreepidInviteStore.instance + .storeInvite(roomString, params as IThreepidInviteWireFormat); + } // on our URLs there might be a ?via=matrix.org or similar to help // joins to the room succeed. We'll pass these through as an array @@ -1669,8 +1671,12 @@ export default class MatrixChat extends React.PureComponent { // it as highlighted, which will propagate to RoomView and highlight the // associated EventTile. highlighted: Boolean(eventId), - third_party_invite: thirdPartyInvite, - oob_data: oobData, + threepid_invite: threepidInvite, + oob_data: { + name: threepidInvite?.roomName, + avatarUrl: threepidInvite?.roomAvatarUrl, + inviterName: threepidInvite?.inviterName, + }, room_alias: undefined, room_id: undefined, }; diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 738042c680..039d36a8de 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -72,6 +72,7 @@ import RoomHeader from "../views/rooms/RoomHeader"; import TintableSvg from "../views/elements/TintableSvg"; import type * as ConferenceHandler from '../../VectorConferenceHandler'; import {XOR} from "../../@types/common"; +import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -86,15 +87,7 @@ if (DEBUG) { interface IProps { ConferenceHandler?: ConferenceHandler; - // An object representing a third party invite to join this room - // Fields: - // * inviteSignUrl (string) The URL used to join this room from an email invite - // (given as part of the link in the invite email) - // * invitedEmail (string) The email address that was invited to this room - thirdPartyInvite?: { - inviteSignUrl: string; - invitedEmail: string; - }; + threepidInvite: IThreepidInvite, // Any data about the room that would normally come from the homeserver // but has been passed out-of-band, eg. the room name and avatar URL @@ -1178,8 +1171,7 @@ export default class RoomView extends React.Component { // return; } else { Promise.resolve().then(() => { - const signUrl = this.props.thirdPartyInvite ? - this.props.thirdPartyInvite.inviteSignUrl : undefined; + const signUrl = this.props.threepidInvite?.signUrl; dis.dispatch({ action: 'join_room', opts: { inviteSignUrl: signUrl, viaServers: this.props.viaServers }, @@ -1752,10 +1744,7 @@ export default class RoomView extends React.Component { if (this.props.oobData) { inviterName = this.props.oobData.inviterName; } - let invitedEmail = undefined; - if (this.props.thirdPartyInvite) { - invitedEmail = this.props.thirdPartyInvite.invitedEmail; - } + const invitedEmail = this.props.threepidInvite?.toEmail; // We have no room object for this room, only the ID. // We've got to this room by following a link, possibly a third party invite. @@ -1773,7 +1762,7 @@ export default class RoomView extends React.Component { inviterName={inviterName} invitedEmail={invitedEmail} oobData={this.props.oobData} - signUrl={this.props.thirdPartyInvite ? this.props.thirdPartyInvite.inviteSignUrl : null} + signUrl={this.props.threepidInvite?.signUrl} room={this.state.room} /> @@ -1907,10 +1896,7 @@ export default class RoomView extends React.Component { if (this.props.oobData) { inviterName = this.props.oobData.inviterName; } - let invitedEmail = undefined; - if (this.props.thirdPartyInvite) { - invitedEmail = this.props.thirdPartyInvite.invitedEmail; - } + const invitedEmail = this.props.threepidInvite?.toEmail; hideCancel = true; previewBar = ( {roomId, ...wireInvite}; + const id = this.generateIdOf(invite); + localStorage.setItem(`${STORAGE_PREFIX}${id}`, JSON.stringify(invite)); + return this.translateInvite(invite); + } + + public getInvites(): IThreepidInvite[] { + const result: IThreepidInvite[] = []; + for (let i = 0; i < localStorage.length; i++) { + const keyName = localStorage.key(i); + if (!keyName.startsWith(STORAGE_PREFIX)) continue; + + const persisted = JSON.parse(localStorage.getItem(keyName)) as IPersistedThreepidInvite; + result.push(this.translateInvite(persisted)); + } + return result; + } + + // Currently Element can only handle one invite at a time, so handle that + public pickBestInvite(): IThreepidInvite { + return this.getInvites()[0]; + } + + public resolveInvite(invite: IThreepidInvite) { + localStorage.removeItem(`${STORAGE_PREFIX}${invite.id}`); + } + + private generateIdOf(persisted: IPersistedThreepidInvite): string { + // Use a consistent "hash" to form an ID. + return base32.stringify(Buffer.from(JSON.stringify(persisted))); + } + + private translateInvite(persisted: IPersistedThreepidInvite): IThreepidInvite { + return { + id: this.generateIdOf(persisted), + roomId: persisted.roomId, + toEmail: persisted.email, + signUrl: persisted.signurl, + roomName: persisted.room_name, + roomAvatarUrl: persisted.room_avatar_url, + inviterName: persisted.inviter_name, + }; + } +}