diff --git a/src/IdentityAuthClient.js b/src/IdentityAuthClient.js index 075ae93709..7cbad074bf 100644 --- a/src/IdentityAuthClient.js +++ b/src/IdentityAuthClient.js @@ -65,7 +65,7 @@ export default class IdentityAuthClient { } // Returns a promise that resolves to the access_token string from the IS - async getAccessToken(check=true) { + async getAccessToken({ check = true } = {}) { if (!this.authEnabled) { // The current IS doesn't support authentication return null; diff --git a/src/boundThreepids.js b/src/boundThreepids.js index 799728f801..3b32815913 100644 --- a/src/boundThreepids.js +++ b/src/boundThreepids.js @@ -16,7 +16,7 @@ limitations under the License. import IdentityAuthClient from './IdentityAuthClient'; -export async function getThreepidBindStatus(client, filterMedium) { +export async function getThreepidsWithBindStatus(client, filterMedium) { const userId = client.getUserId(); let { threepids } = await client.getThreePids(); @@ -24,27 +24,33 @@ export async function getThreepidBindStatus(client, filterMedium) { threepids = threepids.filter((a) => a.medium === filterMedium); } - if (threepids.length > 0) { - // TODO: Handle terms agreement - // See https://github.com/vector-im/riot-web/issues/10522 - const authClient = new IdentityAuthClient(); - const identityAccessToken = await authClient.getAccessToken(); + // Check bind status assuming we have an IS and terms are agreed + if (threepids.length > 0 && !!client.getIdentityServerUrl()) { + try { + const authClient = new IdentityAuthClient(); + const identityAccessToken = await authClient.getAccessToken({ check: false }); - // Restructure for lookup query - const query = threepids.map(({ medium, address }) => [medium, address]); - const lookupResults = await client.bulkLookupThreePids(query, identityAccessToken); + // Restructure for lookup query + const query = threepids.map(({ medium, address }) => [medium, address]); + const lookupResults = await client.bulkLookupThreePids(query, identityAccessToken); - // Record which are already bound - for (const [medium, address, mxid] of lookupResults.threepids) { - if (mxid !== userId) { - continue; + // Record which are already bound + for (const [medium, address, mxid] of lookupResults.threepids) { + if (mxid !== userId) { + continue; + } + if (filterMedium && medium !== filterMedium) { + continue; + } + const threepid = threepids.find(e => e.medium === medium && e.address === address); + if (!threepid) continue; + threepid.bound = true; } - if (filterMedium && medium !== filterMedium) { - continue; + } catch (e) { + // Ignore terms errors here and assume other flows handle this + if (!(e.errcode === "M_TERMS_NOT_SIGNED")) { + throw e; } - const threepid = threepids.find(e => e.medium === medium && e.address === address); - if (!threepid) continue; - threepid.bound = true; } } diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js index ae550725f1..9ef5fb295e 100644 --- a/src/components/views/settings/SetIdServer.js +++ b/src/components/views/settings/SetIdServer.js @@ -22,7 +22,7 @@ import sdk from '../../../index'; import MatrixClientPeg from "../../../MatrixClientPeg"; import Modal from '../../../Modal'; import dis from "../../../dispatcher"; -import { getThreepidBindStatus } from '../../../boundThreepids'; +import { getThreepidsWithBindStatus } from '../../../boundThreepids'; import IdentityAuthClient from "../../../IdentityAuthClient"; import {SERVICE_TYPES} from "matrix-js-sdk"; import {abbreviateUrl, unabbreviateUrl} from "../../../utils/UrlUtils"; @@ -249,7 +249,7 @@ export default class SetIdServer extends React.Component { }; async _showServerChangeWarning({ title, unboundMessage, button }) { - const threepids = await getThreepidBindStatus(MatrixClientPeg.get()); + const threepids = await getThreepidsWithBindStatus(MatrixClientPeg.get()); const boundThreepids = threepids.filter(tp => tp.bound); let message; diff --git a/src/components/views/settings/account/EmailAddresses.js b/src/components/views/settings/account/EmailAddresses.js index eb60d4a322..b7324eb272 100644 --- a/src/components/views/settings/account/EmailAddresses.js +++ b/src/components/views/settings/account/EmailAddresses.js @@ -23,8 +23,8 @@ import Field from "../../elements/Field"; import AccessibleButton from "../../elements/AccessibleButton"; import * as Email from "../../../../email"; import AddThreepid from "../../../../AddThreepid"; -const sdk = require('../../../../index'); -const Modal = require("../../../../Modal"); +import sdk from '../../../../index'; +import Modal from '../../../../Modal'; /* TODO: Improve the UX for everything in here. @@ -113,11 +113,15 @@ export class ExistingEmailAddress extends React.Component { } export default class EmailAddresses extends React.Component { - constructor() { - super(); + static propTypes = { + emails: PropTypes.array.isRequired, + onEmailsChange: PropTypes.func.isRequired, + } + + constructor(props) { + super(props); this.state = { - emails: [], verifying: false, addTask: null, continueDisabled: false, @@ -125,16 +129,9 @@ export default class EmailAddresses extends React.Component { }; } - componentWillMount(): void { - const client = MatrixClientPeg.get(); - - client.getThreePids().then((addresses) => { - this.setState({emails: addresses.threepids.filter((a) => a.medium === 'email')}); - }); - } - _onRemoved = (address) => { - this.setState({emails: this.state.emails.filter((e) => e !== address)}); + const emails = this.props.emails.filter((e) => e !== address); + this.props.onEmailsChange(emails); }; _onChangeNewEmailAddress = (e) => { @@ -184,12 +181,16 @@ export default class EmailAddresses extends React.Component { this.state.addTask.checkEmailLinkClicked().then(() => { const email = this.state.newEmailAddress; this.setState({ - emails: [...this.state.emails, {address: email, medium: "email"}], addTask: null, continueDisabled: false, verifying: false, newEmailAddress: "", }); + const emails = [ + ...this.props.emails, + { address: email, medium: "email" }, + ]; + this.props.onEmailsChange(emails); }).catch((err) => { this.setState({continueDisabled: false}); if (err.errcode !== 'M_THREEPID_AUTH_FAILED') { @@ -204,7 +205,7 @@ export default class EmailAddresses extends React.Component { }; render() { - const existingEmailElements = this.state.emails.map((e) => { + const existingEmailElements = this.props.emails.map((e) => { return ; }); diff --git a/src/components/views/settings/account/PhoneNumbers.js b/src/components/views/settings/account/PhoneNumbers.js index fbb5b7e561..8f91eb22cc 100644 --- a/src/components/views/settings/account/PhoneNumbers.js +++ b/src/components/views/settings/account/PhoneNumbers.js @@ -23,8 +23,8 @@ import Field from "../../elements/Field"; import AccessibleButton from "../../elements/AccessibleButton"; import AddThreepid from "../../../../AddThreepid"; import CountryDropdown from "../../auth/CountryDropdown"; -const sdk = require('../../../../index'); -const Modal = require("../../../../Modal"); +import sdk from '../../../../index'; +import Modal from '../../../../Modal'; /* TODO: Improve the UX for everything in here. @@ -108,11 +108,15 @@ export class ExistingPhoneNumber extends React.Component { } export default class PhoneNumbers extends React.Component { - constructor() { - super(); + static propTypes = { + msisdns: PropTypes.array.isRequired, + onMsisdnsChange: PropTypes.func.isRequired, + } + + constructor(props) { + super(props); this.state = { - msisdns: [], verifying: false, verifyError: false, verifyMsisdn: "", @@ -124,16 +128,9 @@ export default class PhoneNumbers extends React.Component { }; } - componentWillMount(): void { - const client = MatrixClientPeg.get(); - - client.getThreePids().then((addresses) => { - this.setState({msisdns: addresses.threepids.filter((a) => a.medium === 'msisdn')}); - }); - } - _onRemoved = (address) => { - this.setState({msisdns: this.state.msisdns.filter((e) => e !== address)}); + const msisdns = this.props.msisdns.filter((e) => e !== address); + this.props.onMsisdnsChange(msisdns); }; _onChangeNewPhoneNumber = (e) => { @@ -181,7 +178,6 @@ export default class PhoneNumbers extends React.Component { const token = this.state.newPhoneNumberCode; this.state.addTask.haveMsisdnToken(token).then(() => { this.setState({ - msisdns: [...this.state.msisdns, {address: this.state.verifyMsisdn, medium: "msisdn"}], addTask: null, continueDisabled: false, verifying: false, @@ -190,6 +186,11 @@ export default class PhoneNumbers extends React.Component { newPhoneNumber: "", newPhoneNumberCode: "", }); + const msisdns = [ + ...this.props.msisdns, + { address: this.state.verifyMsisdn, medium: "msisdn" }, + ]; + this.props.onMsisdnsChange(msisdns); }).catch((err) => { this.setState({continueDisabled: false}); if (err.errcode !== 'M_THREEPID_AUTH_FAILED') { @@ -210,7 +211,7 @@ export default class PhoneNumbers extends React.Component { }; render() { - const existingPhoneElements = this.state.msisdns.map((p) => { + const existingPhoneElements = this.props.msisdns.map((p) => { return ; }); diff --git a/src/components/views/settings/discovery/EmailAddresses.js b/src/components/views/settings/discovery/EmailAddresses.js index 4d18c1d355..d6628f900a 100644 --- a/src/components/views/settings/discovery/EmailAddresses.js +++ b/src/components/views/settings/discovery/EmailAddresses.js @@ -23,7 +23,6 @@ import MatrixClientPeg from "../../../../MatrixClientPeg"; import sdk from '../../../../index'; import Modal from '../../../../Modal'; import AddThreepid from '../../../../AddThreepid'; -import { getThreepidBindStatus } from '../../../../boundThreepids'; /* TODO: Improve the UX for everything in here. @@ -59,6 +58,11 @@ export class EmailAddress extends React.Component { }; } + componentWillReceiveProps(nextProps) { + const { bound } = nextProps.email; + this.setState({ bound }); + } + async changeBinding({ bind, label, errorTitle }) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const { medium, address } = this.props.email; @@ -187,27 +191,14 @@ export class EmailAddress extends React.Component { } export default class EmailAddresses extends React.Component { - constructor() { - super(); - - this.state = { - loaded: false, - emails: [], - }; - } - - async componentWillMount() { - const client = MatrixClientPeg.get(); - - const emails = await getThreepidBindStatus(client, 'email'); - - this.setState({ emails }); + static propTypes = { + emails: PropTypes.array.isRequired, } render() { let content; - if (this.state.emails.length > 0) { - content = this.state.emails.map((e) => { + if (this.props.emails.length > 0) { + content = this.props.emails.map((e) => { return ; }); } else { diff --git a/src/components/views/settings/discovery/PhoneNumbers.js b/src/components/views/settings/discovery/PhoneNumbers.js index fdebac5d22..99a90f23fb 100644 --- a/src/components/views/settings/discovery/PhoneNumbers.js +++ b/src/components/views/settings/discovery/PhoneNumbers.js @@ -23,7 +23,6 @@ import MatrixClientPeg from "../../../../MatrixClientPeg"; import sdk from '../../../../index'; import Modal from '../../../../Modal'; import AddThreepid from '../../../../AddThreepid'; -import { getThreepidBindStatus } from '../../../../boundThreepids'; /* TODO: Improve the UX for everything in here. @@ -51,6 +50,11 @@ export class PhoneNumber extends React.Component { }; } + componentWillReceiveProps(nextProps) { + const { bound } = nextProps.msisdn; + this.setState({ bound }); + } + async changeBinding({ bind, label, errorTitle }) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const { medium, address } = this.props.msisdn; @@ -206,27 +210,14 @@ export class PhoneNumber extends React.Component { } export default class PhoneNumbers extends React.Component { - constructor() { - super(); - - this.state = { - loaded: false, - msisdns: [], - }; - } - - async componentWillMount() { - const client = MatrixClientPeg.get(); - - const msisdns = await getThreepidBindStatus(client, 'msisdn'); - - this.setState({ msisdns }); + static propTypes = { + msisdns: PropTypes.array.isRequired, } render() { let content; - if (this.state.msisdns.length > 0) { - content = this.state.msisdns.map((e) => { + if (this.props.msisdns.length > 0) { + content = this.props.msisdns.map((e) => { return ; }); } else { diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 9c37730fc5..b378db707a 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -37,6 +37,7 @@ import {Service, startTermsFlow} from "../../../../../Terms"; import {SERVICE_TYPES} from "matrix-js-sdk"; import IdentityAuthClient from "../../../../../IdentityAuthClient"; import {abbreviateUrl} from "../../../../../utils/UrlUtils"; +import { getThreepidsWithBindStatus } from '../../../../../boundThreepids'; export default class GeneralUserSettingsTab extends React.Component { static propTypes = { @@ -58,17 +59,20 @@ export default class GeneralUserSettingsTab extends React.Component { // agreedUrls, // From the startTermsFlow callback // resolve, // Promise resolve function for startTermsFlow callback }, + emails: [], + msisdns: [], }; this.dispatcherRef = dis.register(this._onAction); } async componentWillMount() { - const serverRequiresIdServer = await MatrixClientPeg.get().doesServerRequireIdServerParam(); + const cli = MatrixClientPeg.get(); + + const serverRequiresIdServer = await cli.doesServerRequireIdServerParam(); this.setState({serverRequiresIdServer}); - // Check to see if terms need accepting - this._checkTerms(); + this._getThreepidState(); } componentWillUnmount() { @@ -78,10 +82,31 @@ export default class GeneralUserSettingsTab extends React.Component { _onAction = (payload) => { if (payload.action === 'id_server_changed') { this.setState({haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl())}); - this._checkTerms(); + this._getThreepidState(); } }; + _onEmailsChange = (emails) => { + this.setState({ emails }); + } + + _onMsisdnsChange = (msisdns) => { + this.setState({ msisdns }); + } + + async _getThreepidState() { + const cli = MatrixClientPeg.get(); + + // Check to see if terms need accepting + this._checkTerms(); + + // Need to get 3PIDs generally for Account section and possibly also for + // Discovery (assuming we have an IS and terms are agreed). + const threepids = await getThreepidsWithBindStatus(cli); + this.setState({ emails: threepids.filter((a) => a.medium === 'email') }); + this.setState({ msisdns: threepids.filter((a) => a.medium === 'msisdn') }); + } + async _checkTerms() { if (!this.state.haveIdServer) { this.setState({idServerHasUnsignedTerms: false}); @@ -91,7 +116,7 @@ export default class GeneralUserSettingsTab extends React.Component { // By starting the terms flow we get the logic for checking which terms the user has signed // for free. So we might as well use that for our own purposes. const authClient = new IdentityAuthClient(); - const idAccessToken = await authClient.getAccessToken(/*check=*/false); + const idAccessToken = await authClient.getAccessToken({ check: false }); startTermsFlow([new Service( SERVICE_TYPES.IS, MatrixClientPeg.get().getIdentityServerUrl(), @@ -200,10 +225,16 @@ export default class GeneralUserSettingsTab extends React.Component { if (this.state.haveIdServer || this.state.serverRequiresIdServer === false) { threepidSection =
{_t("Email addresses")} - + {_t("Phone numbers")} - +
; } else if (this.state.serverRequiresIdServer === null) { threepidSection = ; @@ -279,10 +310,10 @@ export default class GeneralUserSettingsTab extends React.Component { const threepidSection = this.state.haveIdServer ?
{_t("Email addresses")} - + {_t("Phone numbers")} - +
: null; return (