//////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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 implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #pragma once #include #include #include #include "event_loop_dispatcher.hpp" #include "platform.hpp" #include "js_class.hpp" #include "js_collection.hpp" #include "sync/sync_manager.hpp" #include "sync/sync_config.hpp" #include "sync/sync_session.hpp" #include "sync/sync_user.hpp" #include "realm/util/logger.hpp" #include "realm/util/uri.hpp" namespace realm { namespace js { using SharedUser = std::shared_ptr; using WeakSession = std::weak_ptr; template class UserClass : public ClassDefinition { using GlobalContextType = typename T::GlobalContext; using ContextType = typename T::Context; using FunctionType = typename T::Function; using ObjectType = typename T::Object; using ValueType = typename T::Value; using String = js::String; using Object = js::Object; using Value = js::Value; using Function = js::Function; using ReturnValue = js::ReturnValue; using NativeAccessor = realm::NativeAccessor; public: std::string const name = "User"; static FunctionType create_constructor(ContextType); static void get_server(ContextType, ObjectType, ReturnValue &); static void get_identity(ContextType, ObjectType, ReturnValue &); static void get_token(ContextType, ObjectType, ReturnValue &); static void is_admin(ContextType, ObjectType, ReturnValue &); PropertyMap const properties = { {"server", {wrap, nullptr}}, {"identity", {wrap, nullptr}}, {"token", {wrap, nullptr}}, {"isAdmin", {wrap, nullptr}}, }; static void create_user(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); MethodMap const static_methods = { {"createUser", wrap} }; /*static void current_user(ContextType ctx, ObjectType object, ReturnValue &return_value);*/ static void all_users(ContextType ctx, ObjectType object, ReturnValue &return_value); PropertyMap const static_properties = { /*{"current", {wrap, nullptr}},*/ {"all", {wrap, nullptr}}, }; static void logout(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); static void session_for_on_disk_path(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); MethodMap const methods = { {"logout", wrap}, {"_sessionForOnDiskPath", wrap} }; }; template void UserClass::get_server(ContextType ctx, ObjectType object, ReturnValue &return_value) { std::string server = get_internal>(object)->get()->server_url(); return_value.set(server); } template void UserClass::get_identity(ContextType ctx, ObjectType object, ReturnValue &return_value) { std::string identity = get_internal>(object)->get()->identity(); return_value.set(identity); } template void UserClass::get_token(ContextType ctx, ObjectType object, ReturnValue &return_value) { std::string token = get_internal>(object)->get()->refresh_token(); return_value.set(token); } template void UserClass::is_admin(ContextType ctx, ObjectType object, ReturnValue &return_value) { return_value.set(get_internal>(object)->get()->is_admin()); } template void UserClass::create_user(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 3, 4); SharedUser *user = new SharedUser(SyncManager::shared().get_user( Value::validated_to_string(ctx, arguments[1]), Value::validated_to_string(ctx, arguments[2]), (std::string)Value::validated_to_string(ctx, arguments[0]), Value::validated_to_boolean(ctx, arguments[3]))); return_value.set(create_object>(ctx, user)); } template void UserClass::all_users(ContextType ctx, ObjectType object, ReturnValue &return_value) { auto users = Object::create_empty(ctx); for (auto user : SyncManager::shared().all_logged_in_users()) { if (!user->is_admin()) { Object::set_property(ctx, users, user->identity(), create_object>(ctx, new SharedUser(user)), ReadOnly | DontDelete); } } return_value.set(users); } /* template void UserClass::current_user(ContextType ctx, ObjectType object, ReturnValue &return_value) { SharedUser *current = nullptr; for (auto user : SyncManager::shared().all_logged_in_users()) { if (!user->is_admin()) { if (current != nullptr) { throw std::runtime_error("More than one user logged in currently."); } current = new SharedUser(user); } } if (current != nullptr) { return_value.set(create_object>(ctx, current)); } else { return_value.set_undefined(); } } */ template void UserClass::logout(ContextType ctx, FunctionType, ObjectType this_object, size_t, const ValueType[], ReturnValue &) { get_internal>(this_object)->get()->log_out(); } template class SessionClass : public ClassDefinition { using ContextType = typename T::Context; using FunctionType = typename T::Function; using ObjectType = typename T::Object; using ValueType = typename T::Value; using String = js::String; using Object = js::Object; using Value = js::Value; using ReturnValue = js::ReturnValue; public: std::string const name = "Session"; static FunctionType create_constructor(ContextType); static void get_config(ContextType, ObjectType, ReturnValue &); static void get_user(ContextType, ObjectType, ReturnValue &); static void get_url(ContextType, ObjectType, ReturnValue &); static void get_state(ContextType, ObjectType, ReturnValue &); static void simulate_error(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); static void refresh_access_token(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); PropertyMap const properties = { {"config", {wrap, nullptr}}, {"user", {wrap, nullptr}}, {"url", {wrap, nullptr}}, {"state", {wrap, nullptr}} }; MethodMap const methods = { {"_simulateError", wrap}, {"_refreshAccessToken", wrap} }; }; template class SyncSessionErrorHandlerFunctor { public: SyncSessionErrorHandlerFunctor(typename T::Context ctx, typename T::Function error_func) : m_ctx(Context::get_global_context(ctx)) , m_func(ctx, error_func) { } typename T::Function func() const { return m_func; } void operator()(std::shared_ptr session, SyncError error) { HANDLESCOPE auto error_object = Object::create_empty(m_ctx); Object::set_property(m_ctx, error_object, "message", Value::from_string(m_ctx, error.message)); Object::set_property(m_ctx, error_object, "isFatal", Value::from_boolean(m_ctx, error.is_fatal)); Object::set_property(m_ctx, error_object, "category", Value::from_string(m_ctx, error.error_code.category().name())); Object::set_property(m_ctx, error_object, "code", Value::from_number(m_ctx, error.error_code.value())); auto user_info = Object::create_empty(m_ctx); for (auto& kvp : error.user_info) { Object::set_property(m_ctx, user_info, kvp.first, Value::from_string(m_ctx, kvp.second)); } Object::set_property(m_ctx, error_object, "userInfo", user_info); typename T::Value arguments[2]; arguments[0] = create_object>(m_ctx, new WeakSession(session)); arguments[1] = error_object; Function::call(m_ctx, m_func, 2, arguments); } private: const Protected m_ctx; const Protected m_func; }; template void UserClass::session_for_on_disk_path(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { auto user = *get_internal>(this_object); if (auto session = user->session_for_on_disk_path(Value::validated_to_string(ctx, arguments[0]))) { return_value.set(create_object>(ctx, new WeakSession(session))); } else { return_value.set_undefined(); } } template void SessionClass::get_config(ContextType ctx, ObjectType object, ReturnValue &return_value) { if (auto session = get_internal>(object)->lock()) { ObjectType config = Object::create_empty(ctx); Object::set_property(ctx, config, "user", create_object>(ctx, new SharedUser(session->config().user))); Object::set_property(ctx, config, "url", Value::from_string(ctx, session->config().realm_url)); if (auto* dispatcher = session->config().error_handler.template target>()) { auto& handler = *dispatcher->func().template target>(); Object::set_property(ctx, config, "error", handler.func()); } return_value.set(config); } else { return_value.set_undefined(); } } template void SessionClass::get_user(ContextType ctx, ObjectType object, ReturnValue &return_value) { if (auto session = get_internal>(object)->lock()) { return_value.set(create_object>(ctx, new SharedUser(session->config().user))); } else { return_value.set_undefined(); } } template void SessionClass::get_url(ContextType ctx, ObjectType object, ReturnValue &return_value) { if (auto session = get_internal>(object)->lock()) { if (util::Optional url = session->full_realm_url()) { return_value.set(*url); return; } } return_value.set_undefined(); } template void SessionClass::get_state(ContextType ctx, ObjectType object, ReturnValue &return_value) { static const std::string invalid("invalid"); static const std::string inactive("inactive"); static const std::string active("active"); return_value.set(invalid); if (auto session = get_internal>(object)->lock()) { if (session->state() == SyncSession::PublicState::Inactive) { return_value.set(inactive); } else if (session->state() != SyncSession::PublicState::Error) { return_value.set(active); } } } template void SessionClass::simulate_error(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &) { validate_argument_count(argc, 2); if (auto session = get_internal>(this_object)->lock()) { SyncError error; error.error_code = std::error_code(Value::validated_to_number(ctx, arguments[0]), realm::sync::protocol_error_category()); error.message = Value::validated_to_string(ctx, arguments[1]); SyncSession::OnlyForTesting::handle_error(*session, std::move(error)); } } template void SessionClass::refresh_access_token(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &) { validate_argument_count(argc, 2); if (auto session = get_internal>(this_object)->lock()) { std::string access_token = Value::validated_to_string(ctx, arguments[0], "accessToken"); std::string realm_url = Value::validated_to_string(ctx, arguments[1], "realmUrl"); session->refresh_access_token(std::move(access_token), std::move(realm_url)); } } template class SyncClass : public ClassDefinition { using GlobalContextType = typename T::GlobalContext; using ContextType = typename T::Context; using FunctionType = typename T::Function; using ObjectType = typename T::Object; using ValueType = typename T::Value; using String = js::String; using Object = js::Object; using Value = js::Value; using Function = js::Function; using ReturnValue = js::ReturnValue; using NativeAccessor = realm::NativeAccessor; public: std::string const name = "Sync"; static FunctionType create_constructor(ContextType); static void set_sync_log_level(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); static void set_verify_servers_ssl_certificate(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); // private static void populate_sync_config(ContextType, ObjectType realm_constructor, ObjectType config_object, Realm::Config&); // static properties static void get_is_developer_edition(ContextType, ObjectType, ReturnValue &); MethodMap const static_methods = { {"setLogLevel", wrap}, {"setVerifyServersSslCertificate", wrap} }; }; template inline typename T::Function SyncClass::create_constructor(ContextType ctx) { FunctionType sync_constructor = ObjectWrap>::create_constructor(ctx); PropertyAttributes attributes = ReadOnly | DontEnum | DontDelete; Object::set_property(ctx, sync_constructor, "User", ObjectWrap>::create_constructor(ctx), attributes); Object::set_property(ctx, sync_constructor, "Session", ObjectWrap>::create_constructor(ctx), attributes); // setup synced realmFile paths ensure_directory_exists_for_file(default_realm_file_directory()); SyncManager::shared().configure_file_system(default_realm_file_directory(), SyncManager::MetadataMode::NoEncryption); return sync_constructor; } template void SyncClass::set_sync_log_level(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1); std::string log_level = Value::validated_to_string(ctx, arguments[0]); std::istringstream in(log_level); // Throws in.imbue(std::locale::classic()); // Throws in.unsetf(std::ios_base::skipws); util::Logger::Level log_level_2 = util::Logger::Level(); in >> log_level_2; // Throws if (!in || !in.eof()) throw std::runtime_error("Bad log level"); realm::SyncManager::shared().set_log_level(log_level_2); } template void SyncClass::set_verify_servers_ssl_certificate(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1); bool verify_servers_ssl_certificate = Value::validated_to_boolean(ctx, arguments[0]); realm::SyncManager::shared().set_client_should_validate_ssl(verify_servers_ssl_certificate); } template void SyncClass::populate_sync_config(ContextType ctx, ObjectType realm_constructor, ObjectType config_object, Realm::Config& config) { ValueType sync_config_value = Object::get_property(ctx, config_object, "sync"); if (!Value::is_undefined(ctx, sync_config_value)) { auto sync_config_object = Value::validated_to_object(ctx, sync_config_value); ObjectType sync_constructor = Object::validated_get_object(ctx, realm_constructor, std::string("Sync")); Protected protected_sync(ctx, sync_constructor); Protected protected_ctx(Context::get_global_context(ctx)); EventLoopDispatcher bind([protected_ctx, protected_sync](const std::string& path, const realm::SyncConfig& config, std::shared_ptr) { HANDLESCOPE if (config.user->is_admin()) { // FIXME: This log-in callback is called while the object store still holds some sync-related locks. // Notify the object store of the access token asynchronously to avoid the deadlock that would result // from reentering the object store here. auto thread = std::thread([path, config]{ auto session = SyncManager::shared().get_existing_active_session(path); session->refresh_access_token(config.user->refresh_token(), config.realm_url); }); thread.detach(); } else { ObjectType user_constructor = Object::validated_get_object(protected_ctx, protected_sync, std::string("User")); FunctionType refreshAccessToken = Object::validated_get_function(protected_ctx, user_constructor, std::string("_refreshAccessToken")); ValueType arguments[3]; arguments[0] = create_object>(protected_ctx, new SharedUser(config.user)); arguments[1] = Value::from_string(protected_ctx, path.c_str()); arguments[2] = Value::from_string(protected_ctx, config.realm_url.c_str()); Function::call(protected_ctx, refreshAccessToken, 3, arguments); } }); std::function error_handler; ValueType error_func = Object::get_property(ctx, sync_config_object, "error"); if (!Value::is_undefined(ctx, error_func)) { error_handler = EventLoopDispatcher(SyncSessionErrorHandlerFunctor(ctx, Value::validated_to_function(ctx, error_func))); } ObjectType user = Object::validated_get_object(ctx, sync_config_object, "user"); SharedUser shared_user = *get_internal>(user); if (shared_user->state() != SyncUser::State::Active) { throw std::runtime_error("User is no longer valid."); } std::string raw_realm_url = Object::validated_get_string(ctx, sync_config_object, "url"); // FIXME - use make_shared config.sync_config = std::shared_ptr(new SyncConfig{shared_user, raw_realm_url, SyncSessionStopPolicy::AfterChangesUploaded, std::move(bind), std::move(error_handler)}); config.schema_mode = SchemaMode::Additive; config.path = realm::SyncManager::shared().path_for_realm(shared_user->identity(), raw_realm_url); } } } // js } // realm