src/crypto/DeviceList.js
/**
     * Start device queries for any users with outdated device lists
     *
     * We tolerate multiple concurrent device queries, but only one query per
     * user.
     *
     * If any users already have downloads in progress, they are ignored - they
     * will be refreshed when the current download completes anyway, so
     * each user with outdated device lists will be updated eventually.
     *
     * The returned promise resolves immediately if there are no users with
     * outdated device lists, or if all users with outdated device lists already
     * have a query in progress.
     *
     * Otherwise, a new query request is made, and the promise resolves
     * once that query completes. If the query fails, the promise will reject
     * if rejectOnFailure was truthy, otherwise it will still resolve.
     *
     * @param {Boolean?} rejectOnFailure  true to make the returned promise
     *   reject if the device list query fails.
     *
     * @return {Promise}
     */
Start device queries for any users with outdated device lists
We tolerate multiple concurrent device queries, but only one query per
user.

If any users already have downloads in progress, they are ignored - they
will be refreshed when the current download completes anyway, so
each user with outdated device lists will be updated eventually.

The returned promise resolves immediately if there are no users with
outdated device lists, or if all users with outdated device lists already
have a query in progress.

Otherwise, a new query request is made, and the promise resolves
once that query completes. If the query fails, the promise will reject
if rejectOnFailure was truthy, otherwise it will still resolve.

@param {Boolean?} rejectOnFailure  true to make the returned promise
reject if the device list query fails.

@return {Promise}
Start device queries for any users with outdated device lists We tolerate multiple concurrent device queries but only one query per user . If any users already have downloads in progress they are ignored - they will be refreshed when the current download completes anyway so each user with outdated device lists will be updated eventually . The returned promise resolves immediately if there are no users with outdated device lists or if all users with outdated device lists already have a query in progress . Otherwise a new query request is made and the promise resolves once that query completes . If the query fails the promise will reject if rejectOnFailure was truthy otherwise it will still resolve . @param { Boolean? } rejectOnFailure true to make the returned promise reject if the device list query fails . @return { Promise }
refreshOutdatedDeviceLists(rejectOnFailure) {
        const users = Object.keys(this._pendingUsersWithNewDevices).filter(
            (u) => !this._keyDownloadsInProgressByUser[u],
        );
        if (users.length === 0) {
            return q();
        }
        let prom = this._doKeyDownloadForUsers(users).then(() => {
            users.forEach((u) => {
                delete this._keyDownloadsInProgressByUser[u];
            });
            this.refreshOutdatedDeviceLists().done();
        }, (e) => {
            console.error(
                'Error updating device key cache for ' + users + ":", e,
            );
             reinstate the pending flags on any users which failed; this will
             mean that we will do another download in the future, but won't
             tight-loop.
            users.forEach((u) => {
                delete this._keyDownloadsInProgressByUser[u];
                this._pendingUsersWithNewDevices[u] = true;
            });
             TODO: schedule a retry.
            throw e;
        });
        users.forEach((u) => {
            delete this._pendingUsersWithNewDevices[u];
            this._keyDownloadsInProgressByUser[u] = prom;
        });
        if (!rejectOnFailure) {
             normally we just want to swallow the exception - we've already
             logged it futher up.
            prom = prom.catch((e) => {});
        }
        return prom;
    }
refreshOutdatedDeviceLists ( rejectOnFailure ) { const users = Object . keys ( this . _pendingUsersWithNewDevices ) . filter ( ( u ) => ! this . _keyDownloadsInProgressByUser [ u ] , ) ; if ( users . length === 0 ) { return q ( ) ; } let prom = this . _doKeyDownloadForUsers ( users ) . then ( ( ) => { users . forEach ( ( u ) => { delete this . _keyDownloadsInProgressByUser [ u ] ; } ) ; this . refreshOutdatedDeviceLists ( ) . done ( ) ; } , ( e ) => { console . error ( 'Error updating device key cache for ' + users + ":" , e , ) ; users . forEach ( ( u ) => { delete this . _keyDownloadsInProgressByUser [ u ] ; this . _pendingUsersWithNewDevices [ u ] = true ; } ) ; throw e ; } ) ; users . forEach ( ( u ) => { delete this . _pendingUsersWithNewDevices [ u ] ; this . _keyDownloadsInProgressByUser [ u ] = prom ; } ) ; if ( ! rejectOnFailure ) { prom = prom . catch ( ( e ) => { } ) ; } return prom ; }
Start device queries for any users with outdated device lists
 We tolerate multiple concurrent device queries, but only one query per
 user.
Start device queries for any users with outdated device lists We tolerate multiple concurrent device queries but only one query per user .
// flush out any more requests that were blocked up while that // was going on, but let the initial promise complete now. // // reinstate the pending flags on any users which failed; this will // mean that we will do another download in the future, but won't // tight-loop. // // TODO: schedule a retry. // normally we just want to swallow the exception - we've already // logged it futher up.