Store and thread 3pid invite through the app

This doesn't do anything with the stored value (yet), but enables us to do something with it in a future commit.
This commit is contained in:
Travis Ralston 2020-09-11 19:49:48 -06:00
parent 77f8c48dc4
commit dc44b9ef59
4 changed files with 141 additions and 41 deletions

View file

@ -56,6 +56,7 @@ import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPay
import RoomListStore from "../../stores/room-list/RoomListStore"; import RoomListStore from "../../stores/room-list/RoomListStore";
import NonUrgentToastContainer from "./NonUrgentToastContainer"; import NonUrgentToastContainer from "./NonUrgentToastContainer";
import { ToggleRightPanelPayload } from "../../dispatcher/payloads/ToggleRightPanelPayload"; 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) // 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. // 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 // eslint-disable-next-line camelcase
page_type: string; page_type: string;
autoJoin: boolean; autoJoin: boolean;
thirdPartyInvite?: object; threepidInvite?: IThreepidInvite;
roomOobData?: object; roomOobData?: object;
currentRoomId: string; currentRoomId: string;
ConferenceHandler?: object; ConferenceHandler?: object;
@ -631,7 +632,7 @@ class LoggedInView extends React.Component<IProps, IState> {
ref={this._roomView} ref={this._roomView}
autoJoin={this.props.autoJoin} autoJoin={this.props.autoJoin}
onRegistered={this.props.onRegistered} onRegistered={this.props.onRegistered}
thirdPartyInvite={this.props.thirdPartyInvite} threepidInvite={this.props.threepidInvite}
oobData={this.props.roomOobData} oobData={this.props.roomOobData}
viaServers={this.props.viaServers} viaServers={this.props.viaServers}
key={this.props.currentRoomId || 'roomview'} key={this.props.currentRoomId || 'roomview'}

View file

@ -78,6 +78,7 @@ import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotif
import { SettingLevel } from "../../settings/SettingLevel"; import { SettingLevel } from "../../settings/SettingLevel";
import { leaveRoomBehaviour } from "../../utils/membership"; import { leaveRoomBehaviour } from "../../utils/membership";
import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog"; import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog";
import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore";
/** constants for MatrixChat.state.view */ /** constants for MatrixChat.state.view */
export enum Views { export enum Views {
@ -137,9 +138,9 @@ interface IRoomInfo {
auto_join?: boolean; auto_join?: boolean;
highlighted?: boolean; highlighted?: boolean;
third_party_invite?: object;
oob_data?: object; oob_data?: object;
via_servers?: string[]; via_servers?: string[];
threepid_invite?: IThreepidInvite;
} }
/* eslint-enable camelcase */ /* eslint-enable camelcase */
@ -196,7 +197,7 @@ interface IState {
resizeNotifier: ResizeNotifier; resizeNotifier: ResizeNotifier;
serverConfig?: ValidatedServerConfig; serverConfig?: ValidatedServerConfig;
ready: boolean; ready: boolean;
thirdPartyInvite?: object; threepidInvite?: IThreepidInvite,
roomOobData?: object; roomOobData?: object;
viaServers?: string[]; viaServers?: string[];
pendingInitialSync?: boolean; pendingInitialSync?: boolean;
@ -260,6 +261,14 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// outside this.state because updating it should never trigger a // outside this.state because updating it should never trigger a
// rerender. // rerender.
this.screenAfterLogin = this.props.initialScreenAfterLogin; 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.windowWidth = 10000;
this.handleResize(); this.handleResize();
@ -835,10 +844,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// context of that particular event. // context of that particular event.
// @param {boolean=} roomInfo.highlighted If true, add event_id to the hash of the URL // @param {boolean=} roomInfo.highlighted If true, add event_id to the hash of the URL
// and alter the EventTile to appear highlighted. // and alter the EventTile to appear highlighted.
// @param {Object=} roomInfo.third_party_invite Object containing data about the third party // @param {Object=} roomInfo.threepid_invite Object containing data about the third party
// we received to join the room, if any. // 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.oob_data Object of additional data about the room // @param {Object=} roomInfo.oob_data Object of additional data about the room
// that has been passed out-of-band (eg. // that has been passed out-of-band (eg.
// room name and avatar from an invite email) // room name and avatar from an invite email)
@ -896,7 +903,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
view: Views.LOGGED_IN, view: Views.LOGGED_IN,
currentRoomId: roomInfo.room_id || null, currentRoomId: roomInfo.room_id || null,
page_type: PageTypes.RoomView, page_type: PageTypes.RoomView,
thirdPartyInvite: roomInfo.third_party_invite, threepidInvite: roomInfo.threepid_invite,
roomOobData: roomInfo.oob_data, roomOobData: roomInfo.oob_data,
viaServers: roomInfo.via_servers, viaServers: roomInfo.via_servers,
ready: true, ready: true,
@ -1639,16 +1646,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// TODO: Handle encoded room/event IDs: https://github.com/vector-im/element-web/issues/9149 // TODO: Handle encoded room/event IDs: https://github.com/vector-im/element-web/issues/9149
// FIXME: sort_out caseConsistency let threepidInvite: IThreepidInvite;
const thirdPartyInvite = { if (params.signurl && params.email) {
inviteSignUrl: params.signurl, threepidInvite = ThreepidInviteStore.instance
invitedEmail: params.email, .storeInvite(roomString, params as IThreepidInviteWireFormat);
}; }
const oobData = {
name: params.room_name,
avatarUrl: params.room_avatar_url,
inviterName: params.inviter_name,
};
// on our URLs there might be a ?via=matrix.org or similar to help // 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 // joins to the room succeed. We'll pass these through as an array
@ -1669,8 +1671,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// it as highlighted, which will propagate to RoomView and highlight the // it as highlighted, which will propagate to RoomView and highlight the
// associated EventTile. // associated EventTile.
highlighted: Boolean(eventId), highlighted: Boolean(eventId),
third_party_invite: thirdPartyInvite, threepid_invite: threepidInvite,
oob_data: oobData, oob_data: {
name: threepidInvite?.roomName,
avatarUrl: threepidInvite?.roomAvatarUrl,
inviterName: threepidInvite?.inviterName,
},
room_alias: undefined, room_alias: undefined,
room_id: undefined, room_id: undefined,
}; };

View file

@ -72,6 +72,7 @@ import RoomHeader from "../views/rooms/RoomHeader";
import TintableSvg from "../views/elements/TintableSvg"; import TintableSvg from "../views/elements/TintableSvg";
import type * as ConferenceHandler from '../../VectorConferenceHandler'; import type * as ConferenceHandler from '../../VectorConferenceHandler';
import {XOR} from "../../@types/common"; import {XOR} from "../../@types/common";
import { IThreepidInvite } from "../../stores/ThreepidInviteStore";
const DEBUG = false; const DEBUG = false;
let debuglog = function(msg: string) {}; let debuglog = function(msg: string) {};
@ -86,15 +87,7 @@ if (DEBUG) {
interface IProps { interface IProps {
ConferenceHandler?: ConferenceHandler; ConferenceHandler?: ConferenceHandler;
// An object representing a third party invite to join this room threepidInvite: IThreepidInvite,
// 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;
};
// Any data about the room that would normally come from the homeserver // 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 // 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<IProps, IState> {
// return; // return;
} else { } else {
Promise.resolve().then(() => { Promise.resolve().then(() => {
const signUrl = this.props.thirdPartyInvite ? const signUrl = this.props.threepidInvite?.signUrl;
this.props.thirdPartyInvite.inviteSignUrl : undefined;
dis.dispatch({ dis.dispatch({
action: 'join_room', action: 'join_room',
opts: { inviteSignUrl: signUrl, viaServers: this.props.viaServers }, opts: { inviteSignUrl: signUrl, viaServers: this.props.viaServers },
@ -1752,10 +1744,7 @@ export default class RoomView extends React.Component<IProps, IState> {
if (this.props.oobData) { if (this.props.oobData) {
inviterName = this.props.oobData.inviterName; inviterName = this.props.oobData.inviterName;
} }
let invitedEmail = undefined; const invitedEmail = this.props.threepidInvite?.toEmail;
if (this.props.thirdPartyInvite) {
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
}
// We have no room object for this room, only the ID. // 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. // 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<IProps, IState> {
inviterName={inviterName} inviterName={inviterName}
invitedEmail={invitedEmail} invitedEmail={invitedEmail}
oobData={this.props.oobData} oobData={this.props.oobData}
signUrl={this.props.thirdPartyInvite ? this.props.thirdPartyInvite.inviteSignUrl : null} signUrl={this.props.threepidInvite?.signUrl}
room={this.state.room} room={this.state.room}
/> />
</ErrorBoundary> </ErrorBoundary>
@ -1907,10 +1896,7 @@ export default class RoomView extends React.Component<IProps, IState> {
if (this.props.oobData) { if (this.props.oobData) {
inviterName = this.props.oobData.inviterName; inviterName = this.props.oobData.inviterName;
} }
let invitedEmail = undefined; const invitedEmail = this.props.threepidInvite?.toEmail;
if (this.props.thirdPartyInvite) {
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
}
hideCancel = true; hideCancel = true;
previewBar = ( previewBar = (
<RoomPreviewBar <RoomPreviewBar

View file

@ -0,0 +1,107 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import EventEmitter from "events";
import { ComponentClass } from "../@types/common";
import { UPDATE_EVENT } from "./AsyncStore";
import { base32, base64 } from "rfc4648";
// Dev note: the interface is split in two so we don't have to disable the
// linter across the whole project.
export interface IThreepidInviteWireFormat {
email: string;
signurl: string;
room_name: string;
room_avatar_url: string;
inviter_name: string;
// TODO: Figure out if these are ever populated
guest_access_token?: string;
guest_user_id?: string;
}
interface IPersistedThreepidInvite extends IThreepidInviteWireFormat {
roomId: string;
}
export interface IThreepidInvite {
id: string; // generated by us
roomId: string;
toEmail: string;
signUrl: string;
roomName: string;
roomAvatarUrl: string;
inviterName: string;
}
const STORAGE_PREFIX = "mx_threepid_invite_";
export default class ThreepidInviteStore extends EventEmitter {
private static _instance: ThreepidInviteStore;
public static get instance(): ThreepidInviteStore {
if (!ThreepidInviteStore._instance) {
ThreepidInviteStore._instance = new ThreepidInviteStore();
}
return ThreepidInviteStore._instance;
}
public storeInvite(roomId: string, wireInvite: IThreepidInviteWireFormat): IThreepidInvite {
console.log("Storing invite: ", {roomId, ...wireInvite});
const invite = <IPersistedThreepidInvite>{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,
};
}
}