Home Reference Source Test Repository

src/controller/identity.js

import NPROneSDK from './../index';
import FetchUtil from './../util/fetch-util';
import User from './../model/user';
import Logger from './../util/logger';
import AccessToken from './../model/access-token';
import StationFinder from './station-finder';


/**
 * Encapsulates all of the logic for communication with the [Identity Service](https://dev.npr.org/api/#/identity)
 * in the NPR One API.
 *
 * Note that consumers should not be accessing this class directly but should instead use the provided pass-through
 * functions in the main {@link NprOneSDK} class.
 *
 * @example <caption>How to change a user's station using station search</caption>
 * const nprOneSDK = new NprOneSDK();
 * nprOneSDK.config = { ... };
 * nprOneSDK.getUser() // optional; verifies that you have a logged-in user
 *     .then(() => {
 *        return nprOneSDK.searchStations('wnyc');
 *     })
 *     .then((stations) => {
 *         const stationId = stations[0].id; // in reality, you'd probably have the user select a station, see the StationFinder for detail
 *         nprOneSDK.setUserStation(stationId);
 *     });
 */
export default class Identity {
    /**
     * Gets user metadata, such as first and last name, programs they have shown an affinity for, and preferred NPR One
     * station.
     *
     * @returns {Promise<User>}
     */
    getUser() {
        const url = `${NPROneSDK.getServiceUrl('identity')}/user`;

        return FetchUtil.nprApiFetch(url).then(json => new User(json));
    }

    /**
     * Sets a user's favorite NPR station. Note that this function will first validate whether the station with the given
     * ID actually exists, and will return a promise that rejects if not.
     *
     * @param {number|string} stationId   The station's ID, which is either an integer or a numeric string (e.g. `123` or `'123'`)
     * @returns {Promise<User>}
     */
    setUserStation(stationId) {
        return StationFinder.validateStation(stationId)
            .then(() => {
                const url = `${NPROneSDK.getServiceUrl('identity')}/stations`;
                const options = {
                    method: 'PUT',
                    body: JSON.stringify([stationId]),
                };
                return FetchUtil.nprApiFetch(url, options).then(json => new User(json));
            })
            .catch((e) => {
                Logger.debug('setUserStation failed, message: ', e);
                return Promise.reject(e);
            });
    }

    /**
     * Indicates that the user wishes to follow, or subscribe to, the show, program, or podcast with the given numeric
     * ID. Followed shows will appear more frequently in a user's list of recommendations.
     *
     * Note that at this time, because we have not yet implemented search in this SDK, there is no way to retrieve a list
     * of aggregation (show) IDs through this SDK. You can either add functionality to your own app that makes an API call
     * to `GET https://api.npr.org/listening/v2/search/recommendations` with a program name or other search parameters, or
     * wait until we implement search in this SDK (hopefully later this year).
     *
     * @param {number|string} aggregationId    The aggregation (show) ID, which is either an integer or a numeric string (e.g. `123` or `'123'`)
     * @returns {Promise<User>}
     * @throws {TypeError} if the passed-in aggregation (show) ID is not either a number or a numeric string
     */
    followShow(aggregationId) {
        return this._setFollowingStatusForShow(aggregationId, true);
    }

    /**
     * Indicates that the user wishes to unfollow, or unsubscribe from, the show, program, or podcast with the given
     * numeric ID. See {@link followShow} for more information.
     *
     * @param {number|string} aggregationId    The aggregation (show) ID, which is either an integer or a numeric string (e.g. `123` or `'123'`)
     * @returns {Promise<User>}
     * @throws {TypeError} if the passed-in aggregation (show) ID is not either a number or a numeric string
     */
    unfollowShow(aggregationId) {
        return this._setFollowingStatusForShow(aggregationId, false);
    }

    /**
     * Primary workhorse for {@link followShow} and {@link unfollowShow}.
     *
     * @param {number|string} aggregationId    The aggregation (show) ID, which is either an integer or a numeric string (e.g. `123` or `'123'`)
     * @param {boolean} shouldFollow           Whether or not the aggregation should be followed (`true`) or unfollowed (`false`)
     * @returns {Promise<User>}
     * @throws {TypeError} if the passed-in aggregation (show) ID is not either a number or a numeric string
     * @private
     */
    _setFollowingStatusForShow(aggregationId, shouldFollow) {
        const n = parseInt(aggregationId, 10);
        if (isNaN(n) || !isFinite(n)) {
            throw new TypeError('Aggregation (show) ID must be an integer greater than 0');
        }

        const data = {
            id: aggregationId,
            following: shouldFollow,
        };

        const url = `${NPROneSDK.getServiceUrl('identity')}/following`;
        const options = {
            method: 'POST',
            body: JSON.stringify(data),
        };

        return FetchUtil.nprApiFetch(url, options).then(json => new User(json));
    }

    /**
     * Creates a temporary user from the NPR One API and use that user's access token for
     * subsequent API requests.
     *
     * Caution: most clients are not authorized to use temporary users.
     *
     * @returns {Promise<User>}
     * @throws {TypeError} if an OAuth proxy is not configured or no client ID is set
     */
    createTemporaryUser() {
        if (!NPROneSDK.config.authProxyBaseUrl) {
            throw new TypeError('OAuth proxy not configured. Unable to create temporary users.');
        }
        if (!NPROneSDK.config.clientId) {
            throw new TypeError('A client ID must be set for temporary user requests.');
        }

        let url = `${NPROneSDK.config.authProxyBaseUrl}${NPROneSDK.config.tempUserPath}`;
        const glueCharacter = url.indexOf('?') >= 0 ? '&' : '?';
        url = `${url}${glueCharacter}clientId=${NPROneSDK.config.clientId}`;

        const options = {
            credentials: 'include',
        };

        return FetchUtil.nprApiFetch(url, options)
            .then((json) => {
                const tokenModel = new AccessToken(json);
                tokenModel.validate(); // throws exception if invalid
                NPROneSDK.accessToken = tokenModel.token;
                return tokenModel; // never directly consumed, but useful for testing
            });
    }
}