Newer
Older
bremer-ios-app / Pods / Realm / core / realm-monorepo.xcframework / macos-x86_64_arm64 / Headers / realm / object-store / sync / app.hpp
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2020 Realm Inc.
//
// 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 utilied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

#ifndef REALM_APP_HPP
#define REALM_APP_HPP

#include <realm/object-store/sync/app_credentials.hpp>
#include <realm/object-store/sync/app_service_client.hpp>
#include <realm/object-store/sync/auth_request_client.hpp>
#include <realm/object-store/sync/generic_network_transport.hpp>
#include <realm/object-store/sync/push_client.hpp>
#include <realm/object-store/sync/subscribable.hpp>

#include <realm/object_id.hpp>
#include <realm/util/logger.hpp>
#include <realm/util/optional.hpp>
#include <realm/util/functional.hpp>

#include <mutex>

namespace realm {
class SyncUser;
class SyncSession;
class SyncManager;
struct SyncClientConfig;
class SyncAppMetadata;

namespace app {

class App;

typedef std::shared_ptr<App> SharedApp;

/// The `App` has the fundamental set of methods for communicating with a Atlas App Services backend.
///
/// This class provides access to login and authentication.
///
/// You can also use it to execute [Functions](https://docs.mongodb.com/stitch/functions/).
class App : public std::enable_shared_from_this<App>,
            public AuthRequestClient,
            public AppServiceClient,
            public Subscribable<App> {
public:
    struct Config {
        // Information about the device where the app is running
        struct DeviceInfo {
            std::string platform_version;  // json: platformVersion
            std::string sdk_version;       // json: sdkVersion
            std::string sdk;               // json: sdk
            std::string device_name;       // json: deviceName
            std::string device_version;    // json: deviceVersion
            std::string framework_name;    // json: frameworkName
            std::string framework_version; // json: frameworkVersion
            std::string bundle_id;         // json: bundleId

            DeviceInfo();
            DeviceInfo(std::string, std::string, std::string, std::string, std::string, std::string, std::string,
                       std::string);

        private:
            friend App;

            std::string platform;     // json: platform
            std::string cpu_arch;     // json: cpuArch
            std::string core_version; // json: coreVersion
        };

        std::string app_id;
        std::shared_ptr<GenericNetworkTransport> transport;
        util::Optional<std::string> base_url;
        util::Optional<std::string> local_app_name;
        util::Optional<std::string> local_app_version;
        util::Optional<uint64_t> default_request_timeout_ms;
        DeviceInfo device_info;
    };

    // `enable_shared_from_this` is unsafe with public constructors; use `get_shared_app` instead
    App(const Config& config);
    App(App&&) noexcept = default;
    App& operator=(App&&) noexcept = default;
    ~App();

    const Config& config() const
    {
        return m_config;
    }

    const std::string& base_url() const
    {
        return m_base_url;
    }

    /// Get the last used user.
    std::shared_ptr<SyncUser> current_user() const;

    /// Get all users.
    std::vector<std::shared_ptr<SyncUser>> all_users() const;

    std::shared_ptr<SyncManager> const& sync_manager() const
    {
        return m_sync_manager;
    }

    /// A struct representing a user API key as returned by the App server.
    struct UserAPIKey {
        // The ID of the key.
        ObjectId id;

        /// The actual key. Will only be included in
        /// the response when an API key is first created.
        util::Optional<std::string> key;

        /// The name of the key.
        std::string name;

        /// Whether or not the key is disabled.
        bool disabled;
    };

    /// A client for the user API key authentication provider which
    /// can be used to create and modify user API keys. This
    /// client should only be used by an authenticated user.
    class UserAPIKeyProviderClient {
    public:
        /// Creates a user API key that can be used to authenticate as the current user.
        /// @param name The name of the API key to be created.
        /// @param completion A callback to be invoked once the call is complete.
        void create_api_key(const std::string& name, const std::shared_ptr<SyncUser>& user,
                            util::UniqueFunction<void(UserAPIKey&&, util::Optional<AppError>)>&& completion);

        /// Fetches a user API key associated with the current user.
        /// @param id The id of the API key to fetch.
        /// @param completion A callback to be invoked once the call is complete.
        void fetch_api_key(const realm::ObjectId& id, const std::shared_ptr<SyncUser>& user,
                           util::UniqueFunction<void(UserAPIKey&&, util::Optional<AppError>)>&& completion);

        /// Fetches the user API keys associated with the current user.
        /// @param completion A callback to be invoked once the call is complete.
        void
        fetch_api_keys(const std::shared_ptr<SyncUser>& user,
                       util::UniqueFunction<void(std::vector<UserAPIKey>&&, util::Optional<AppError>)>&& completion);

        /// Deletes a user API key associated with the current user.
        /// @param id The id of the API key to delete.
        /// @param user The user to perform this operation.
        /// @param completion A callback to be invoked once the call is complete.
        void delete_api_key(const realm::ObjectId& id, const std::shared_ptr<SyncUser>& user,
                            util::UniqueFunction<void(util::Optional<AppError>)>&& completion);

        /// Enables a user API key associated with the current user.
        /// @param id The id of the API key to enable.
        /// @param user The user to perform this operation.
        /// @param completion A callback to be invoked once the call is complete.
        void enable_api_key(const realm::ObjectId& id, const std::shared_ptr<SyncUser>& user,
                            util::UniqueFunction<void(util::Optional<AppError>)>&& completion);

        /// Disables a user API key associated with the current user.
        /// @param id The id of the API key to disable.
        /// @param user The user to perform this operation.
        /// @param completion A callback to be invoked once the call is complete.
        void disable_api_key(const realm::ObjectId& id, const std::shared_ptr<SyncUser>& user,
                             util::UniqueFunction<void(util::Optional<AppError>)>&& completion);

    private:
        friend class App;
        UserAPIKeyProviderClient(AuthRequestClient& auth_request_client)
            : m_auth_request_client(auth_request_client)
        {
        }

        std::string url_for_path(const std::string& path) const;
        AuthRequestClient& m_auth_request_client;
    };

    /// A client for the username/password authentication provider which
    /// can be used to obtain a credential for logging in,
    /// and to perform requests specifically related to the username/password provider.
    ///
    class UsernamePasswordProviderClient {
    public:
        /// Registers a new email identity with the username/password provider,
        /// and sends a confirmation email to the provided address.
        /// @param email The email address of the user to register.
        /// @param password The password that the user created for the new username/password identity.
        /// @param completion A callback to be invoked once the call is complete.
        void register_email(const std::string& email, const std::string& password,
                            util::UniqueFunction<void(util::Optional<AppError>)>&& completion);

        /// Confirms an email identity with the username/password provider.
        /// @param token The confirmation token that was emailed to the user.
        /// @param token_id The confirmation token id that was emailed to the user.
        /// @param completion A callback to be invoked once the call is complete.
        void confirm_user(const std::string& token, const std::string& token_id,
                          util::UniqueFunction<void(util::Optional<AppError>)>&& completion);

        /// Re-sends a confirmation email to a user that has registered but
        /// not yet confirmed their email address.
        /// @param email The email address of the user to re-send a confirmation for.
        /// @param completion A callback to be invoked once the call is complete.
        void resend_confirmation_email(const std::string& email,
                                       util::UniqueFunction<void(util::Optional<AppError>)>&& completion);

        void send_reset_password_email(const std::string& email,
                                       util::UniqueFunction<void(util::Optional<AppError>)>&& completion);

        /// Retries the custom confirmation function on a user for a given email.
        /// @param email The email address of the user to retry the custom confirmation for.
        /// @param completion A callback to be invoked once the retry is complete.
        void retry_custom_confirmation(const std::string& email,
                                       util::UniqueFunction<void(util::Optional<AppError>)>&& completion);

        /// Resets the password of an email identity using the
        /// password reset token emailed to a user.
        /// @param password The desired new password.
        /// @param token The password reset token that was emailed to the user.
        /// @param token_id The password reset token id that was emailed to the user.
        /// @param completion A callback to be invoked once the call is complete.
        void reset_password(const std::string& password, const std::string& token, const std::string& token_id,
                            util::UniqueFunction<void(util::Optional<AppError>)>&& completion);

        /// Resets the password of an email identity using the
        /// password reset function set up in the application.
        /// @param email The email address of the user.
        /// @param password The desired new password.
        /// @param args A bson array of arguments.
        /// @param completion A callback to be invoked once the call is complete.
        void call_reset_password_function(const std::string& email, const std::string& password,
                                          const bson::BsonArray& args,
                                          util::UniqueFunction<void(util::Optional<AppError>)>&& completion);

    private:
        friend class App;
        UsernamePasswordProviderClient(SharedApp app)
            : m_parent(app)
        {
            REALM_ASSERT(app);
        }
        SharedApp m_parent;
    };

    /// Retrieve a cached app instance if one was previously generated for `config`'s app_id+base_url combo,
    /// otherwise generate and return a new instance and persist it in the cache.
    static SharedApp get_shared_app(const Config& config, const SyncClientConfig& sync_client_config);

    /// Generate and return a new app instance for the given config, bypassing the app cache.
    static SharedApp get_uncached_app(const Config& config, const SyncClientConfig& sync_client_config);

    /// Return a cached app instance if one was previously generated for the `app_id`+`base_url` combo using
    /// `get_shared_app`.
    /// If base_url is not provided, and there are multiple cached apps with the same app_id but different base_urls,
    /// then a non-determinstic one will be returned.
    ///
    /// Prefer using `get_shared_app` or populating `base_url` to avoid the non-deterministic behavior.
    static SharedApp get_cached_app(const std::string& app_id,
                                    const std::optional<std::string>& base_url = std::nullopt);

    /// Log in a user and asynchronously retrieve a user object.
    /// If the log in completes successfully, the completion block will be called, and a
    /// `SyncUser` representing the logged-in user will be passed to it. This user object
    /// can be used to open `Realm`s and retrieve `SyncSession`s. Otherwise, the
    /// completion block will be called with an error.
    ///
    /// @param credentials A `SyncCredentials` object representing the user to log in.
    /// @param completion A callback block to be invoked once the log in completes.
    void log_in_with_credentials(
        const AppCredentials& credentials,
        util::UniqueFunction<void(const std::shared_ptr<SyncUser>&, util::Optional<AppError>)>&& completion);

    /// Logout the current user.
    void log_out(util::UniqueFunction<void(util::Optional<AppError>)>&&);

    /// Refreshes the custom data for a specified user
    /// @param user The user you want to refresh
    /// @param update_location If true, the location metadata will be updated before refresh
    void refresh_custom_data(const std::shared_ptr<SyncUser>& user, bool update_location,
                             util::UniqueFunction<void(util::Optional<AppError>)>&& completion);
    void refresh_custom_data(const std::shared_ptr<SyncUser>& user,
                             util::UniqueFunction<void(util::Optional<AppError>)>&& completion);

    /// Log out the given user if they are not already logged out.
    void log_out(const std::shared_ptr<SyncUser>& user,
                 util::UniqueFunction<void(util::Optional<AppError>)>&& completion);

    /// Links the currently authenticated user with a new identity, where the identity is defined by the credential
    /// specified as a parameter. This will only be successful if this `SyncUser` is the currently authenticated
    /// with the client from which it was created. On success the user will be returned with the new identity.
    ///
    /// @param user The user which will have the credentials linked to, the user must be logged in
    /// @param credentials The `AppCredentials` used to link the user to a new identity.
    /// @param completion The completion handler to call when the linking is complete.
    ///                         If the operation is  successful, the result will contain the original
    ///                         `SyncUser` object representing the user.
    void
    link_user(const std::shared_ptr<SyncUser>& user, const AppCredentials& credentials,
              util::UniqueFunction<void(const std::shared_ptr<SyncUser>&, util::Optional<AppError>)>&& completion);

    /// Switches the active user with the specified one. The user must
    /// exist in the list of all users who have logged into this application, and
    /// the user must be currently logged in, otherwise this will throw an
    /// AppError.
    ///
    /// @param user The user to switch to
    /// @returns A shared pointer to the new current user
    std::shared_ptr<SyncUser> switch_user(const std::shared_ptr<SyncUser>& user) const;

    /// Logs out and removes the provided user.
    /// This invokes logout on the server.
    /// @param user the user to remove
    /// @param completion Will return an error if the user is not found or the http request failed.
    void remove_user(const std::shared_ptr<SyncUser>& user,
                     util::UniqueFunction<void(util::Optional<AppError>)>&& completion);

    /// Deletes a user and all its data from the server.
    /// @param user The user to delete
    /// @param completion Will return an error if the user is not found or the http request failed.
    void delete_user(const std::shared_ptr<SyncUser>& user,
                     util::UniqueFunction<void(util::Optional<AppError>)>&& completion);

    // Get a provider client for the given class type.
    template <class T>
    T provider_client()
    {
        return T(this);
    }

    void call_function(const std::shared_ptr<SyncUser>& user, const std::string& name, std::string_view args_ejson,
                       const util::Optional<std::string>& service_name,
                       util::UniqueFunction<void(const std::string*, util::Optional<AppError>)>&& completion) final;

    void call_function(
        const std::shared_ptr<SyncUser>& user, const std::string& name, const bson::BsonArray& args_bson,
        const util::Optional<std::string>& service_name,
        util::UniqueFunction<void(util::Optional<bson::Bson>&&, util::Optional<AppError>)>&& completion) final;

    void call_function(
        const std::shared_ptr<SyncUser>& user, const std::string&, const bson::BsonArray& args_bson,
        util::UniqueFunction<void(util::Optional<bson::Bson>&&, util::Optional<AppError>)>&& completion) final;

    void call_function(
        const std::string& name, const bson::BsonArray& args_bson, const util::Optional<std::string>& service_name,
        util::UniqueFunction<void(util::Optional<bson::Bson>&&, util::Optional<AppError>)>&& completion) final;

    void call_function(
        const std::string&, const bson::BsonArray& args_bson,
        util::UniqueFunction<void(util::Optional<bson::Bson>&&, util::Optional<AppError>)>&& completion) final;

    template <typename T>
    void call_function(const std::shared_ptr<SyncUser>& user, const std::string& name,
                       const bson::BsonArray& args_bson,
                       util::UniqueFunction<void(util::Optional<T>&&, util::Optional<AppError>)>&& completion)
    {
        call_function(
            user, name, args_bson, util::none,
            [completion = std::move(completion)](util::Optional<bson::Bson>&& value, util::Optional<AppError> error) {
                if (value) {
                    return completion(util::some<T>(static_cast<T>(*value)), std::move(error));
                }

                return completion(util::none, std::move(error));
            });
    }

    template <typename T>
    void call_function(const std::string& name, const bson::BsonArray& args_bson,
                       util::UniqueFunction<void(util::Optional<T>&&, util::Optional<AppError>)>&& completion)
    {
        call_function(current_user(), name, args_bson, std::move(completion));
    }

    // NOTE: only sets "Accept: text/event-stream" header. If you use an API that sets that but doesn't support
    // setting other headers (eg. EventSource() in JS), you can ignore the headers field on the request.
    Request make_streaming_request(const std::shared_ptr<SyncUser>& user, const std::string& name,
                                   const bson::BsonArray& args_bson,
                                   const util::Optional<std::string>& service_name) const;

    // MARK: Push notification client
    PushClient push_notification_client(const std::string& service_name);

    static void clear_cached_apps();

    // Immediately close all open sync sessions for all cached apps.
    // Used by JS SDK to ensure no sync clients remain open when a developer
    // reloads an app (#5411).
    static void close_all_sync_sessions();

private:
    friend class Internal;
    friend class OnlyForTesting;

    Config m_config;

    // mutable to allow locking for reads in const functions
    // this is a shared pointer to support the App move constructor
    mutable std::shared_ptr<std::mutex> m_route_mutex = std::make_shared<std::mutex>();
    std::string m_base_url;
    std::string m_base_route;
    std::string m_app_route;
    std::string m_auth_route;
    bool m_location_updated = false;

    uint64_t m_request_timeout_ms;
    std::shared_ptr<SyncManager> m_sync_manager;
    std::shared_ptr<util::Logger> m_logger_ptr;

    /// m_logger_ptr is not set until the first call to one of these functions.
    /// If configure() not been called, a logger will not be available yet.
    /// @returns true if the logger was set, otherwise false.
    bool init_logger();
    /// These helpers prevent all the checks for if(m_logger_ptr) throughout the
    /// code.
    bool would_log(util::Logger::Level level);
    template <class... Params>
    void log_debug(const char* message, Params&&... params);
    template <class... Params>
    void log_error(const char* message, Params&&... params);

    /// Refreshes the access token for a specified `SyncUser`
    /// @param completion Passes an error should one occur.
    /// @param update_location If true, the location metadata will be updated before refresh
    void refresh_access_token(const std::shared_ptr<SyncUser>& user, bool update_location,
                              util::UniqueFunction<void(util::Optional<AppError>)>&& completion);

    /// Checks if an auth failure has taken place and if so it will attempt to refresh the
    /// access token and then perform the orginal request again with the new access token
    /// @param error The error to check for auth failures
    /// @param response The original response to pass back should this not be an auth error
    /// @param request The request to perform
    /// @param completion returns the original response in the case it is not an auth error, or if a failure
    /// occurs, if the refresh was a success the newly attempted response will be passed back
    void handle_auth_failure(const AppError& error, const Response& response, Request&& request,
                             const std::shared_ptr<SyncUser>& user,
                             util::UniqueFunction<void(const Response&)>&& completion);

    std::string url_for_path(const std::string& path) const override;

    /// Return the app route for this App instance, or creates a new app route string if
    /// a new hostname is provided
    /// @param hostname The hostname to generate a new app route
    std::string get_app_route(const util::Optional<std::string>& hostname = util::none) const;

    /// Request the app metadata information from the server if it has not been processed yet. If
    /// a new hostname is provided, the app metadata will be refreshed using the new hostname.
    /// @param completion The server response if an error was encountered during the update
    /// @param new_hostname If provided, the metadata will be requested from this hostname
    void init_app_metadata(util::UniqueFunction<void(const util::Optional<Response>&)>&& completion,
                           const util::Optional<std::string>& new_hostname = util::none);

    /// Update the app metadata and resend the request with the updated metadata
    /// @param request The original request object that needs to be sent after the update
    /// @param completion The original completion object that will be called with the response to the request
    /// @param new_hostname If provided, the metadata will be requested from this hostname
    void update_metadata_and_resend(Request&& request, util::UniqueFunction<void(const Response&)>&& completion,
                                    const util::Optional<std::string>& new_hostname = util::none);

    void post(std::string&& route, util::UniqueFunction<void(util::Optional<AppError>)>&& completion,
              const bson::BsonDocument& body);

    /// Performs a request to the Stitch server. This request does not contain authentication state.
    /// @param request The request to be performed
    /// @param completion Returns the response from the server
    /// @param update_location Force the location metadata to be updated prior to sending the request
    void do_request(Request&& request, util::UniqueFunction<void(const Response&)>&& completion,
                    bool update_location = false);

    /// Check to see if hte response is a redirect and handle, otherwise pass the response to compleetion
    /// @param request The request to be performed (in case it needs to be sent again)
    /// @param response The response from the send_request_to_server operation
    /// @param completion Returns the response from the server if not a redirect
    void handle_possible_redirect_response(Request&& request, const Response& response,
                                           util::UniqueFunction<void(const Response&)>&& completion);

    /// Process the redirect response received from the last request that was sent to the server
    /// @param request The request to be performed (in case it needs to be sent again)
    /// @param response The response from the send_request_to_server operation
    /// @param completion Returns the response from the server if not a redirect
    void handle_redirect_response(Request&& request, const Response& response,
                                  util::UniqueFunction<void(const Response&)>&& completion);

    /// Performs an authenticated request to the Stitch server, using the current authentication state
    /// @param request The request to be performed
    /// @param completion Returns the response from the server
    void do_authenticated_request(Request&& request, const std::shared_ptr<SyncUser>& user,
                                  util::UniqueFunction<void(const Response&)>&& completion) override;


    /// Gets the social profile for a `SyncUser`
    /// @param completion Callback will pass the `SyncUser` with the social profile details
    void
    get_profile(const std::shared_ptr<SyncUser>& user,
                util::UniqueFunction<void(const std::shared_ptr<SyncUser>&, util::Optional<AppError>)>&& completion);

    /// Log in a user and asynchronously retrieve a user object.
    /// If the log in completes successfully, the completion block will be called, and a
    /// `SyncUser` representing the logged-in user will be passed to it. This user object
    /// can be used to open `Realm`s and retrieve `SyncSession`s. Otherwise, the
    /// completion block will be called with an error.
    ///
    /// @param credentials A `SyncCredentials` object representing the user to log in.
    /// @param linking_user A `SyncUser` you want to link these credentials too
    /// @param completion A callback block to be invoked once the log in completes.
    void log_in_with_credentials(
        const AppCredentials& credentials, const std::shared_ptr<SyncUser>& linking_user,
        util::UniqueFunction<void(const std::shared_ptr<SyncUser>&, util::Optional<AppError>)>&& completion);

    /// Provides MongoDB Realm Cloud with metadata related to the users session
    void attach_auth_options(bson::BsonDocument& body);

    std::string function_call_url_path() const;

    void configure(const SyncClientConfig& sync_client_config);

    std::string make_sync_route(const std::string& http_app_route);

    void update_hostname(const util::Optional<realm::SyncAppMetadata>& metadata);

    void update_hostname(const std::string& hostname, const util::Optional<std::string>& ws_hostname = util::none);

    bool verify_user_present(const std::shared_ptr<SyncUser>& user) const;
};

// MARK: Provider client templates
template <>
App::UsernamePasswordProviderClient App::provider_client<App::UsernamePasswordProviderClient>();
template <>
App::UserAPIKeyProviderClient App::provider_client<App::UserAPIKeyProviderClient>();

} // namespace app
} // namespace realm

#endif /* REALM_APP_HPP */