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:
parent
77f8c48dc4
commit
dc44b9ef59
4 changed files with 141 additions and 41 deletions
|
@ -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'}
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
|
107
src/stores/ThreepidInviteStore.ts
Normal file
107
src/stores/ThreepidInviteStore.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue