Show the user's 3PID invite after they've reloaded the page

This is a step towards https://github.com/vector-im/element-web/issues/13430

Since we've stored the invite, we can send the user to it once they reload the page or revisit Element. We currently only support one invite at a time, but this should be fine for most cases. 

We only do this restoration if the next screen isn't set to avoid breaking the user out of an expected flow.

As an added touch, this also ensures that the email address is pre-filled on the registration page if needed, just in case the user refreshes before getting to the submit button.
This commit is contained in:
Travis Ralston 2020-09-11 20:20:33 -06:00
parent a5d7b24805
commit 803badba1b
3 changed files with 39 additions and 8 deletions

View file

@ -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";
@ -676,7 +677,17 @@ async function _clearStorage() {
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
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) {

View file

@ -1214,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.
@ -2015,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}

View file

@ -60,23 +60,24 @@ export default class ThreepidInviteStore extends EventEmitter {
} }
public storeInvite(roomId: string, wireInvite: IThreepidInviteWireFormat): IThreepidInvite { public storeInvite(roomId: string, wireInvite: IThreepidInviteWireFormat): IThreepidInvite {
console.log("Storing invite: ", {roomId, ...wireInvite});
const invite = <IPersistedThreepidInvite>{roomId, ...wireInvite}; const invite = <IPersistedThreepidInvite>{roomId, ...wireInvite};
const id = this.generateIdOf(invite); const id = this.generateIdOf(invite);
localStorage.setItem(`${STORAGE_PREFIX}${id}`, JSON.stringify(invite)); localStorage.setItem(`${STORAGE_PREFIX}${id}`, JSON.stringify(invite));
return this.translateInvite(invite); return this.translateInvite(invite);
} }
public getInvites(): IThreepidInvite[] { public getWireInvites(): IPersistedThreepidInvite[] {
const result: IThreepidInvite[] = []; const results: IPersistedThreepidInvite[] = [];
for (let i = 0; i < localStorage.length; i++) { for (let i = 0; i < localStorage.length; i++) {
const keyName = localStorage.key(i); const keyName = localStorage.key(i);
if (!keyName.startsWith(STORAGE_PREFIX)) continue; if (!keyName.startsWith(STORAGE_PREFIX)) continue;
results.push(JSON.parse(localStorage.getItem(keyName)) as IPersistedThreepidInvite);
const persisted = JSON.parse(localStorage.getItem(keyName)) as IPersistedThreepidInvite;
result.push(this.translateInvite(persisted));
} }
return result; 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 // Currently Element can only handle one invite at a time, so handle that
@ -104,4 +105,14 @@ export default class ThreepidInviteStore extends EventEmitter {
inviterName: persisted.inviter_name, 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,
};
}
} }