Merge pull request #5201 from matrix-org/travis/3pid-invites
Tactical improvements to 3PID invites
This commit is contained in:
commit
3d9c520af8
6 changed files with 192 additions and 48 deletions
|
@ -42,6 +42,7 @@ import {Mjolnir} from "./mjolnir/Mjolnir";
|
||||||
import DeviceListener from "./DeviceListener";
|
import DeviceListener from "./DeviceListener";
|
||||||
import {Jitsi} from "./widgets/Jitsi";
|
import {Jitsi} from "./widgets/Jitsi";
|
||||||
import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "./BasePlatform";
|
import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "./BasePlatform";
|
||||||
|
import ThreepidInviteStore from "./stores/ThreepidInviteStore";
|
||||||
|
|
||||||
const HOMESERVER_URL_KEY = "mx_hs_url";
|
const HOMESERVER_URL_KEY = "mx_hs_url";
|
||||||
const ID_SERVER_URL_KEY = "mx_is_url";
|
const ID_SERVER_URL_KEY = "mx_is_url";
|
||||||
|
@ -666,17 +667,30 @@ export async function onLoggedOut() {
|
||||||
// that can occur when components try to use a null client.
|
// that can occur when components try to use a null client.
|
||||||
dis.dispatch({action: 'on_logged_out'}, true);
|
dis.dispatch({action: 'on_logged_out'}, true);
|
||||||
stopMatrixClient();
|
stopMatrixClient();
|
||||||
await _clearStorage();
|
await _clearStorage({deleteEverything: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param {object} opts Options for how to clear storage.
|
||||||
* @returns {Promise} promise which resolves once the stores have been cleared
|
* @returns {Promise} promise which resolves once the stores have been cleared
|
||||||
*/
|
*/
|
||||||
async function _clearStorage() {
|
async function _clearStorage(opts: {deleteEverything: boolean}) {
|
||||||
Analytics.disable();
|
Analytics.disable();
|
||||||
|
|
||||||
if (window.localStorage) {
|
if (window.localStorage) {
|
||||||
|
// try to save any 3pid invites from being obliterated
|
||||||
|
const pendingInvites = ThreepidInviteStore.instance.getWireInvites();
|
||||||
|
|
||||||
window.localStorage.clear();
|
window.localStorage.clear();
|
||||||
|
|
||||||
|
// now restore those invites
|
||||||
|
if (!opts?.deleteEverything) {
|
||||||
|
pendingInvites.forEach(i => {
|
||||||
|
const roomId = i.roomId;
|
||||||
|
delete i.roomId; // delete to avoid confusing the store
|
||||||
|
ThreepidInviteStore.instance.storeInvite(roomId, i);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.sessionStorage) {
|
if (window.sessionStorage) {
|
||||||
|
|
|
@ -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();
|
||||||
|
@ -404,9 +413,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
});
|
});
|
||||||
}).then((loadedSession) => {
|
}).then((loadedSession) => {
|
||||||
if (!loadedSession) {
|
if (!loadedSession) {
|
||||||
// fall back to showing the welcome screen
|
// fall back to showing the welcome screen... unless we have a 3pid invite pending
|
||||||
|
if (ThreepidInviteStore.instance.pickBestInvite()) {
|
||||||
|
dis.dispatch({action: 'start_registration'});
|
||||||
|
} else {
|
||||||
dis.dispatch({action: "view_welcome_page"});
|
dis.dispatch({action: "view_welcome_page"});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Note we don't catch errors from this: we catch everything within
|
// Note we don't catch errors from this: we catch everything within
|
||||||
// loadSession as there's logic there to ask the user if they want
|
// loadSession as there's logic there to ask the user if they want
|
||||||
|
@ -835,10 +848,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 +907,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,
|
||||||
|
@ -1203,6 +1214,14 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// the homepage.
|
// the homepage.
|
||||||
dis.dispatch({action: 'view_home_page'});
|
dis.dispatch({action: 'view_home_page'});
|
||||||
}
|
}
|
||||||
|
} else if (ThreepidInviteStore.instance.pickBestInvite()) {
|
||||||
|
// The user has a 3pid invite pending - show them that
|
||||||
|
const threepidInvite = ThreepidInviteStore.instance.pickBestInvite();
|
||||||
|
|
||||||
|
// HACK: This is a pretty brutal way of threading the invite back through
|
||||||
|
// our systems, but it's the safest we have for now.
|
||||||
|
const params = ThreepidInviteStore.instance.translateToWireFormat(threepidInvite);
|
||||||
|
this.showScreen(`room/${threepidInvite.roomId}`, params)
|
||||||
} else {
|
} else {
|
||||||
// The user has just logged in after registering,
|
// The user has just logged in after registering,
|
||||||
// so show the homepage.
|
// so show the homepage.
|
||||||
|
@ -1639,16 +1658,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 +1683,15 @@ 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,
|
// TODO: Replace oob_data with the threepidInvite (which has the same info).
|
||||||
|
// This isn't done yet because it's threaded through so many more places.
|
||||||
|
// See https://github.com/vector-im/element-web/issues/15157
|
||||||
|
oob_data: {
|
||||||
|
name: threepidInvite?.roomName,
|
||||||
|
avatarUrl: threepidInvite?.roomAvatarUrl,
|
||||||
|
inviterName: threepidInvite?.inviterName,
|
||||||
|
},
|
||||||
room_alias: undefined,
|
room_alias: undefined,
|
||||||
room_id: undefined,
|
room_id: undefined,
|
||||||
};
|
};
|
||||||
|
@ -2002,12 +2023,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
view = <Welcome />;
|
view = <Welcome />;
|
||||||
} else if (this.state.view === Views.REGISTER) {
|
} else if (this.state.view === Views.REGISTER) {
|
||||||
const Registration = sdk.getComponent('structures.auth.Registration');
|
const Registration = sdk.getComponent('structures.auth.Registration');
|
||||||
|
const email = ThreepidInviteStore.instance.pickBestInvite()?.toEmail;
|
||||||
view = (
|
view = (
|
||||||
<Registration
|
<Registration
|
||||||
clientSecret={this.state.register_client_secret}
|
clientSecret={this.state.register_client_secret}
|
||||||
sessionId={this.state.register_session_id}
|
sessionId={this.state.register_session_id}
|
||||||
idSid={this.state.register_id_sid}
|
idSid={this.state.register_id_sid}
|
||||||
email={this.props.startingFragmentQueryParams.email}
|
email={email}
|
||||||
brand={this.props.config.brand}
|
brand={this.props.config.brand}
|
||||||
makeRegistrationUrl={this.makeRegistrationUrl}
|
makeRegistrationUrl={this.makeRegistrationUrl}
|
||||||
onLoggedIn={this.onRegisterFlowComplete}
|
onLoggedIn={this.onRegisterFlowComplete}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -25,6 +25,7 @@ import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
import Spinner from "../elements/Spinner";
|
||||||
|
|
||||||
/* This file contains a collection of components which are used by the
|
/* This file contains a collection of components which are used by the
|
||||||
* InteractiveAuth to prompt the user to enter the information needed
|
* InteractiveAuth to prompt the user to enter the information needed
|
||||||
|
@ -404,8 +405,12 @@ export class EmailIdentityAuthEntry extends React.Component {
|
||||||
// the validation link, we won't know the email address, so if we don't have it,
|
// the validation link, we won't know the email address, so if we don't have it,
|
||||||
// assume that the link has been clicked and the server will realise when we poll.
|
// assume that the link has been clicked and the server will realise when we poll.
|
||||||
if (this.props.inputs.emailAddress === undefined) {
|
if (this.props.inputs.emailAddress === undefined) {
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
return <Spinner />;
|
||||||
return <Loader />;
|
} else if (this.props.stageState?.emailSid) {
|
||||||
|
// we only have a session ID if the user has clicked the link in their email,
|
||||||
|
// so show a loading state instead of "an email has been sent to..." because
|
||||||
|
// that's confusing when you've already read that email.
|
||||||
|
return <Spinner />;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
116
src/stores/ThreepidInviteStore.ts
Normal file
116
src/stores/ThreepidInviteStore.ts
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
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 { base32 } 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; // eslint-disable-line camelcase
|
||||||
|
room_avatar_url: string; // eslint-disable-line camelcase
|
||||||
|
inviter_name: string; // eslint-disable-line camelcase
|
||||||
|
|
||||||
|
// TODO: Figure out if these are ever populated
|
||||||
|
guest_access_token?: string; // eslint-disable-line camelcase
|
||||||
|
guest_user_id?: string; // eslint-disable-line camelcase
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
const invite = <IPersistedThreepidInvite>{roomId, ...wireInvite};
|
||||||
|
const id = this.generateIdOf(invite);
|
||||||
|
localStorage.setItem(`${STORAGE_PREFIX}${id}`, JSON.stringify(invite));
|
||||||
|
return this.translateInvite(invite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getWireInvites(): IPersistedThreepidInvite[] {
|
||||||
|
const results: IPersistedThreepidInvite[] = [];
|
||||||
|
for (let i = 0; i < localStorage.length; i++) {
|
||||||
|
const keyName = localStorage.key(i);
|
||||||
|
if (!keyName.startsWith(STORAGE_PREFIX)) continue;
|
||||||
|
results.push(JSON.parse(localStorage.getItem(keyName)) as IPersistedThreepidInvite);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getInvites(): IThreepidInvite[] {
|
||||||
|
return this.getWireInvites().map(i => this.translateInvite(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public translateToWireFormat(invite: IThreepidInvite): IThreepidInviteWireFormat {
|
||||||
|
return {
|
||||||
|
email: invite.toEmail,
|
||||||
|
signurl: invite.signUrl,
|
||||||
|
room_name: invite.roomName,
|
||||||
|
room_avatar_url: invite.roomAvatarUrl,
|
||||||
|
inviter_name: invite.inviterName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue